Versioning in C#

 

Versioning is an after-thought in most languages, but not in C#. “Versioning” actually has two different meanings. A new version of a component is “source compatible” with a previous version if code that depends on the previous version can, when recompiled, work with the new version. In contrast, for a “binary compatible” component, a program that depended on the old version can, without recompilation, work with the new version.

 

Most languages do not support binary compatibility at all, and many do little to facilitate source compatibility. In fact, some languages contain flaws that make it impossible, in general, to evolve a class over time without breaking some client code.

 

As an example, consider the situation of a base class author who ships a class named Base. In this first version, Base contains no F method. A component named Derived derives from Base, and introduces an F. This Derived class, along with the class Base that it depends on, is released to customers, who deploy to numerous clients and servers.

 

// Company

namespace Company

{

    class Employee // version 1

    { }

}

 

// Employee Manager

namespace B

{

    class Manager : Company.Employee

    {

        public virtual void GetId()

        {

            System.Console.WriteLine("Manager.GetId");

        }

    }

}

 

So far, so good. But now the versioning trouble begins. The author of Base produces a new version, and adds its own F method.

 

// Company

namespace Company

{

    class Manager // version 2

    {

        public virtual void GetId()

        { // added in version 2

            System.Console.WriteLine("Manager.GetId");

        }

    }

}

 

This new version of Base should be both source and binary compatible with the initial version. (If it weren’t possible to simply add a method then a base class could never evolve.) Unfortunately, the new F in Base makes the meaning of Derived’s F is unclear. Did Derived mean to override Base’s F? This seems unlikely, since when Derived was compiled, Base did not even have an F! Further, if Derived’s F does override Base’s F, then does Derived’s F adhere to the contract specified by Base? This seems even more unlikely, since it is pretty darn difficult for Derived’s F to adhere to a contract that didn’t exist when it was written. For example, the contract of Base’s F might require that overrides of it always call the base. Derived’s F could not possibly adhere to such a contract since it cannot call a method that does not yet exist.