On Designing Good Libraries -- Part III
Good discussion
on the first two.... Let's see how this goes.
Fields"urn:schemas-microsoft-com:office:office" />
•
Never use publicly exposed
instance fields
•
Properties offer more flexibility at
minimal cost
•
"urn:schemas-microsoft-com:office:smarttags" />JIT inlines
simple property access
•
Easy to add cache or delay creation
in the future
•
For static fields, do not include a
prefix on a public field name
•
Example: 'g_' or 's_' to distinguish
static vs. non-static fields
const
•
Compile-time
evaluation
•
Stable across
versions
•
Always
static
readonly
•
Run-time
evaluation
•
Unstable across
versions
•
Static or
instance
class Math
{
public const double Pi =
3.14;
}
class Color
{
public static readonly Color Red = new
Color(...);
public static readonly Color Blue = new
Color(...);
public static readonly Color Green = new
Color(...);
}
Properties
•
Smart
fields
•
Calling syntax like
fields
•
Flexibility of
methods
•
Use read only properties where
appropriate
•
Do not use write-only
properties
•
Consider raising PropertyChanged
events
•
Property getters should be simple
and therefore unlikely to throw exceptions
•
Properties should not have
dependencies on each other
•
Setting one property should not
affect other properties
•
Properties should be settable in any
order
•
Common to have read-only public
access and protected write access
public class
MyClass {
private string
name;
public string Name
{
get
{
return name;
}
}
protected void SetName (string
name)
{
this.name
= name;
}
}
Properties versus
Methods
•
Do use a
property:
•
If the member has a logical data
member
string Name
{get;} //Good
string
GetName() //Bad
"urn:schemas:contacts" />Guid GetNext(){} //Good
Guid Next {get;} //Bad
•
Do use a method:
•
If the operation is a conversion,
such as ToString()
•
If the getter has an observable side
effect
•
If order of execution is
important
•
If the method might not return
immediately
•
If the member returns an
array
EmployeeList
l = FillList();
for (int i =
0; i < l.Length; i++) {
if (l.All[i] ==
x){...}
}
//calling
code:
if
(l.GetAll()[i]== x) {...}
•
Creates a new snap shot of the array
each time through the loop
•
The GetAll() form makes this much
clearer
Properties:
Indexers
•
Use if the logical backing store is
an array
•
Almost always int or string
indexed
•
Rare to have multiple
indices
public char
this[int index] {get;}
String
s = "foo";
Console.WriteLine
(s[i]); // calls indexer
Events
•
Defining an
event
public
delegate void EventHandler(object sender,
EventArgs
e);
public class
Button: Control {
public
event EventHandler Click;
protected void OnClick(EventArgs
e) {
if
(Click != null)
Click(this, e);
}
}
•
Using an
Event
void
Initialize() {
Button b = new
Button(...);
b.Click += new
EventHandler(ButtonClick);
}
void
ButtonClick(object sender, EventArgs e)
{
MessageBox.Show("You pressed the
button");
}
•
Events are raised, not triggered or
fired
•
Name events with a
verb
•
E.g. : Click,
Paint, DrawItem, DropDown,
•
Event handlers have void return
type
public
delegate void MouseEventHandler (
object sender,
MouseEventArgs e);
•
Event handler delegates use a
signature that follows the event design pattern
•
Use strongly typed event data where
appropriate
public class
MouseEventArgs :
EventArgs { }
•
Able to add new members without a
breaking change
•
Provide a protected method to raise
the event
•
Named OnEventName
•
Make it virtual if extensibility is
needed
public class
Button {
private
ButtonClickHandler onClickHandler;
protected void OnClick
(ClickEventArgs e) {
if
(onClickHandler != null) {
// call the delegate if
non-null
onClickHandler(this, e);
}
}
}
•
Events are callbacks into arbitrary
user code
•
Do not assume anything about the
state of your object
•
Code
defensively
protected
void DoClick() {
PaintDown(); // paint button in depressed state
try {
OnClick(); //
call event handler
}
finally {
// window may be
deleted in event handler
if (windowHandle
!= null) {
PaintUp(); // paint button in normal state
}
}
}
Static
Members
•
Any kind of member can be
static
•
Static members
•
Cannot access instance
state
•
Cannot override or
specialize
•
Should be
thread-safe
•
Commonly used
for
•
Singleton
pattern
•
Utility
methods
•
Statics are the .NET equivalent of global variables or global
functions
•
Not object
oriented
•
Same evils as
global
•
But can be very useful, e.g.,
System.Math
Singleton
Pattern
•
Ensures that a class has only one
instance and provide a global point of access to it
•
This is not exactly the GoF pattern
(see threading section)
public
sealed class DBNull {
private DBNull() {}
public static readonly DBNull Value = new DBNull();
// instance
Methods...
}
//Calling
code
if (x ==
DBNull.Value) {..}
•
Notice this
class:
•
Is sealed to prevent sub-classing to
add instances
•
The Value is static for easy
access
•
The Value is readonly so it cannot be
modified
•
Has a private
constructor
•
The instance is immutable
•
No methods that can mutate its
state
Parameter
Passing
•
Value types and Reference types can
both be passed by value or by reference
•
A value type by value copies the
value
•
No side
effects
•
Commonly used
public int Add (int x, int y)
{..}
•
A value type by reference uses a
pointer to the value
•
Side effects
possible
•
Rarely used
public static int
Exchange (ref int location, int value) {..}
•
A reference type by value copied the
reference
•
Side effects are possible on mutable
types
•
Commonly used
public void Insert (object value)
{..}
•
A reference type by reference uses a
pointer to the reference variable
•
Side effects
possible
•
Almost never used
public static
int Method (ref object moreData) {..}
•
Using Ref and Out
parameters
•
Primarily used for
interop
•
Avoid directly exposing
publicly
•
May be used for extremely
performance-sensitive areas
•
Almost exclusively used with value
types
•
Ref is a CLR feature
•
Out is a C#
feature
•
Out parameter semantics downgrade to
Ref semantics in other languages
•
Ref and out designs are less
usable
public void
GetLocation (
ref int x, out int y) {..}
//calling
code
int x =
42;
int y;
b.GetLocation (ref x, out y);
public
struct Point {
public int X
{get;}
public int Y
{get;}
}
//calling
code
Point
p = b.Location;
Argument
Validation
•
Do argument checking on every
publicly exposed member
•
Catches errors early
(fail-fast)
•
Much easier to
debug
•
Powerful security
precaution
•
Throw meaningful
exceptions
•
Subclasses of
ArgumentException
public int
Count {
get {return
count;}
set {
if (value < 0
|| value >= MaxValue)
throw new ArgumentException(..);
}
}
public void Select (int
start, int end) {
if
(start < 0)
throw new
ArgumentException(..);
if
(end < start)
throw new
ArgumentException(..);
}