Anatomy of a .NET Assembly – Type forwardsPublished 23 December 2011 2:02 pm
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:
[assembly: TypeForwardedTo(typeof(Lazy<>))] [assembly: TypeForwardedTo(typeof(LazyThreadSafetyMode))] [assembly: TypeForwardedTo(typeof(Action))] [assembly: TypeForwardedTo(typeof(Action<,>))] [assembly: TypeForwardedTo(typeof(Action<,,>))] [assembly: TypeForwardedTo(typeof(Action<,,,>))]This post has a look at what these are, and how they’re implemented.
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.
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
TypeNamespace: the exported type’s full name
Implementation: the module (or nested
ExportedType) the type can be found.
TypeDefId: the index within the
TypeDeftable 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
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
Actiontype is resolved using the normal C# type resolution rules to
[mscorlib]System.Action, and the compiler generates an entry in
Implementation: assembly reference to
TypeDefId: 0 (type forwards don’t use this field)
[System.Core]System.Actionare transparently redirected to
[mscorlib]System.Actionat runtime; assemblies using the forwarded type don’t have to be recompiled.
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.