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

需要动态代码

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