Hot off the presses.. I just added this to the working draft fo the Design Guidelines document internally.  As always, comments welcome.



4.1.2       Subclassing Usage Guidelines

One of the advantages of an object oriented environment is that developers can extend and customize the system in ways we did not expect.  This is both the power and danger of extensible design.

            Do minimize number and complexity of virtual members in your APIs.  Virtual members defeat some performance optimizations the runtime provides and could “box-in” your design.

            Do not use virtual members unless you have specifically designed for specialization:

            You have a concrete extensibility scenario in mind.

            You have written a quality sample demonstrating the extensibility scenario.  Preferably the sample should be done by a “3rd party” to independently validate the design.

            You have clearly documented contracts of all virtual members.

            Do think twice before you virtualize members:

            Modules that use references to base types must be able to use references to derived types without knowing the difference.  See Liskov’s Substitution Principle

            As you version your component, you must be sure that the virtual members are called in the same order and frequency.

            As you version virtual members you must not change the range of the outputs or the inputs to the member.  For non-virtual members widening inputs is allowed.

Rationale: Clients of the base class may start calling the virtual member with increased input or can assume the output is always true. But then all the subclasses need to be modified to support the new contract.


Annotation  - The peril. If you ship types with virtual members you are promising to forever abide by subtle and complex observable behaviors and subclass interactions.

I think API designers underestimate their peril.  For example, we found that ArrayList item enumeration calls several virtual methods per each MoveNext and Current. Fixing those performance problems could (but probably doesn't) break user-defined implementations of virtual members on the ArrayList class that are dependent upon virtual method call order/frequency. 


Annotation  - To understand the level of complexity that can be introduced by general purpose virtual methods you need only remember the sort of pain that developers (both creators and users) of the Win32 API feel with regard to generation and handling of windows messages.  WM_WHATEVER is nothing more than custom virtual dispatch mechanism with all of the usual pitfalls.  There are messages of both imperative (commands) and informative (events) variety and there are many levels of interaction which enrich the message stream.  The net of all this is that even when considering relatively harmless looking messages there are tough decisions to make such as "will I break the universe if I send (another) WM_HITTEST before the WM_RESIZE when resizing a normal frame?"   I assure it doesn't say anywhere that you get any WM_HITTEST messages at all, much less exactly 1 and it certainly doesn't say where they come relative to WM_RESIZE but chances are you'll break something if you were to change it.   Or worse yet, "Is anyone originating their own WM_HITTEST to do some exotic mouse tests in response to WM_RESIZE and if I change WM_HITTEST will I now cause re-entrancy problems that didn't use to be there?"


FxCop Rule (draft): Flag non-sealed public types that introduce virtual members with the message: “Virtual members represent points of specialization in your type.  Great care should be taken in exposing virtual members, please see the design guidelines document on this topic and fully consider the ramifications.”


            Do make only the most complex overload virtual to minimize the number of virtual calls needed to interact with your class and reduce the risk of subclasses introducing subtle bugs.
public class Foo {
   private const string defaultForA = "a default";
   private const int defaultForB = 42;
   public void Bar(){
      Bar(defaultForA, defaultForB);
   public void Bar (string a){
      Bar(a, defaultForB);
   public virtual void Bar (string a, int b){
      // core implementation here

            Consider designing classes with inheritance scenarios in mind and appropriate virtual members. 
Doing this correctly requires a careful specification outlining the exact contract of all the virtual members.  It is strongly suggested that you create at least one subclass ideally in the shipping product.   Although this is the ideal, it is somewhat costly.

            Do default to implementing unsealed classes with no virtuals if you do not have the resources for extensibility testing and design of the APIs as described above or if you do not see a need for extensibility through overriding.  Even this plan requires minimal testing to be sure inadvertent virtual members were not added.

            Do default to implementing unsealed class with no protected members if you have not designed the class for extensibility. 
Rationale: Although the risks are lower than with virtual members the lack of extensibility is clearer if there are no protected members.

            Do seal a class if:

            It is a static class (TODO, add section reference)

            It is an attribute that requires very fast runtime look up. (TODO, add section reference)

            You don’t have the resources to test that inadvertent virtual members were added.  Notice this cost is very small as FXCop can flag virtual members. 

As an example consider System.Drawing.Graphics class. This class is sealed and internally implemented in unmanaged code such that developers can not extend its functionality in any meaningful way.  Every extensibility scenario we were aware of was to add new functionality something that actually would not work because of their implementation details.  So if we had it unsealed we would have just delayed the developer’s frustration. 


As another example consider the System.WeakReference class.  We have no defined scenarios for subclassing this type but there are also no implementation details that cause subclassing to be impossible.  We therefore made sure there were no virtual members and made the class unsealed. 


            Do not declare protected or virtual members on sealed types

By definition, sealed types cannot be inherited from. This means that protected members on sealed types cannot be called, and virtual methods on sealed types cannot be overridden.

            Do fully document the contact for any virtual members.  It is especially important to specify what the guarantees that are NOT given about the order in which virtuals are called relative to each other, relative to the raising of events, and relative to changes in observable state (including properties).   

For platform types you are required to guarantee all observable behavior, while we allow libraries to specifically list orderings in which some observable behavior is NOT guaranteed.
Rationale: With Platforms types there is no testing when they are upgraded.  So customers who extend them without thoroughly understanding the contract (and you can bet that's most of the extenders) will find their code broken when the relative order changes even though it wasn't guaranteed. 

It may be useful to follow a standard template for all virtual member reference pages as it is useful to structure your thinking about these contracts.  As an example consider this example from the Object.Equals() method from the ECMA\ISO CLI standard.

The following statements must be true for all implementations of the System.Object.Equals method. In the list, x, y, and z represent object references that are not null.

•     x.Equals(x) returns true, except in cases that involve floating-point types. See IEC 60559:1989, Binary Floating-point Arithmetic for Microprocessor Systems.

•     x.Equals(y) returns the same value as y.Equals(x).

•     x.Equals(y) returns true if both x and y are NaN.

•     (x.Equals(y) && y.Equals(z)) returns true if and only if x.Equals(z) returns true.

•     Successive calls to x.Equals(y) return the same value as long as the objects referenced by x and y are not modified.

•     x.Equals(null) returns false.

See System.Object.GetHashCode for additional required behaviors pertaining to the System.Object.Equals method.

Implementations of System.Object.Equals must not throw exceptions.

For some kinds of objects, it is desirable to have System.Object.Equals test for value equality instead of referential equality. Such implementations of System.Object.Equals return true if the two objects have the same "value", even if they are not the same instance. The type's implementer decides what constitutes an object's "value", but it is typically some or all the data stored in the instance variables of the object. For example, the value of a System.String is based on the characters of the string; the System.Object.Equals method of the System.String class returns true for any two string instances that contain exactly the same characters in the same order.