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 兼容,但当你更新代码时,这种情况可能会改变,并且你可能会忘记查看所有抑制。