公共语言运行时中的类型转发

使用类型转发可以将类型移到另一个程序集,而不必重新编译使用原始程序集的应用程序。

例如,假设应用程序使用名为 Utility.dll 的程序集中的 Example 类。 Utility.dll 的开发人员可能决定重构该程序集,并且在重构过程中可能将 Example 类移到另一个程序集。 如果旧版本的 Utility.dll 由新版本的 Utility.dll 及其配套程序集取代,则使用 Example 类的应用程序会失败,因其无法在新版本的 Utility.dll 中找到 Example 类。

Utility.dll 开发人员可使用 TypeForwardedToAttribute 属性转发 Example 类的请求,从而避免这种情况。 如果已向新版本的 Utility.dll 应用了该属性,则对 Example 类的请求会转发到该类目前所属的程序集。 现有应用程序将继续正常运行,无需重新编译。

转发类型

转发类型有四个步骤:

  1. 将类型的源代码从原始程序集移到目标程序集。

  2. 在类型曾存于的程序集中,为已移动的类型添加 TypeForwardedToAttribute。 下面的代码显示名为 Example 的被移动类型的属性。

     [assembly:TypeForwardedToAttribute(Example::typeid)]
    
     [assembly:TypeForwardedToAttribute(typeof(Example))]
    
  3. 编译该类型目前所属的程序集。

  4. 重新编译该类型原来所属的程序集,其中带有对该类型目前所属的程序集的引用。 例如,如果从命令行编译一个 C# 文件,则使用 References(C# 编译器选项)选项指定包含类型的程序集。 在 C++ 中,在源文件中使用 #using 指令指定包含类型的程序集。

C# 类型转发示例

继续上面的人为示例描述,假设你要开发 Utility.dll,并且你有一个 Example 类。 Utility.csproj 是一个基本类库:

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

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

</Project>

Example 类提供一些属性并替代 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}";
}

现在,假设有一个进行中的项目,该项目在 Consumer 程序集中表示。 此使用项目引用 Utility 程序集。 例如,它实例化 Example 对象并将其写入控制台的 Program.cs 文件中:

using System;
using Common.Objects;

Example example = new();

Console.WriteLine(example);

当进行中的应用运行时,它会输出 Example 对象的状态。 此时,没有类型转发,因为 Consuming.csproj 引用了 Utility.csproj。 但是,Utility 程序集的开发人员决定删除 Example 对象作为重构的一部分。 此类型已移至新创建的 Common.csproj。

通过从 Utility 程序集中删除此类型,开发人员引入了一项重大更改。 所有进行中的项目在更新到最新的 Utility 程序集时都会中断。

可以转发类型,而不是要求使用项目添加对 Common 程序集的新引用。 由于此类型已从 Utility 程序集中删除,因此你需要让 Utility.csproj 引用 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>

前面的 C# 项目现在引用新创建的 Common 程序集。 这可以是 PackageReferenceProjectReference。 Utility 程序集需要提供类型转发信息。 按照惯例,类型前向声明通常封装在一个名为 TypeForwarders 的文件中,请考虑 Utility 程序集中的以下 TypeForwarders.cs C# 文件:

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

[assembly:TypeForwardedTo(typeof(Example))]

Utility 程序集引用 Common 程序集,并转发 Example 类型。 如果要编译带有类型转发声明的 Utility 程序集并将 Utility.dll 放入 Consuming 箱中,则进行中的应用无需编译即可运行。

请参阅