Class Design


Member Accessibility

When declaring classes, methods and fields, do not rely upon the default member accessibility.  Instead, explicitly decorate each with “public”, “private”, etc. as necessary.  This will aid maintainability, as other developers may not be familiar with default accessibility.

Constructors

If you do not specify at least one non-static constructor, an empty parameterless public constructor is created automatically during compilation.  Relying on this behavior is a dangerous practice.  If another developer later adds an explicit constructor accepting parameters, the compiler will no longer include the default parameterless constructor – this will break any existing client code which relied upon the default constructor.

Always explicitly code the parameterless constructor if it is needed.  If it is not needed and you do not have any other constructors which accept parameters, create the parameterless one but mark it as private.  [For more information on classes with no constructors, see Static Classes on page 2.]

Example – Explicitly adding parameterless constructor, not relying on default
public class Customer
{
    public Customer()
    {
        // Add implementation
    }
}

Example – Marking the parameterless constructor private when no constructors should exist
public class Customer
{
    private Customer() {} 
}

Example – Not including the parameterless constructor because another constructor exists
public class Customer
{
    public Customer(string firstName, stringLastName)
    {
        // Implementation
    }
}

Property Order Dependencies

Do not write classes which expect property A to be set before setting B - properties of classes must be able to be assigned to in any order.  Ensure the class’s constructor returns the instance in a state ready for such use.

Public and Protected Virtual Methods

In non-sealed classes, you should generally mark any public or protected methods as virtual.  This allows straightforward specialization of the class without having to resort to the new keyword, which results in different (and generally misunderstood) behavior.

Using Structures

Consider using a structure instead of a class when you wish to model a small set of value types and optionally a minor amount behavior around those values.  Think of a structure as a small and fast value type data container.  As structures are created on the stack they will yield performance advantages over classes when used properly.

That same performance advantage gained by using the stack might become a liability when too many fields are contained within the same struct.  When too many fields are used, a point of diminishing returns may be reached where the system spends more time managing the stack to access struct values than it would using the direct references to values that a class provides.  While there is no absolute number defining how many fields are “too many”, keep this potential behavior in mind when designing your systems.

As structs do not support inheritance (although they can implement interfaces), if there is a chance that what you are modeling will grow to serve a more complex purpose, use a class instead.

Additionally, structs lose performance advantages when used in collections because collections access their entries as reference types.  Therefore, each access to a struct will require boxing operations – these generally outweigh the stack-based benefits a struct provides.

Overriding Object.ToString()

When implementing a new class, remember to implement an override for Object.ToString() to render a meaningful text representation of the current object instance.  Often, this string represents the state of the instance for debugging/logging purposes.  Document what it returns using a XML documentation.

Example:
namespace Examples
{
    public class Customer
    {
        private string _name;

        ...

        ///<summary>
        ///Returns the current Customer name.
        ///</summary>
        public override string ToString()
        {
            return _name;
        }
    }
}

The default Object implementation of ToString() returns the full name of the class.  From the above example Customer class, an instance rendered with Console.Write(customer) would have printed “Examples.Customer” without the ToString() override, but with the override now prints the string assigned to the _name field.

Object Comparison

If ordering or comparison of instances of your class would make sense, be certain to implement the IComparable interface.  Implement IComparable.CompareTo() and overload the operators <, >, <= and >= (in terms of your CompareTo implementation.)

Also, consider overriding Object.Equals() (and the == and != operators) if you implement the IComparable interface.  (See Object Equality below.)

Example:
public class SomeClass : IComparable
{
    private int _someID;

    int IComparable.CompareTo(object o)
    {
        return (_someID – ((SomeClass)o)._someID);
    }

    public static bool operator < (SomeClass lhs, SomeClass rhs)
    {
        return ((IComparable)lhs).CompareTo(rhs) < 0;
    }

    public static bool operator > (SomeClass lhs, SomeClass rhs)
    {
        return ((IComparable)lhs).CompareTo(rhs) > 0;
    }

    public static bool operator <= (SomeClass lhs, SomeClass rhs)
    {
        return ((IComparable)lhs).CompareTo(rhs) <= 0;
    }

    public static bool operator >= (SomeClass lhs, SomeClass rhs)
    {
        return ((IComparable)lhs).CompareTo(rhs) >= 0;
    }
}

[Note:  This example uses an explicitly implemented interface to reduce method clutter.  For details on this approach, see Explicit Interface Implementation on page 29.]

Object Equality

If the concept of equivalence applies to instances of your class, consider overriding Object.Equals() and the equality (==) and inequality (!=) operators.  Observe the following when doing so:

  1. If you override the behavior of the equality (==) operator, also override Object.Equals().
  2. x.Equals(x) must return true.
  3. x.Equals(y) must return the same value as y.Equals(x).
  4. If x.Equals(y) and y.Equals(z) both return true, x.Equals(z) must also return true.
  5. Implement Object.GetHashCode() whenever implementing Object.Equals() (VS.Net will flag a warning reminder if you do not.)  Equal instances should return the same hash code from GetHashCode().  Ideally, the hash value should be based on immutable values (so the hash remains the same over the life of the instance, regardless of changes to its state.)

[For details on implementing GetHashCode(), see VS.Net help under “Object.GetHashCode Method”, or pages 162-4 of Applied Microsoft .Net Framework Programming by Richter.]


Equality of Value Types

For value type classes, override Object.GetHashCode(), then override Object.Equals() using an underlying call to a type-safe version of Equals to avoid boxing overhead.

Example:
public class ValueType
{
    private <ReferenceOrValueType> _data;

    public override int GetHashCode()
    {
        return _data.GetHashCode();
    }

    public override bool Equals(object o)
    {
        if (!(o is ValueType))
        {
            // Not an instance of ValueType, cannot be equal
            return false;
        }

        // Call type-safe version with casted reference
        return this.Equals((ValueType)o);
    }
 
    public bool Equals(ValueType rhs)
    {
        return _data.Equals(rhs._data);  // If _data is a value type
      /* --OR-- */
        return Object.Equals(_data, rhs._data); // If _data is a reference type
    }

    public static bool operator==(ValueType lhs, ValueType rhs)
    {
        return lhs.Equals(rhs);  // Evaluate using Equals override
    }

    public static bool operator!=(ValueType lhs, ValueType rhs)
    {
        return !lhs.Equals(rhs); // Evaluate using Equals override
    }
}

Equality of Reference Types

As there are default implementations for the equality (==) operator with reference types, you should use caution when overriding equality on reference types.  Most reference types that define equivalence as whether two pointers reference the same instance should not override the equality operator (even if they do override Object.Equals().)  

However, if you are implementing a reference type that defines equality based on values each instance contains, rather than the testing whether two references point to the same instance, you should override the equality operator.  This is (essentially) how the String class (a reference type with value type semantics) behaves.

[For a complete discussion of object equality, see “Object Equality and Inequality” in Applied Microsoft .Net Framework Programming by Richter.]

Exposing Reference Types Safely

When a class exposes a reference type via a property, you must be aware that changes may be made to that instance by the calling code.  For example, consider a property, “List” that exposes a private ArrayList instance. 

private ArrayList _members;

public ArrayList Members
{
    get {return _members;}
    set {_members = value;}
}

This property basically allows any modifications to be made to that instance – even if the “set” is removed!  The issue is that once the calling code gets the reference/pointer, any action taken, such as “o.Members.Add(something)”, will affect that private _members ArrayList.  In this case, the user could add an object of any type, not just a “Member” instance, or even null.

It is up to the class designer to determine when and to what extent private reference types exposed via properties (or returned by methods) need to be safeguarded.  Any countermeasures must be fully documented in the class’ XML documentation, so a developer utilizing the class is aware of the behavior.

Some potential countermeasures:

  1. Clone the reference type and return the copy via the property [see Object Cloning below for details.]  The property should not have a “set”, and in fact probably shouldn’t be a property at all.  Consider creating a method with a helpful name, such as “GetMemberListCopy()”.

  2. Some reference types enable read-only modes.  For example, the ArrayList class exposes a static ReadOnly() method that is used to return a wrapper of the given ArrayList that prevents changes.  The above example would become:

Private ArrayList _members;

public ArrayList Members
{
    get {return ArrayList.ReadOnly(_members);}
    // set {_members = value;}
}

  1. Do not expose the reference type directly via a property or method at all.  Expose only “helper” properties or methods that allow indirect/monitored access to the state of the internal reference type.  For example, you could add methods like “GetMember()” and “AddMember(…)” that allow monitored access to the private ArrayList in the above example.

Object Cloning

If your class needs to support the copying of one instance into a new instance, you should implement the ICloneable interface.  You must decide and document whether the Clone() method returns a shallow or deep copy of the instance.  A shallow copy simply duplicates value types and if any reference types are in the object, the reference (pointer) is duplicated.  A deep copy duplicates both value types and the contents of any contained reference types.  A shallow copy may result in instances with shared reference data – changing one would affect the other.  By contrast, a deep copy results in two instances that share nothing.

If you choose to support shallow copies, implement Clone() in terms of a call to Object.MemberwiseClone().

Shallow Copy Clone Example:
public class SomeClass : ICloneable
{
    private int _value;
    private string _someString;
    private Hashtable _ht;

    ///<summary>
    ///Returns a new instance that is a shallow copy of the current instance
    ///</summary>
    public object Clone()
    {
        return this.MemberwiseClone();
    }
}

If you elect to return a deep copy, consider implementing it in terms of a “copy constructor”.  A copy constructor is a version of your class’ constructor that accepts an instance of it’s own type and sets all internal state of the instance currently being created to be equivalent.  It is typically marked private or protected.

Deep Copy Clone Example:
public class SomeClass : ICloneable
{
    private int _value;
    private string _someString;
    private Hashtable _ht;

    // Generates a new instance of SomeClass the state of
    // which is copied from a source SomeClass instance.
    private SomeClass(SomeClass rhs)
    {
        _value = rhs._value;
        _someString = rhs._someString;
        _ht = (Hashtable)rhs._ht.Clone();
    }

    ///<summary>
    ///Returns a new instance that is a deep copy of the current instance
    ///</summary>
    public object Clone()
    {
        return new SomeClass(this);
    }
}


Static Classes

If a class consists only of static methods and fields, add a private parameterless (default) constructor to ensure a programmer will not accidentally create an instance of the class (which would have no practical use.)

In addition, consider marking the class sealed.

Example:
public sealed class FunctionLibrary
{
    private FunctionLibrary(){}   //”private” ensures class cannot be instantiated

    public static string DoSomething(...){...}
    public static int DoSomethingElse(...){...}
}

Static Methods for Utilities and Factories

Static classes or instance classes with static methods are a good approach for building utility or factory classes (when instance state is not important.) 

If possible, create a utility class so that it can be called directly without creating and managing an instance:
double x = 12345;
double log = System.Math.Log10(x);

Instead of:
double x = 12345;
System.Math math = new System.Math();
double log = math.Log10(x);  // This is a fictional method, but illustrates the point

// Alternatively, inlining the instance as: 
double log = new System.Math().Log10(x);
// would be shorter, but still requires the wasteful overhead of the instance.  Avoid this.

Remember, a static class can have state, but since it is static (class-level) it has to be meaningful and correct for all callers of that single static instance and any changes to the static state made outside of a constructor should be threadsafe as well.

Finalization, Garbage Collection and Disposing

If (and only if) your class has holds references to unmanaged resources or, to a lesser extent, must execute cleanup code before it is garbage collected, you should implement a finalizer.  (A finalizer is somewhat similar to what a destructor is in C++.)

However, given the nondeterministic nature of the garbage collection system in the CLR, it may be necessary to control the timing of the execution of your finalization code, so a simple destructor is often insufficient.  In these cases, you should implement the IDisposable interface as follows:

public class ClassNeedingDisposal : IDisposable
{
    private bool _isDisposed = false;   // Flag to prevent double disposal

    ~ClassNeedingDisposal()
    {
        Dispose(false);       // Call Dispose here in case it was not called manually

        // You might consider adding this debug call to warn developers to call Dispose:
        //System.Diagnostics.Debug.Assert(false, "Dispose() was not called on an instance of the class \"ClassNeedingDisposal\".");
    }

    public void Dispose()
    {
        Dispose(true);        // Call Dispose with flag to kill managed resources
        GC.SuppressFinalize(this);    // Tell CLR not to call ~ClassNeedingDisposal
    }

    protected virtual void Dispose(bool disposeManaged)
    {
        if(!_isDisposed)
        {
            if(disposeManaged)
            {
                // Dispose any managed resources held by this instance
            }

            // Dispose any unmanaged resources
            // Do NOT call/use any managed references held by this instance here!

            _isDisposed = true;
        }
    }
}

NOTE:  Take care to only implement a finalizer or the IDisposable interface when necessary as doing so adds to system overhead.  Most classes do not require finalization.

Why is a Boolean passed to a virtual implementation of Dispose?  When a finalizer is called by the runtime, you can no longer be certain that the managed resources held by the instance are still available.  In this case, you should not touch nor rely upon reference to managed resources.  Doing so may result in what is known as instance “resurrection” and hard-to-diagnose side effects.  False is passed in from the finalizer to tell Dispose not to touch managed resources. 

It is also good practice in a disposable class to reference the _isDisposed Boolean in the methods of that class to ensure they are not called after a call to Dispose().  In such cases, throw a System.ObjectDisposedException.


Example:
public string DoSomething(int someID)
{
    if(_isDisposed)
    {
        throw new ObjectDisposedException(“Unable to process – Dispose() has already been called on this instance.”);
    }

    ...

}

Additionally, a class that implements IDisposable because it holds references to unmanaged resources should inherit from System.MarshalByRefObject.  This is because unmanaged resources cannot be marshaled by value in Remoting scenarios.

Wrap Disposable Objects with using

When working with an instance of a class that implements IDisposable, your code will be simpler and less error-prone if you wrap the lifetime of that instance with the using statement.  When control passes outside the using block, the Dispose method of the object is automatically called – even when an Exception has been thrown.  Multiple using statements can be stacked before a code region.

Example:
using (ClassNeedingDisposal cnd = new ClassNeedingDisposal())
using (ClassNeedingDisposal cnd2 = new ClassNeedingDisposal())
{

   ... use cnd and cnd2 variables here

}  // cnd.Dispose() and cnd2.Dispose() called automatically on exit (or exception)

During compilation to MSIL, the using construct expands to a try/finally block.

[For a description of GC and finalization, see “Pinning and Memory Management” in Inside C#, 2nd Edition by Archer and Whitechapel.]


Optional Parameters via Method Overloading

You may be used to the Optional keyword from Visual Basic.  The concept of optional parameters is not directly supported in C#, but there are ways to simulate them.  By using method overloading, a method can expose numerous variations of its parameter list and chain them to functionally create default and/or optional settings.

For example, if you wanted a method to accept a string, and optionally an integer, you could code the method signatures as follows:
public string ChainedExample(string a)
{
       // Call next version, supplying a default integer value
       return ChainedExample(a, SOME_DEFAULT_VALUE);
}
public string ChainedExample(string a, int b)
{
       // Do the real processing here, either with a user-provided int b
       // or the default one passed by the above ChainedExample overload.
return ...
}

A developer could call either ChainedExample(“test”) or ChainedExample(“test”, 123).  Note also that the return value of the second method is itself returned to the caller by the first method.  Methods overloaded in this fashion are automatically represented as a list in VS.Net IntelliSense with arrow selectors and the text “x of y”.

Explicit Interface Implementation

An interface is typically implemented exposing the members with a public access modifier as follows:

Example – Publicly implemented  interface
public class NormalWay : IComparable
{
    private int _someID;

    public CompareTo(object o)
    {
        return (_someID – ((NormalWay)o)._someID);
    }
}

However, there is an alternate, but infrequently used, way to implement an interface, called explicit implementation.  This approach qualifies the implemented interface member with the name of the interface.  Additionally, no access modifiers are (or can be) used:

Example – Explicitly implemented interface
public class ExplicitWay : IComparable
{
    private int _someID;

    int IComparable.CompareTo(object o)
    {
        return (_someID – ((ExplicitWay)o)._someID);
    }
}

The result of using the second approach is that the implemented member (in this case CompareTo()) will not appear at the class level of the implementing class.  In order to see that member, you must cast your object reference to the interface.

For example, to call CompareTo() on an instance of the ExplicitWay class, you need to do something like the following:
ExplicitWay ew1 = new ExplicitWay (...);
ExplicitWay ew2 = new ExplicitWay (...);
IComparable ic1 = (IComparable)ew1;
int result = ic1.CompareTo(ew2);
Trying “ew1.CompareTo(ew2)” results in a compilation error because you need an IComparable view, not an ExplicitWay view, to see it.

So, why do this?

There are two main reasons to consider using explicit implementation.  The first is name hiding. This is useful for reducing the clutter of the class implementing an interface.  If the specific members implemented for an interface are not of general use to a consumer of the class, consider using explicit implementation to hide those members from the “main view” of the class.  They are of course still very much available, but the consumer must first cast the reference to the correct interface before using that member.

The second, but more important, reason is for resolving naming conflicts.  Say a method, Execute(), is required with the same signature by more than one interface which your class implements.  Using explicit implementation allows you to code specific implementations for each Execute() method appropriate to each interface:

Example – Using explicit implementation to avoid name conflicts

public interface IDoSomething
{
   void Execute();
}

public interface IDoSomethingElse
{
   void Execute();
}

public class ExplicitWay : IDoSomething, IDoSomethingElse
{
    void IDoSomething.Execute()
    {
        Console.WriteLine(“IDoSomething is doing something.”);
        return;
    }

    void IDoSomethingElse.Execute()
    {
        Console.WriteLine(“IDoSomethingElse is doing something else.”);
        return;
    }
}

The above example would require a consumer of ExplicitWay to cast the reference to either interface before invoking Execute().  This approach helps to avoid ambiguity.

[For a more detailed discussion, see “Explicit Interface Member Name Qualification” in Inside C#, 2nd Edition by Archer and Whitechapel.]



Comments

Popular posts from this blog

Cloud Computing in simple

How to Write an Effective Design Document

Bookmark