AOT 警告簡介

將應用程式發佈為原生 AOT 時,建置處理序會產生在執行階段支援應用程式所需的所有機器碼和資料結構。 這與非原生部署不同,其會從以抽象說法 (虛擬機器的程式) 描述應用程式的格式,執行應用程式,並會在執行階段視需要建立原生標記法。

程式組件的抽象標記法,對原生標記法沒有一對一的對應。 例如,泛型 List<T>.Add 方法的抽象描述,會對應至可能無限的原生方法主體,而這些主體需要針對指定的 T 特殊化 (例如 List<int>.AddList<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

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
}

UnconditionalSuppressMessage 就像 SuppressMessage 一樣,但 publish 可以看見它以及其他建置後工具。 SuppressMessage#pragma 指示詞只會出現在來源中,因此無法用於讓組建不要發出警告。

警告

隱藏 AOT 警告時請小心。 呼叫現在可能與 AOT 相容,但當您更新程式碼時,有可能會變更,而且您可能會忘記要檢閱所有隱藏事項。