共通言語ランタイムでの型の転送

型の転送を使用すると、別のアセンブリに型を移動する際に、元のアセンブリを使用するアプリケーションを再コンパイルする必要がありません。

たとえば、Utility.dll というアセンブリ内の Example クラスをアプリケーションが使用しているとします。 その場合、Utility.dll の開発者がアセンブリをリファクタリングすることになった際には、その過程で Example クラスが別のアセンブリに移動される可能性があります。 Utility.dll の旧バージョンが Utility.dll の新バージョンとそのコンパニオン アセンブリに置き換えられると、Example クラスを使用するアプリケーションは、新しいバージョンの Utility.dllExample クラスを見つけられないために失敗します。

Utility.dll の開発者は、TypeForwardedToAttribute 属性を使用して、Example クラスの要求を転送することで、この問題を回避できます。 この属性が新バージョンの Utility.dll に適用されている場合、Example クラスに対する要求は、現在このクラスを含んでいるアセンブリに転送されます。 既存のアプリケーションは、再コンパイルを必要とすることなく正常に機能し続けます。

型の転送

型の転送は 4 つの手順で行います。

  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 アセンブリが参照されます。 これは、PackageReference または ProjectReference のいずれかになります。 Utility アセンブリから型転送の情報を提供する必要があります。 慣習により、型転送の宣言は通常 TypeForwarders という名前の単一ファイルにカプセル化されますが、Utility アセンブリにある次の TypeForwarders.cs という C# ファイルを考えてみましょう。

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

[assembly:TypeForwardedTo(typeof(Example))]

Utility アセンブリは Common アセンブリを参照し、これにより Example 型が転送されます。 型転送の宣言を使って Utility アセンブリをコンパイルする場合、Utility.dllConsuming ビンにドロップする場合は、使用するアプリはコンパイルせずに動作します。

関連項目