March 21, 2019
Hot Topics:

Type Design Guidelines for Reusable .NET Libraries

  • December 23, 2005
  • By Krzysztof Cwalina and Brad Abrams
  • Send Email »
  • More Articles »

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();
         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 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 : IList {

         public void Add(T item){
         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 interfacebased 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, and IConvertible are all interfaces so value types such as Int32, 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 achieve 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.MarshalByRef- Object 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.

Abstract Class Design

DO NOT define public or protected-internal constructors in abstract types.

Constructors should be public only if users will need to create instances of the type. Because you cannot create instances of an abstract type, an abstract type with a public constructor is incorrectly designed and misleading to the users.(2)

     // bad design
     public abstract class Claim {
         public Claim (int number) {
     // good design
     public abstract class Claim {
         // incorrect Design
         protected Claim (int number) {

DO define a protected or an internal constructor on abstract classes.

A protected constructor is more common and simply allows the base class to do its own initialization when subtypes are created.

     public abstract class Claim {
         protected Claim() {

An internal constructor can be used to limit concrete implementations of the abstract class to the assembly defining the class.

     public abstract class Claim {
         internal Claim() {

BRAD ABRAMS Many languages (such as C#) will insert a protected constructor if you do not. It is a good practice to define the constructor explicitly in the source so that it can be more easily documented and maintained over time.

DO provide at least one concrete type that inherits from each abstract class that you ship.

This helps to validate the design of the abstract class. For example, System. IO.FileStream is an implementation of the System.IO.Stream abstract class.

BRAD ABRAMS I have seen countless examples of a "well-designed" base class or interface where the designers spent hundreds of hours debating and tweaking the design only to have it blown out of the water when the first real-world client came to use the design. Far too often these realworld clients come too late in the product cycle to allow time for the correct fix. Forcing yourself to provide at least one concrete implementation reduces the chances of finding a new problem late in the product cycle.

Static Class Design

A static class is defined as a class that contains only static members (of course besides the instance members inherited from System.Object and possibly a private constructor). Some languages provide built-in support for static classes. In C# 2.0, when a class is declared to be static, it is sealed, abstract, and no instance members can be overridden or declared.

     public static class File {

If your language does not have built-in support for static classes, you can declare such classes manually as in the following C++ example:

     public class File abstract sealed {

Static classes are a compromise between pure object-oriented design and simplicity. They are commonly used to provide shortcuts to other operations (such as System.IO.File), or functionality for which a full objectoriented wrapper is unwarranted (such as System.Environment).

DO use static classes sparingly.

Static classes should be used only as supporting classes for the objectoriented core of the framework.

DO NOT treat static classes as a miscellaneous bucket.

There should be a clear charter for the class.

DO NOT declare or override instance members in static classes.

DO declare static classes as sealed, abstract, and add a private instance constructor, if your programming language does not have built-in support for static classes.

BRIAN GRUNKEMEYER In the .NET Framework 1.0, I wrote the code for the System.Environment class, which is an excellent example of a static class. I messed up and accidentally added a property to this class that wasn't static (HasShutdownStarted). Because it was an instance method on a class that could never be instantiated, no one could call it. We didn't discover the problem early enough in the product cycle to fix it before releasing version 1.0. If I were inventing a new language, I would explicitly add the concept of a static class into the language to help people avoid falling into this trap. And in fact, in C# 2.0 did add support for static classes!

JEFFREY RICHTER Make sure that you do not attempt to define a static structure, because structures (value types) can always be instantiated no matter what. Only classes can be static.

2. This also applies to protected-internal constructors.

Page 3 of 6

Comment and Contribute


(Maximum characters: 1200). You have characters left.



Enterprise Development Update

Don't miss an article. Subscribe to our newsletter below.

Thanks for your registration, follow us on our social networks to keep up-to-date