將應用程式發佈為原生 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 相容,但當您更新程式碼時,情況可能會改變,您可能會忘記檢查所有抑制項目。