Saturday, September 30, 2006 7:19 PM bart

.NET 2.0 type forwarding - System.Runtime.CompilerServices.TypeForwardedToAttribute explained

Introduction

Imagine the following situation: you've created a large software library with lots of types defined in it and lots of people are using it already. However, the library has become so bloated due to its size, it becomes more and more difficult to maintain it. So, you want to split the existing library into multiple assemblies, but without breaking any existing consumer applications.

You recognize this situation? Then the TypeForwardedToAttribute will be something for you. I'll explain why.

A simple demo scenario

Put yourself in the middle of a 1.0 release of some simple library (company A). Here it is:

public class Bar
{
   public static void
Do()
   {
      System.
Console.WriteLine("[lib]Bar::Do"
);
   }
}

public class
Foo
{
}

Just imagine it's a lot bigger with real functionality and compile it to a dll:

>csc /t:library lib.cs
Microsoft (R) Visual C# 2005 Compiler version 8.00.50727.112
for Microsoft (R) Windows (R) 2005 Framework version 2.0.50727
Copyright (C) Microsoft Corporation 2001-2005. All rights reserved.

Right your library has shipped. Let's transfer ourselves in the mindset of a library consumer (company B) and write the following:

class Demo
{
   public static void
Main()
   {
      Bar.Do();
   }
}

Compiling and running it shouldn't be surprising (or how Company B is happy):

>csc /r:lib.dll demo.cs
Microsoft (R) Visual C# 2005 Compiler version 8.00.50727.112
for Microsoft (R) Windows (R) 2005 Framework version 2.0.50727
Copyright (C) Microsoft Corporation 2001-2005. All rights reserved.

>demo
[lib]Bar::Do

Time for changes. Company A dislikes its bloated lib library (or whatever the reason might be) and wants to split the library into multiple separate assemblies. So, we'll create a newlib.cs file containing the Bar class:

public class Bar
{
   public static void
Do()
   {
      System.
Console.WriteLine("[lib]Bar::Do"
);
   }
}

This one gets compiled into newlib.dll, as follows:

>csc /t:library newlib.cs
Microsoft (R) Visual C# 2005 Compiler version 8.00.50727.112
for Microsoft (R) Windows (R) 2005 Framework version 2.0.50727
Copyright (C) Microsoft Corporation 2001-2005. All rights reserved.

And lib.cs is stripped down to:

public class Foo
{
}

Assume you compile this library now:

>csc /t:library lib.cs
Microsoft (R) Visual C# 2005 Compiler version 8.00.50727.112
for Microsoft (R) Windows (R) 2005 Framework version 2.0.50727
Copyright (C) Microsoft Corporation 2001-2005. All rights reserved.

But what about company B? Xcopying both assemblies lib.dll and newlib.dll and trying to execute demo.exe will be a nasty experience:

>demo

Unhandled Exception: System.TypeLoadException: Could not load type 'Bar' from as
sembly 'lib, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'.
   at Demo.Main()

Okay, we could recompile demo.exe or you might wonder why lib.dll (changed but with the same version number as before) and newlib.dll got copied to company B after all. Perfectly valid questions to ask yourself, but just assume you want to fix this issue without recompiling stuff at the side of company B.

Back to company A for a better solution. Just add one single line to lib.cs containing our original though stripped down library:

[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(Bar))]

public class Foo
{
}

Now compile, but reference the newlib.dll assembly in where the Bar type lives right now:

>csc /r:newlib.dll /t:library lib.cs
Microsoft (R) Visual C# 2005 Compiler version 8.00.50727.112
for Microsoft (R) Windows (R) 2005 Framework version 2.0.50727
Copyright (C) Microsoft Corporation 2001-2005. All rights reserved.

Now copy lib.dll and newlib.dll to company B. Guess what? demo.exe just works fine

>demo
[lib]Bar::Do

Recall nothing has changed to demo.exe, so it still contains this metadata token:

.assembly extern lib
{
   .ver 0:0:0:0
}

and a call to the Bar::Do method defined in lib:

IL_0001: call void [lib]Bar::Do()

Magic happens in lib.dll however, where you'll find this piece of metadata in the manifest:

.class extern forwarder Bar
{
   .assembly extern newlib
}

So, when the type loader of the CLR kicks in and searches for type Bar in the lib.dll assembly, it receives help from the metadata telling that the Bar type has been relocated to another assembly "newlib":

.assembly extern newlib
{
   .ver 0:0:0:0
}

Conclusion

As usual you should consider carefully whether this feature can be helpful to you. It might be much better to ship another version of your library, split up in multiple assemblies and have your consumers use that one if they want or need to do so. "It works fine, so keep your hands off." might be a good attitude (I think it is).

Btw, there are some caveats too: over-using this feature carelessly could cause cycles to be created. The C# compiler knows about this: CS0731. And what about this warning? The classes in System.Runtime.CompilerServices are for compiler writers use only. Anyway, now you know the meaning of this attribute when you encounter it somewhere somehow.

To forward or not to forward? Shakespeare won't have considered this sentence in his oeuvre (I guess), but you should. Have fun!

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Filed under: ,

Comments

# re: .NET 2.0 type forwarding - System.Runtime.CompilerServices.TypeForwardedToAttribute explained

Monday, October 23, 2006 5:59 PM by hanu

Your explanation is much better than one of the .net books i bought. thank you hanu