Type Forwarding Across Assemblies in .NET C
Type forwarding is a .NET runtime mechanism that allows you to move a
type from one assembly to another without breaking existing client
applications compiled against the original assembly. By utilizing the
[TypeForwardedTo] attribute, developers can refactor class
libraries, split monolithic assemblies into modular packages, and
maintain binary backward compatibility without requiring downstream
consumers to recompile their code.
The Problem: Breaking Changes in Assembly Refactoring
In .NET, the identity of a type is strictly tied to its assembly-qualified name, which includes the type's full name, the assembly name, version, culture, and public key token.
If you have a class OrderService in
LegacyApp.Core.dll and move its code to a new assembly
called LegacyApp.Orders.dll, any existing application
compiled against LegacyApp.Core.dll will fail at runtime
with a TypeLoadException. Even though the namespace and
type name remain identical, the runtime looks for the type strictly
inside the assembly referenced at compile time.
How Type Forwarding Works
Type forwarding addresses this issue at the Common Language Runtime (CLR) metadata level:
- Physical Relocation: You move the actual source code of the type to the destination assembly.
- Metadata Forwarding: In the source (original)
assembly, you declare a type forwarder using the
TypeForwardedToAttribute. - Metadata Table Entry: When compiling the source
assembly, the C# compiler adds an entry to the
ExportedTypemetadata table indicating that the type now resides in the target assembly. - Runtime Resolution: When an un-recompiled client requests the type from the old assembly, the CLR checks the metadata, discovers the forwarder, loads the target assembly, and resolves the type transparently.
Step-by-Step Implementation
Step 1: Move the Type to the New Assembly
Move the class definition into the new project (e.g.,
NewAssembly). Ensure the namespace and class signature
remain unchanged.
// Inside NewAssembly.dll
namespace Company.Services
{
public class OrderProcessor
{
public void Process(int orderId)
{
// Business logic
}
}
}Step 2: Add a Reference to the New Assembly
The original assembly (OldAssembly) must reference the
new assembly (NewAssembly) so it can access the relocated
type for forwarding.
Step 3: Declare the TypeForwardedTo Attribute
In the original assembly, remove the original class file entirely.
Then, apply the TypeForwardedTo attribute at the assembly
level, typically in an AssemblyInfo.cs file or any other
top-level C# file.
// Inside OldAssembly.dll
using System.Runtime.CompilerServices;
using Company.Services;
[assembly: TypeForwardedTo(typeof(OrderProcessor))]Important Considerations
- Public Types Only: Type forwarding only applies to
publictypes. Internal or private types cannot be forwarded because they are not part of the public assembly contract. - No Circular References: The original assembly must reference the new assembly to declare the attribute. The new assembly must not reference the original assembly, as circular dependencies between projects are prohibited.
- Runtime Version: Type forwarding is supported
natively across .NET Framework, .NET Core, and modern .NET (.NET 5+). It
is the primary mechanism Microsoft uses to unify runtime libraries (such
as moving types from
mscorlibtoSystem.Runtime). - Source vs. Binary Compatibility: While type forwarding guarantees binary backward compatibility, recompiling the client project will directly bind it to the new assembly, bypassing the forwarder entirely.