アプリケーションをネイティブ AOT として発行すると、ビルド プロセスによって、実行時にアプリケーションをサポートするために必要なすべてのネイティブ コードとデータ構造が生成されます。 これは、アプリケーションを抽象用語 (仮想マシンのプログラム) で記述し、実行時にオンデマンドでネイティブ表現を作成する形式からアプリケーションを実行する非ネイティブ デプロイとは異なります。
プログラム パーツの抽象表現には、ネイティブ表現への 1 対 1 のマッピングはありません。 たとえば、ジェネリック List<T>.Add
メソッドの抽象的な説明は、特定の T
に特化する必要がある無限のネイティブ メソッド本体 (たとえば、 List<int>.Add
や List<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 がないと、プログラムはメモリ不足になるまで実行されます。 Native 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
です。
動的コードが必要
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
}
UnconditionalSuppressMessage
は SuppressMessage
のようなものですが、 publish
やその他のビルド後のツールで確認できます。 SuppressMessage
ディレクティブと #pragma
ディレクティブはソースにのみ存在するため、ビルドからの警告を無音にするために使用することはできません。
注意事項
AOT 警告を抑制するときは注意してください。 呼び出しは現在 AOT 互換である可能性がありますが、コードを更新すると変更される可能性があり、すべての抑制を確認し忘れる可能性があります。
.NET