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:
1 2 3 4 5 6 |
[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.
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 nameImplementation
: the module (or nestedExportedType
) the type can be found.TypeDefId
: the index within theTypeDef
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
1 |
[assembly: TypeForwardedTo(typeof(Action))] |
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 tomscorlib
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.
Load comments