CHAPTER 10

image

Versioning and Aliases

Software projects rarely exist as a single version of code that is never revised, unless the software never sees the light of day. In most cases, the software library writer is going to want to change some things, and the client will need to adapt to such changes.

Dealing with such issues is known as versioning , and it’s one of the harder things to do in software. One reason why it’s tough is that it requires a bit of planning and foresight; the areas that might change have to be determined, and the design must be modified to allow change.

Another reason why versioning is tough is that most execution environments don’t provide much help to the programmer. For example, in C++, compiled code has internal knowledge of the size and layout of all classes burned into it. With care, some revisions can be made to the class without forcing all users to recompile, but the restrictions are fairly severe. When compatibility is broken, all users need to recompile to use the new version. This may not be that bad, though installing a new version of a library may cause other applications that use an older version of the library to cease functioning.

While it is still possible to write code that has versioning problems, .NET makes versioning easier by deferring the physical layout of classes and members in memory until JIT compilation time. Rather than providing physical layout data, metadata is provided that allows a type to be laid out and accessed in a manner that makes sense for a particular process architecture.

image Note  Versioning is most important when assemblies are replaced without recompiling the source code that uses them, such as when a vendor ships a security update, for example.

A Versioning Example

The following code presents a simple versioning scenario and explains why C# has new and override keywords. The program uses a class named Control, which is provided by another company.

public class Control
{
}
public class MyControl: Control
{
}

During the implementation of MyControl, the virtual function Foo() is added.

public class Control
{
}
public class MyControl: Control
{
    public virtual void Foo() {}
}

This works well, until an upgrade notice arrives from the suppliers of the Control object. The new library includes a virtual Foo() function on the Control object.

public class Control
{

    // newly added virtual

public virtual void Foo() {}

}
public class MyControl: Control
{
    public virtual void Foo() {}
}

That Control uses Foo() as the name of the function is only a coincidence. In the C++ world, the compiler will assume that the version of Foo() in MyControl does what a virtual override of the Foo() in Control should do and will blindly call the version in MyControl. This is bad.

In the Java world, this will also happen, but things can be a fair bit worse; if the virtual function doesn’t have the same return type, the class loader will consider the Foo() in MyControl to be an invalid override of the Foo() in Control, and the class will fail to load at runtime.

In C# and the .NET Runtime, a function defined with virtual is always considered to be the root of a virtual dispatch. If a function is introduced into a base class that could be considered a base virtual function of an existing function, the runtime behavior is unchanged. When the class is next compiled, however, the compiler will generate a warning, requesting that the programmer specify their versioning intent. Returning to the example, to continue the default behavior of not considering the function to be an override, the new modifier is added in front of the function.

class Control
{
    public virtual void Foo() {}
}
class MyControl: Control
{
       // not an override
    public new virtual void Foo() {}
}

The presence of new will suppress the warning.

If, on the other hand, the derived version is an override of the function in the base class, the override modifier is used.

class Control
{
    public virtual void Foo() {}
}
class MyControl: Control
{
       // an override for Control.Foo()
    public override void Foo() {}
}

This tells the compiler that the function really is an override.

image Caution  About this time, you may be thinking, “I’ll just put new on all of my virtual functions, and then I’ll never have to deal with the situation again.” However, doing so reduces the value that the new annotation has to somebody reading the code. If new is used only when it is required, the reader can find the base class and understand what function isn’t being overridden. If new is used indiscriminately, the user will have to refer to the base class every time to see whether new has meaning.

Coding for Versioning

The C# language provides some assistance in writing code that versions well. Here are two examples:

  • Methods aren’t virtual by default. This helps limits the areas where versioning is constrained to those areas that were intended by the designer of the class and prevents “stray virtuals” that constrain future changes to the class.
  • The C# lookup rules are designed to aid in versioning. Adding a new function with a more specific overload (in other words, one that matches a parameter better) to a base class won’t prevent a less specific function in a derived class from being called,1 so a change to the base class won’t break existing behavior.

A language can do only so much. That’s why versioning is something to keep in mind when doing class design. One specific area that has some versioning trade-offs is the choice between classes and interfaces.

The choice between class and interface should be fairly straightforward. Classes are appropriate only for “is-a” relationships (where the derived class is really an instance of the base class), and interfaces are appropriate for all others. If an interface is chosen, however, good design becomes more important because interfaces simply don’t version; when a class implements an interface, it needs to implement the interface exactly, and adding another method at a later time will mean that classes that thought they implemented the interface no longer do.

Type Aliases

Sometimes you end up wanting to use two identically named classes in the same program. Consider the following two classes:

namespace MyCompany.HumanResources.Application.DataModel
{
    class Employee
    {
       public string Name { get; set; }
    }
}
namespace MyCompany.Computer.Network.Model.Classes
{
    class Employee
    {
       public string Name { get; set; }
    }
}

I need to write a method that will take an Employee instance of the first class’s type to an Employee instance of the second class’s type. Here’s the first attempt:

public MyCompany.Computer.Network.Model.Classes.Employee
    CopyEmployeeData(
       MyCompany.HumanResources.Application.DataModel.Employee hrEmployee)
{
    MyCompany.Computer.Network.Model.Classes.Employee networkEmployee =
         new MyCompany.Computer.Network.Model.Classes.Employee();

    networkEmployee.Name = hrEmployee.Name;

    return networkEmployee;
}

That’s really bad. What I need is a way to give different names so the compiler can tell which Employee I mean in a specific situation.

using NetworkEmployee = MyCompany.Computer.Network.Model.Classes.Employee;
using HREmployee = MyCompany.HumanResources.Application.DataModel.Employee;

public NetworkEmployee CopyEmployeeData(HREmployee hrEmployee)
{
    NetworkEmployee networkEmployee = new NetworkEmployee();
    networkEmployee.Name = hrEmployee.Name;

    return networkEmployee;
}

That’s quite a bit nicer.

External Assembly Aliases

Sometimes it’s worse than the previous situation; not only are the two classes named the same, but they are in the same namespace. If two groups ship the same class, the source file may be the same, but if they are in separate assemblies, they are considered different types by the compiler and runtime.2

Type aliases don’t help here, since the full names (in other words, namespace + class name) of the two classes are identical. What is needed is a way to give them different names at the assembly level. This can be done on the command line by using the alias form of the /reference qualifier or by setting the alias property on the reference in Visual Studio, as shown in Figure 10-1.

csc /reference:HR = HRDataModel.dll /reference:Network = NetworkDataModel ...

9781430245933_Fig10-01.jpg

Figure 10-1. Defining aliases in the Visual Studio project properties

To use those aliases within code, first they must be defined.

extern alias HR;
extern alias Network;
using NetworkEmployee = Network::MyCompany.DataModel.Employee;
using HREmployee = HR::MyCompany.DataModel.Employee;

Then they can be used in code as before.

1 See Chapter 6 for just such an example.

2 In other words, types have assembly identity.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.224.70.238