Type forwarding in the common language runtime

Type forwarding allows you to move a type to another assembly without having to recompile applications that use the original assembly.

For example, suppose an application uses the Example class in an assembly named Utility.dll. The developers of Utility.dll might decide to refactor the assembly, and in the process they might move the Example class to another assembly. If the old version of Utility.dll is replaced by the new version of Utility.dll and its companion assembly, the application that uses the Example class fails because it cannot locate the Example class in the new version of Utility.dll.

The developers of Utility.dll can avoid this by forwarding requests for the Example class, using the TypeForwardedToAttribute attribute. If the attribute has been applied to the new version of Utility.dll, requests for the Example class are forwarded to the assembly that now contains the class. The existing application continues to function normally, without recompilation.

Forward a type

There are four steps to forwarding a type:

  1. Move the source code for the type from the original assembly to the destination assembly.

  2. In the assembly where the type used to be located, add a TypeForwardedToAttribute for the type that was moved. The following code shows the attribute for a type named Example that was moved.

     [assembly:TypeForwardedToAttribute(Example::typeid)]
    
     [assembly:TypeForwardedToAttribute(typeof(Example))]
    
  3. Compile the assembly that now contains the type.

  4. Recompile the assembly where the type used to be located, with a reference to the assembly that now contains the type. For example, if you are compiling a C# file from the command line, use the References (C# compiler options) option to specify the assembly that contains the type. In C++, use the #using directive in the source file to specify the assembly that contains the type.

C# type forwarding example

Continuing from the contrived example description above, imagine you're developing the Utility.dll, and you have an Example class. The Utility.csproj is a basic class library:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsing>true</ImplicitUsing>
  </PropertyGroup>

</Project>

The Example class provides a few properties and overrides Object.ToString:

using System;

namespace Common.Objects;

public class Example
{
    public string Message { get; init; } = "Hi friends!";

    public Guid Id { get; init; } = Guid.NewGuid();

    public DateOnly Date { get; init; } = DateOnly.FromDateTime(DateTime.Today);

    public sealed override string ToString() =>
        $"[{Id} - {Date}]: {Message}";
}

Now, imagine that there is a consuming project and it's represented in the Consumer assembly. This consuming project references the Utility assembly. As an example, it instantiates the Example object and writes it to the console in its Program.cs file:

using System;
using Common.Objects;

Example example = new();

Console.WriteLine(example);

When the consuming app runs, it will output the state of the Example object. At this point, there is no type forwarding as the Consuming.csproj references the Utility.csproj. However, the developer's of the Utility assembly decide to remove the Example object as part of a refactoring. This type is moved to a newly created Common.csproj.

By removing this type from the Utility assembly, the developers are introducing a breaking change. All consuming projects will break when they update to the latest Utility assembly.

Instead of requiring the consuming projects to add a new reference to the Common assembly, you can forward the type. Since this type was removed from the Utility assembly, you'll need to have the Utility.csproj reference the Common.csproj:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsing>true</ImplicitUsing>
  </PropertyGroup>

  <ItemGroup>
    <ProjectReference Include="..\Common\Common.csproj" />
  </ItemGroup>

</Project>

The preceding C# project now references the newly created Common assembly. This could be either a PackageReference or a ProjectReference. The Utility assembly needs to provide the type forwarding information. By convention type forward declarations are usually encapsulated in a single file named TypeForwarders, consider the following TypeForwarders.cs C# file in the Utility assembly:

using System.Runtime.CompilerServices;
using Common.Objects;

[assembly:TypeForwardedTo(typeof(Example))]

The Utility assembly references the Common assembly, and it forwards the Example type. If you're to compile the Utility assembly with the type forwarding declarations and drop the Utility.dll into the Consuming bin, the consuming app will work without being compiled.

See also