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(..);
}