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:

  1. Physical Relocation: You move the actual source code of the type to the destination assembly.
  2. Metadata Forwarding: In the source (original) assembly, you declare a type forwarder using the TypeForwardedToAttribute.
  3. Metadata Table Entry: When compiling the source assembly, the C# compiler adds an entry to the ExportedType metadata table indicating that the type now resides in the target assembly.
  4. 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 public types. 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 mscorlib to System.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.