AOT 警告の概要

アプリケーションをネイティブ AOT として発行すると、ビルド プロセスによって、実行時にアプリケーションをサポートするために必要なすべてのネイティブ コードとデータ構造が生成されます。 これは、アプリケーションを抽象用語 (仮想マシンのプログラム) で説明し、実行時にオンデマンドでネイティブ表現を作成する形式からアプリケーションを実行する非ネイティブ デプロイとは異なります。

プログラム パーツの抽象表現には、ネイティブ表現への 1 対 1 のマッピングはありません。 たとえば、ジェネリック List<T>.Add メソッドの抽象的な説明は、特定の T に特化する必要がある無限のネイティブ メソッド本体 (たとえば、List<int>.AddList<double>.Add) にマップされます。

抽象コードとネイティブ コードの関係は 1 対 1 ではないため、ビルド プロセスでは、ビルド時にネイティブ コード本体とデータ構造の完全な一覧を作成する必要があります。 一部の .NET API では、ビルド時にこの一覧を作成することは困難な場合があります。 ビルド時に予測されなかった方法で API を使用すると、実行時に例外がスローされます。

ネイティブ AOT として展開したときに動作が変更されるのを防ぐために、.NET SDK には、"AOT の警告" を使った、AOT 互換性のスタティック分析が用意されています。AOT の警告は、ビルドによって、AOT と互換性がない可能性があるコードが検出されたときに生成されます。 AOT と互換性のないコードでは、ネイティブ AOT としてビルドされた後、動作変更が発生したり、アプリケーションでクラッシュしたりする可能性もあります。 ネイティブ AOT を使用するすべてのアプリケーションで AOT 警告が示されないことが理想的です。 AOT 警告がある場合は、ネイティブ AOT としてビルドした後でアプリを徹底的にテストすることで、動作変更がないことを確かめます。

AOT 警告の例

ほとんどの C# コードでは、生成する必要があるネイティブ コードを簡単に判断できます。 ネイティブ コンパイラではメソッド本体を調べて、アクセスされるネイティブ コードとデータ構造を見つけることができます。 残念ながら、リフレクションのような一部の機能では、重大な問題が発生します。 次のコードがあるとします。

Type t = typeof(int);
while (true)
{
    t = typeof(GenericType<>).MakeGenericType(t);
    Console.WriteLine(Activator.CreateInstance(t));
}

struct GenericType<T> { }

上記のプログラムはあまり役に立ちませんが、アプリケーションをネイティブ AOT としてビルドするときに、無限の数のジェネリック型を作成する必要がある極端なケースを表しています。 ネイティブ AOT がないと、プログラムはメモリ不足になるまで実行されます。 ネイティブ AOT があると、必要なすべての型 (無限の数) を生成する場合、それをビルドすることもできません。

この場合、ネイティブ AOT ビルドでは、MakeGenericType 行に次の警告が発行されます。

AOT analysis warning IL3050: Program.<Main>$(String[]): Using member 'System.Type.MakeGenericType(Type[])' which has 'RequiresDynamicCodeAttribute' can break functionality when AOT compiling. The native code for this instantiation might not be available at runtime.

実行時に、アプリケーションで実際に MakeGenericType 呼び出しから例外をスローします。

AOT 警告に対応する

AOT 警告は、ネイティブ AOT ビルドに予測可能性をもたらすためのものです。 AOT 警告の大部分は、シナリオをサポートするためにネイティブ コードが生成されなかった場合に発生する可能性のある実行時例外に関するものです。 最も広いカテゴリは RequiresDynamicCodeAttribute です。

RequiresDynamicCode

RequiresDynamicCodeAttribute はシンプルで広範です。これは、メンバーが AOT と互換性がないと注釈が付けられたことを意味する属性です。 この注釈は、メンバーがリフレクションまたは別のメカニズムを使用して、実行時に新しいネイティブ コードを作成する可能性があることを意味します。 この属性は、コードが基本的に AOT と互換性がない場合、またはネイティブ依存関係が複雑すぎてビルド時に静的に予測できない場合に使用されます。 これは、多くの場合、Type.MakeGenericType API、リフレクションの生成、またはその他の実行時コード生成テクノロジを使用するメソッドに当てはまります。 次のコードは例を示します。

[RequiresDynamicCode("Use 'MethodFriendlyToAot' instead")]
void MethodWithReflectionEmit() { ... }

void TestMethod()
{
    // IL3050: Using method 'MethodWithReflectionEmit' which has 'RequiresDynamicCodeAttribute'
    // can break functionality when AOT compiling. Use 'MethodFriendlyToAot' instead.
    MethodWithReflectionEmit();
}

RequiresDynamicCode の回避策はあまり多くありません。 最善の解決策は、ネイティブ AOT としてビルドするときにメソッドをまったく呼び出さないようにし、AOT と互換性のある他のものを使用することです。 ライブラリを記述していて、そのメソッドが呼び出されるかどうかを制御できない場合は、独自のメソッドに RequiresDynamicCode を追加することもできます。 これにより、メソッドに AOT と互換性がないと注釈が付けられます。 RequiresDynamicCode を追加すると、注釈付きメソッド内のすべての AOT 警告が示されませんが、他のユーザーが呼び出すたびに警告が生成されます。 このため、ほとんどの場合、警告をパブリック API に "バブリング" するとライブラリの作成者にとって便利です。

呼び出しが安全であると何らかの方法で判断でき、実行時にすべてのネイティブ コードを使用できる場合は、UnconditionalSuppressMessageAttribute を使用して警告を抑制することもできます。 次に例を示します。

[RequiresDynamicCode("Use 'MethodFriendlyToAot' instead")]
void MethodWithReflectionEmit() { ... }

[UnconditionalSuppressMessage("Aot", "IL3050:RequiresDynamicCode",
    Justification = "The unfriendly method is not reachable with AOT")]
void TestMethod()
{
    If (RuntimeFeature.IsDynamicCodeSupported)
        MethodWithReflectionEmit(); // warning suppressed
}

UnconditionalSuppressMessageSuppressMessage と似たものですが、publish やその他のビルド後のツールによって確認できます。 SuppressMessage#pragma ディレクティブはソースにのみ存在します。そのため、ビルドからの警告を示さないようにするために使用することはできません。

注意事項

AOT 警告を抑制するときは注意してください。 呼び出しは現在 AOT 互換である可能性がありますが、コードを更新すると変更される可能性があり、すべての抑制を確認し忘れる可能性があります。