當你的應用程式以原生 AOT 發佈時,建置過程會產生所有支援應用程式執行時所需的原生程式碼和資料結構。 這與非原生部署不同,非原生部署是從以抽象形式描述應用程式的格式(例如虛擬機器的程式)執行應用程式,並在執行時按需建立原生表示。
程式元件的抽象表示法沒有原生表示法的一對一對應。 例如,泛型 List<T>.Add 方法的抽象描述會對應至可能無限的原生方法主體,這些主體需要針對指定 T 特製化(例如 List<int>.Add 和 List<double>.Add)。
由於抽象程式代碼與機器碼的關聯性不是一對一,因此建置程式必須在建置時建立原生程式代碼主體和數據結構的完整清單。 在某些 .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。
需要動態程式碼
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 相容,但當您更新程式碼時,情況可能會改變,您可能會忘記檢查所有抑制項目。