Anatomy of a .NET Assembly – Type forwards

If you’ve ever had a poke around System.dll or System.Core.dll in Reflector, you may have noticed TypeForwardedToAttributes applied to the assembly:

This post has a look at what these are, and how they’re implemented.

Type forwards

TypeForwardedToAttribute is part of a feature introduced in .NET 2 – Type forwarding. As the documentation says, this is a feature that allows a type to be moved to a different assembly without having to recompile assemblies using that type. This is used extensively by the class libraries when types were moved from System.Core.dll to mscorlib.dll between .NET 3.5 and 4, allowing assemblies compiled against .NET 2 and 3.5 to run on the .NET 4 framework as-is, without having to be recompiled.

However, if you think about it, this is much more than a simple attribute; this is a core change to the CLR type resolution mechanism. Every type that is resolved in an assembly first has to check for the existance of a type forward indicating the type has been moved somewhere else.

This isn’t something that can easily be represented in a simple attribute; TypeForwardedToAttribute is actually an example of a pseudo custom attribute.

ExportedType

First, a bit of background. The ExportedType metadata table was originally designed to be used in multi-module assemblies as part of the ‘public contract’ of an assembly; the manifest module for the assembly would contain an entry in ExportedType for every public type defined in other modules, comprising

  • TypeName & TypeNamespace: the exported type’s full name
  • Implementation: the module (or nested ExportedType) the type can be found.
  • TypeDefId: the index within the TypeDef table in the module the type is located.

This allows any tool referencing the multi-module assembly to only need to look in the manifest module to find every public type defined in the assembly and where it can be found; it doesn’t have to scan every module comprising the assembly.

When .NET 2 came along, ExportedType was repurposed to store type forwards as well. If Implementation is a reference to an assembly rather than a module, then that assembly is the new location of the type named by TypeName and TypeNamespace (type forwards don’t allow you to change the type name or namespace).

So, when the C# compiler sees a type forward in System.Core.dll specified by the attribute

the Action type is resolved using the normal C# type resolution rules to [mscorlib]System.Action, and the compiler generates an entry in ExportedType like so:

  • TypeName: Action
  • TypeNamespace: System
  • Implementation: assembly reference to mscorlib
  • TypeDefId: 0 (type forwards don’t use this field)

this entry is then followed by the core CLR type resolution mechanism so that any references to [System.Core]System.Action are transparently redirected to [mscorlib]System.Action at runtime; assemblies using the forwarded type don’t have to be recompiled.

TypeForwardedFrom

Finally, TypeForwardedFromAttribute is the counterpart to TypeForwardedToAttribute; it specifies the assembly a type has been forwarded from (using an assembly name string, rather than a direct metadata assembly reference). However, unlike TypeForwardedTo, this is a normal attribute, has no effect on CLR type resolution, and exists primarily for bookkeeping purposes.

Type forwards may seem like a niche feature aimed at library writers, but they are invaluable in the right circumstances. In the BCL, they allow programs compiled against the .NET 2 and 3.5 frameworks to run on the .NET 4 framework without needing to be recompiled.