.NET Type Design Guidelines—Choosing Between Class and Interface
| CSharp-Online.NET:Articles |
| .NET Articles |
| © 2006 Microsoft Corp. |
Choosing Between Class and Interface
In general, classes are the preferred construct for exposing abstractions.
The main drawback of interfaces is that they are much less flexible than classes when it comes to allowing for evolution of APIs. Once you ship an interface, the set of its members is fixed forever. Any additions to the interface would break existing types implementing the interface.
A class offers much more flexibility. You can add members to classes that have already shipped. As long as the method is not abstract (i.e., as long as you provide a default implementation of the method), any existing derived classes continue to function unchanged.
Let's illustrate the concept with a real example from the .NET Framework. The System.IO.Stream abstract class shipped in version 1.0 of the framework without any support for timing out pending I/O operations. In version 2.0, several members were added to Stream to allow subclasses to support timeout-related operations, even when accessed through their base class APIs.
public abstract class Stream { public virtual bool CanTimeout { get { return false; } } public virtual int ReadTimeout{ get{ throw new NotSupportedException( ); { set { throw new NotSupportedException( ); } } } public class FileStream : Stream { public override bool CanTimeout { get { return true; } } public override int ReadTimeout{ get{ ... { set { ... } } }
The only way to evolve interface-based APIs is to add a new interface with the additional members. This might seem like a good option, but it suffers from several problems. Let's illustrate this on a hypothetical IStream interface. Let's assume we had shipped the following APIs in version 1.0 of the Framework.
public interface IStream { ... } public class FileStream : IStream { ... }
If we wanted to add support for timeouts to streams in version 2.0, we would have to do something like the following:
public interface ITimeoutEnabledStream : IStream { int ReadTimeout{ get; set; } } public class FileStream : ITimeoutEnabledStream { public int ReadTimeout{ get{ ... { set { ... } } }
But now we would have a problem with all the existing APIs that consume and return IStream. For example StreamReader has several constructor overloads and a property typed as Stream.
public class StreamReader { public StreamReader(IStream stream){ } public IStream BaseStream { get { } } }
How would we add support for ITimeoutEnabledStream to StreamReader? We would have several options, each with substantial development cost and usability issues:
Leave the StreamReader as is, and ask users who want to access the timeout-related APIs on the instance returned from BaseStream property to use a dynamic cast and query for the ITimeoutEnabledStream interface.
StreamReader reader = GetSomeReader(); ITimeoutEnabledStream stream = reader.BaseStream as ITimeoutEnabledStream; if(stream != null){ stream.ReadTimeout = 100; }
This option unfortunately does not perform well in usability studies. The fact that some streams can now support the new operations is not immediately visible to the users of StreamReader APIs. Also, some developers have difficulties understanding and using dynamic casts.
Add a new property to StreamReader that would return ITimeoutEnabledStream if one was passed to the constructor or null if IStream was passed.
StreamReader reader = GetSomeReader(); ITimeoutEnabledStream stream = reader.TimeoutEnabledBaseStream; if(stream!= null){ stream.ReadTimeout = 100; }
Such APIs are only marginally better in terms of usability. It's really not obvious to the user that the TimeoutEnabledBaseStream property getter might return null, which results in confusing and often unexpected NullReferenceExceptions.
Add a new type called TimeoutEnabledStreamReader that would take ITimeoutEnabledStream parameters to the constructor overloads and return ITimeoutEnabledStream from the BaseStream property. The problem with this approach is that every additional type in the framework adds complexity for the users. What's worse, the solution usually creates more problems like the one it is trying to solve. StreamReader itself is used in other APIs. These other APIs will now need new versions that can operate on the new TimeoutEnabledStreamReader.
The Framework streaming APIs are based on an abstract class. This allowed for an addition of timeout functionality in version 2.0 of the Framework. The addition is straightforward, discoverable, and had little impact on other parts of the framework.
StreamReader reader = GetSomeReader(); if(reader.BaseStream.CanTimeout){ reader.BaseStream.ReadTimeout = 100; }
One of the most common arguments in favor of interfaces is that they allow separating contract from the implementation. However, the argument incorrectly assumes that you cannot separate contracts from implementation using classes. Abstract classes residing in a separate assembly from their concrete implementations are a great way to achieve such separation. For example, the contract of IList<T> says that when an item is added to a collection, the Count property is incremented by one. Such a simple contract can be expressed and, what's more important, locked for all subtypes, using the following abstract class:
public abstract class CollectionContract<T> : IList<T> { public void Add(T item){ AddCore(item); this.count++; } public int Count { get { return this.count; } } protected abstract void AddCore(T item); private int count; }
| KRZYSZTOF CWALINA |
| I often hear people saying that interfaces specify contracts. I believe this is a dangerous myth. Interfaces, by themselves, do not specify much beyond the syntax required to use an object. The interface-as-contract myth causes people to do the wrong thing when trying to separate contracts from implementation, which is a great engineering practice. Interfaces separate syntax from implementation, which is not that useful, and the myth provides a false sense of doing the right engineering. In reality, the contract is semantics, and these can actually be nicely expressed with some implementation. |
COM exposed APIs exclusively through interfaces, but you should not assume that COM did this because interfaces were superior. COM did it because COM is an interface standard that was intended to be supported on many execution environments. CLR is an execution standard and it provides a great benefit for libraries that rely on portable implementation.
- DO favor defining classes over interfaces.
- Class-based APIs can be evolved with much greater ease than interface-based APIs because it is possible to add members to a class without breaking existing code.
| KRZYSZTOF CWALINA |
| Over the course of the three versions of the .NET Framework, I have talked about this guideline with quite a few developers on our team. Many of them, including those who initially disagreed with the guideline, have said that they regret having shipped some API as an interface. I have not heard of even one case in which somebody regretted that they shipped a class. |
| JEFFREY RICHTER |
I agree with Krzysztof in general. However, you do need to think about some other things. There are some special base classes, such as MarshalByRefObject. If your library type provides an abstract base class that isn't itself derived from MarshalByRefObject, then types that derive from your abstract base class cannot live in a different AppDomain. My recommendation to people is this: Define an interface first and then define an abstract base class that implements the interface. Use the interface to communicate to the object and let end-user developers decide for themselves whether they can just define their own type based on your abstract base class (for convenience) or define their own type based on whatever base class they desire and implement the interface (for flexibility). A good example of this is the IComponent interface and the Component base class that implements IComponent.
|
- DO use abstract classes instead of interfaces to decouple the contract from implementations.
- Abstract classes, if designed correctly, allow for the same degree of decoupling between contract and implementation.
| CHRIS ANDERSON |
| Here is one instance in which the design guideline, if followed too strictly, can paint you into a corner. Abstract types do version much better, and allow for future extensibility, but they also burn your one and only one base type. Interfaces are appropriate when you are really defining a contract between two objects that is invariant over time. Abstract base types are better for defining a common base for a family of types. When we did .NET there was somewhat of a backlash against the complexity and strictness of COM—interfaces, Guids, variants, and IDL, were all seen as bad things. I believe today that we have a more balanced view of this. All of these COMisms have their place, and in fact you can see interfaces coming back as a core concept in Indigo. |
| BRIAN PEPIN |
| One thing I've started doing is to actually bake as much contract into my abstract class as possible. For example, I might want to have four overloads to a method where each overload offers an increasingly complex set of parameters. The best way to do this is to provide a nonvirtual implementation of these methods on the abstract class, and have the implementations all route to a protected abstract method that provides the actual implementation. By doing this, you can write all the boring argument-checking logic once. Developers who want to implement your class will thank you. |
- DO define an interface if you need to provide a polymorphic hierarchy of value types.
- Value types cannot inherit from other types, but they can implement interfaces. For example,
IComparable,IFormattable, andIConvertibleare all interfaces so value types such asInt32,Int64, and other primitives can all be comparable, formattable, and convertible.
public struct Int32 : IComparable, IFormattable, IConvertible { ... } public struct Int64 : IComparable, IFormattable, IConvertible { ... }
- CONSIDER defining interfaces to acheive a similar effect to that of multiple inheritance.
| RICO MARIANI |
Good interface candidates often have this "mix in" feel to them. All sorts of objects can be IFormattable—it isn't restricted to a certain subtype. It's more like a type attribute. Other times we have interfaces that look more like they should be classes—IFormatProvider springs to mind. The fact that the best interface name ended in "er" speaks volumes.
|
| BRIAN PEPIN |
| Another sign that you've got a well-defined interface is that the interface does exactly one thing. If you have an interface that has a grab bag of functionality, that's a warning sign. You'll end up regretting it because in the next version of your product you'll want to add new functionality to this rich interface, but you can't. |
For example, System.IDisposable and System.ICloneable are both interfaces so types, like System.Drawing.Image, can be both disposable, cloneable, and still inherit from System.MarshalByRefObject class.
public class Image : MarshalByRefObject, IDisposable, ICloneable { ... }
| JEFFREY RICHTER |
When a class is derived from a base class, I say that the derived class has an IS-A relationship with the base. For example, a FileStream IS-A Stream. However, when a class implements an interface, I say that the implementing class has a CAN-DO relationship with the interface. For example, a FileStream CAN-DO disposing.
|
|

