Overriding the Equals Method

Problem
You want to know when to override the Equals method.

Solution
Override the Equals instance method when two objects are deemed equal even if they aren’t the same instance of the type.

Comments
You never want the Equals method to throw an exception. You want it to return false if the argument is a null object reference or a different type.

Example

/* This class redefines the Equals method so that two
 * Triangle instances are considered equal when the
 * value of their Side property is the same.
 */
public class Triangle {
    // In the real world, this would be a property.
    public double Side;

    public override bool Equals(object obj) {
        if ( this == obj ) {
            // The argument is the same object as this.
            return true;
        }
        else if ( obj == null || obj.GetType() != this.GetType() ) {
            // The argument is null or of a different type.
            return false;
        }
        else {
            Triangle other = (Triangle) obj;
            return (this.Side == other.Side);
        }
    }
}

Overriding the ToString Method

Problem
You want to know when it’s a good idea to override the ToString method.

Solution
You should consider overriding the ToString method when you want to provide a common way to display an object’s value.

Example

public class Account {
    // In a real-world class, these would be properties.
    public string AccountName;
    public string AccountType;

    public override string ToString() {
        return this.AccountName + " " + this.AccountType;
    }
}

Overloaded Virtual Methods

Problem
You want to know what to do when you have an overloaded method and want to allow developers to override it in derived types.

Solution
Make the most complete version of the method virtual and have all other versions call it.

Comments
This practice simplifies the process of redefining the method in derived classes because inheritors only have to override one method. There is an alternative approach. You can make overloaded methods sealed–not marked virtual–and have them call a protected overridable method. The naming convention for the called method is MethodNameCore. This approach requires a little more code, but it expresses the relationship between public sealed and protected overridable methods more clearly. This technique is frequently used in the .NET Framework, and a variation of it is often applied to properties.

// The original approach.
public void DoWork(int argOne) {
    // Call the other overload.
    DoWork(argOne, "Years");
}

// The only method that needs to be overridden in a derived class.
public virtual void DoWork(int argOne, string argTwo) {
    // Do the work here.
    ...
}

// The alternative approach.
public void DoWork(int argOne) {
    // Call the protected method.
    DoWorkCore(argOne, "Years");
}

public void DoWork(int argOne, string argTwo) {
    // Call the protected method.
    DoWorkCore(argOne, argTwo);
}

// The only method that needs to be overridden in a derived class.
protected virtual void DoWorkCore(int argOne, string argTwo) {
    // Do the work here.
}

Splitting Tasks into Overridable Methods

Problem
You have several long or complicated tasks in non-sealed classes and want to know how to implement them.

Solution
Split the tasks into two or more protected and overridable methods.

Comments
Inheriting classes can easily redefine individual sub-tasks when complex methods are split up.

Example

// Incorrect implementation.
public class Document {
    public virtual void Read() {
        // Read the document's header.
        ...
        // Read the document's body.
        ...
        // Read the document's footer.
        ...
    }
}

// Correct implementation.
public class Document {
    // Note the method is not overridable.
    public void Read() {
        ReadHeader();
        ReadBody();
        ReadFooter();
    }

    protected virtual void ReadHeader() {
        // Read the document's header.
        ...
    }

    protected virtual void ReadBody() {
        // Read the document's body.
        ...
    }

    protected virtual void ReadFooter() {
        // Read the document's footer.
        ...
    }
}

// New type like Document with additional functionality.
public class DocumentWithImages : Document {
    protected override void ReadBody() {
        // First, read the document's body.
        base.ReadBody();
        // Next, parse any images.
        ...
    }
}

Deciding on Virtual Members

Problem
You want to know when to make properties or methods virtual.

Solution
General rule: Don’t make a property or method virtual unless you’re sure of a scenario where derived classes must modify their behavior. Exception rule: Make public and protected members in non-sealed classes virtual unless you have a reason to make them sealed.

Comments
You may have noticed that these two rules contrast each other. They contrast because they reflect two different design approaches. The general rule places more design responsibility on the developer. It requires them to think about how inheritors could misuse base class members. The exception rule places more responsibility on inheritors but simplifies development. The recommendation is to favor the general rule. Spending more time designing an object hierarchy saves time later. Only adopt the exception rule when you’re unsure how inheritors will use the base class. Better performance is a secondary benefit of the general rule. Invoking a non-virtual or sealed member is faster than invoking a virtual one. Limit the number of virtual members to as few as possible.

Number of Members in Interfaces

Problem
You want to know how many methods to include in an interface.

Solution
Limit interfaces to six methods. If additional methods are needed, create base interfaces and derive from them.

Comments
A great example of interface inheritance is IList in the System.Collections namespace. The interface contains 13 methods if you include extension methods, and many of them inherit from ICollection and IEnumerable.

Constructors for Abstract Types

Problem
You want to know how to implement constructors in abstract types.

Solution
Assign a protected scope to constructors for abstract classes.

Comments
Give constructors in abstract classes protected visibility since they are intended only to be called by derived types. The C# compiler will automatically create a protected constructor in abstract classes for you. However, it’s recommended that you explicitly define one.

Example

// OK. The default constructor is appropriately handled by the compiler.
public abstract class File {
    ...
}

// The recommended approach. The constructor is explicitly defined.
public abstract class File {
    // Leave the constructor empty.
    protected File() {
    }
    ...
}