C Sharp

Versioning Assemblies

An assembly's manifest contains a version number as well as a list of all referenced assemblies with their associated version information. As you'll soon see, version numbers are divided into four segments and take the following form: -

<major><minor><build><revision>

The way this works is that at run time .NET uses this version number information to decide which version of a particular assembly to use with an application. As you'll soon see, the default behavior-called the versioning policy-is that once an application is installed, .NET will automatically use the most recent version of that application's referenced assemblies if the versions match at the major and minor levels. You can change this default behavior through the use of configuration files.

Versioning pertains only to shared assemblies-private assemblies need not apply-and is probably the single most important factor in deciding to create and share assemblies. Therefore, let's look at some example code to illustrate how all this works and how to work with assembly versioning.

The example that I'm going to use here is a simpler version of the versioning example that ships with the .NET SDK, and it does not include the Windows Forms material because I want to focus on versioning and what the runtime does to enforce it.

I have two executables representing two accounting packages called Personal and Business. Both of these applications use a common shared assembly named Account. The only functionality that the Account class has is the ability to announce its version such that we can be sure our applications are using the intended version of the Account class. To that end, the demo will include multiple versions of the Account class so that you can see for yourself how the versioning works with a default version policy and how XML is used to create an association between an application and a specific version of an assembly.

To get started, create a folder called Accounting. In this folder, create a key pair that is to be used by all versions of the Account class. To do this, type the following at the command line in the Accounting folder: -

sn /k account.key

Once that key pair has been created, create a folder within the Accounting folder called Personal. In that Personal folder, create a file called Personal.cs that looks like the following: -

// Accounting\Personal\Personal.cs
using System;
class PersonalAccounting
{
    public static void Main()
    {
        Console.WriteLine
            ("PersonalAccounting calling Account.PrintVersion");
        Account.PrintVersion();
    }
}

Within that same Personal folder, create a new folder called Account1000. This folder will house the first version of the demo's Account class. Once you've done that, create the following file (Account.cs) in the Account1000 folder: -

// Accounting\Personal\Account1000\Account.cs
using System;
using System.Reflection;
[assembly:AssemblyKeyFile("..\\..\\Account.key")]
[assembly:AssemblyVersion("1.0.0.0")]
public class Account
{
    public static void PrintVersion()
    {
        Console.WriteLine
            ("This is version 1.0.0.0 of the Account class");
    }
}

As you can see, I'm using the AssemblyeKeyFile and AssemblyVersion attributes to point the C# compiler to the key pair created earlier and to explicitly state the version of the Account class. Now build the Account DLL as follows: -

csc /t:library account.cs

Once the Account class has been created, it needs to be added to the global assembly cache: -

gacutil -i Account.dll

If you want, you can verify that the Account assembly is indeed in the assembly cache. Now go to the Personal folder and build the application like this: -

csc Personal.cs /r:Account1000\Account.dll

Finally, running the application will result in the following output: -

PersonalAccounting calling Account.PrintVersion
This is version 1.0.0.0 of the Account class

So far, we've done nothing new here. However, let's see what happens when another application that uses a newer version of the Account class is installed.

Create a new folder called Business within the Accounting folder. In the Business folder, create a folder called Account1001 to represent a new version of the Account class. That class will be housed in a file called Account.cs,and it will look almost identical to the previous version.

// Accounting\Business\Account1001\Account.cs
using System;
using System.Reflection;
[assembly:AssemblyKeyFile("..\\..\\Account.key")]
[assembly:AssemblyVersion("1.0.0.1")]
public class Account
{
    public static void PrintVersion()
    {
        Console.WriteLine
            ("This is version 1.0.0.1 of the Account class");
    }
}

As before, build this version of the Account class by using the following commands: -

csc /t:library Account.cs
gacutil -i Account.dll

At this point, you should see two versions of the Account class in the global assembly cache. Now create the Business.cs file (in the Accounting\Business folder) as follows: -

// Accounting\Business\Business.cs
using System;
class PersonalAccounting
{
    public static void Main()
    {
        Console.WriteLine
            ("BusinessAccounting calling Account.PrintVersion");
        Account.PrintVersion();
    }
}

Build the Businessapplication by using the following command: -

csc business.cs /r:Account1001\Account.dll

Running the application will produce the following, which confirms the fact that the Businessapplication is using version 1.0.0.1 of the Account assembly: -

BusinessAccounting calling Account.PrintVersion
This is version 1.0.0.1 of the Account class

However, now run the Personal application again and see what happens! -

PersonalAccounting calling Account.PrintVersion
This is version 1.0.0.1 of the Account class

Both the Personaland Businessapplications are using the last version of the Account assembly! Why? It has to do with what is called a Quick Fix Engineering (QFE) update and the .NET default versioning policy.