On Designing Good Libraries -- Part III

Good discussion on the first two.... Let's see how this goes.

 

 

 

Fields

         Never use publicly exposed instance fields

         Properties offer more flexibility at minimal cost

         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

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

 

 

Published 13 July 03 11:13 by BradA

Comments

# Frans Bouma said on July 14, 2003 3:42 AM:
A reference type by value copied the reference * Commonly used: public void Insert (object value) {..} I might be wrong, but 'value' in the example above is passed by reference since it's an object, am I right? Also, an important (IMHO ;)) thing about events is missing from the list (and from MS' documentation), which is: if object A exposes an event, and object B subscribes on that event by defining a new event handler (A.Event += new EventHandler(name); ), and B goes out of scope it will not be garbage collected until A is also garbage collected, because A holds a reference to a method in B.
# Lawrence Oluyede said on July 14, 2003 5:23 AM:
>I might be wrong, but 'value' in the example above is passed by reference since it's an object, am I right? Yes and no. In Java is a well known subtle issue. It's a matter of definitions. In the example above you pass the object reference by value. Hence what Brad said it's right.
# Steve said on July 14, 2003 9:17 AM:
Thanks for the excellent tips...keep them coming!
# Frans Bouma said on July 14, 2003 9:22 AM:
what's 'passing a reference by value' ? You pass OR a reference (i.e. pointer) OR a value (i.e. a copy of the original object). In compiler builder land, 'call by value' is used to specify the calling scheme where the passed in variable's contents is copied into the stackframe. Call by reference is used to specify the calling scheme where the passed in variable's address is copied into the stackframe. The latter is used for objects( or structures pointed by by pointers :) ) or references to value typed variables. The reason I asked was that when you do not specify 'ref' you can still modify a passed in object in a called method and use the modification results in the caller. So passing by 'value' is not possible when using with object typed variables/parameters (you have to do that yourself: copy the object and pass that in). This is also a point of discussion where some people argued for the keyword 'const' in the method header, which effectually makes a parameter called by value, even though it is passed in by reference. :) (at the moment this is not possible).
# Lawrence Oluyede said on July 14, 2003 11:28 AM:
I think that you didn't understand what I mean. When you pass around value types you pass the value (the contained data) by default or if you specify ref you pass a reference (the address I guess) of the variable. This is what I call "passing a value type by value or by reference". When you pass around an object you don't pass its value but the address of the object location. This is what I call "passing the reference by value". As Brad stated there is another passing method that is passing the object by reference. In this situation you pass a reference of the reference (a pointer) and this is what I call "passing the reference by reference". It's a matter of terminology.
# Michael Bouck said on July 14, 2003 1:32 PM:
On Field usage (I might be starting a religious war here!): I personally believe that non-public fields should be somehow prefix-notated as such (_foo, m_foo, etc.) This accomplishes several things: 1) It is immediately obvious that you are referencing a non-public entity. 2) It removes the necessity of using the "this" operator to resolve abiguity. 3) It fits into well-known paradigms for programmers with a C++/Java background. One caveat of using "_"-previxed fields, though, is that it's not CLS-compliant and will cause the C# compiler to throw a error if CLS compliance is turned-on. In any event, I feel the pros outweigh the cons enough that this guidance should be formalized in the guidelines...
# Rick Byers said on July 14, 2003 1:41 PM:
> Property getters should be simple In my oppinion, this cannot be overstated. When using a property it is natural to assume that calling the getter has roughly the same implications as a field access (eg. performance, blocking, side effects, locking, etc.). Infact, the VStudio debugger makes this assumption by having property evaulation enabled in the watch windows by default, which results in MANY getter calls (my tests with VS.Net 2002 showed ~18 calls per property per debug step even after the object containing the property was collapsed or otherwise made not visible!). Due to non-trivial property getters (mostly in the apps I'm debugging, but I believe there are a few in the .NET framework) I've been forced to disable automatic property evaluation during debuging. OtherwiseI I occasionally run into horrible performance problems (the VStudio IDE moves very slowly) and sometimes runtime deadlocks (VS is fine, but debugger won't make any progress). My rule of thumb is if my getter is going to be much more complex than "return someField", I use a 'Get' method instead. Thanks for the great information Brad!
# Kevin Dente said on July 14, 2003 1:45 PM:
Here's a couple of questions/comments: You say "For static fields, do not include a prefix on a public field name". In general, it looks like MS make an effort to expunge hungarian notation from the coding conventions, which I think is goodness. However, there is one place where a form of it remains - the "I" prefix in interfaces. Why the hold over? Personally, I think it junks up the APIs and thus subtly discourages the use of interface-based programming. You also comment that it's "common to have read-only public access and protected write access". One of the few things that I don't like about C# is the requirement that property getters and setter have the same accessibility, which requires ugliness like separate Set methods. Any hope that this will be addressed in the future? Thanks for all the great info. Good stuff.
# Lawrence Oluyede said on July 14, 2003 2:16 PM:
>However, there is one place where a form of it remains - the "I" prefix in interfaces. Why the hold over? Personally, I think it >junks up the APIs and thus subtly discourages the use of interface-based programming. Why do you think it messes up? I like "I" in front of interface. It's a widely used convention (COM, Java) and it allows you to quickly identify interfaces in the object trees.
# Frans Bouma said on July 14, 2003 3:37 PM:
"As Brad stated there is another passing method that is passing the object by reference. In this situation you pass a reference of the reference (a pointer) and this is what I call "passing the reference by reference". " Hmm, I understand, but a reference of a reference would require a pointerpointer to access the actual data, thus a **pFoo. However, it doesn't matter if you specify 'ref' for an object or not, your code would work the same. This can mean 2 things: OR the CLR does the double pointer jump under the hood, OR 'ref' is not necessary for passing the object to a method :). If I understand you right, Lawrence, the CLR does the doublepointer logic under the hood, I thought 'ref' was not necessary for objects, that's the miscommunication we're having I think :)
# Thomas Freudenberg said on July 15, 2003 4:35 AM:
You're giving following example public class Button { private ButtonClickHandler onClickHandler; protected void OnClick (ClickEventArgs e) { if (onClickHandler != null) { // call the delegate if non-null onClickHandler(this, e); } } } According to the naming convention, shouldn't the ButtonClickHandler field be named 'Click', since some tips above you say "Name events with a verb, e.g. Click, Paint, DrawItem, DropDown"?
# Kevin Westhead said on July 15, 2003 1:30 PM:
Regarding byval and byref for reference types, here is how I understand it: void DoSomething(object someValue) A copy of the handle to someValue on the managed heap is passed to DoSomething. This means that you can still change the state of the object via its properties and methods, but you cannot change the original handle. In otherwords, setting someValue to null in the callee will not null the original object that was passed by the caller. void DoSomething(ref object someValue) The original handle to someValue on the managed heap is passed to DoSomething. Setting someValue to null in callee will null the original object that was passed by the caller.
# Lawrence Oluyede said on July 17, 2003 6:43 AM:
This is exactly what I meant.
# Diego Gonzalez said on July 27, 2003 11:27 AM:
Just my 2 cents.... "Any kind of member can be static", except class indexers, which can't be static.
# Brad Abrams said on April 13, 2004 6:18 PM:
# C#deSamurai said on August 26, 2004 12:35 PM:
# Tutto fa .NET said on August 27, 2004 12:45 PM:
New Comments to this post are disabled

Search

Go

This Blog

Syndication

Page view tracker