C# AOT编译限制 C# Native AOT不支持哪些功能

2次阅读

Native AOT 不支持反射动态类型操作、linq表达式树运行时编译、托管代码生成及隐式泛型实例化,因所有类型和方法绑定必须在编译期固化。

C# AOT编译限制 C# Native AOT不支持哪些功能

Native AOT 不支持反射的动态类型操作

Native AOT 在编译期必须确定所有类型和成员的布局,无法在运行时通过 Assembly.LoadType.GetTypeActivator.CreateInstance(String) 动态加载或实例化未知类型。即使类型存在,只要路径是字符串形式(如 "Myapp.Foo"),AOT 就会报错或静默失败。

常见错误现象:System.TypeLoadException: Could not load type 或链接阶段直接失败(如 ILLinkUnresolved assembly)。

  • 允许:使用已知类型的强引用,如 typeof(Foo)new Foo()
  • 禁止:基于字符串的类型解析、Assembly.GetExecutingAssembly().GetTypes()(除非显式保留)
  • 折中方案:用 [DynamicDependency]rd.xml 声明需保留的类型,但仅限编译期可枚举的集合

Native AOT 不支持 LINQ to Objects 的完整表达式树执行

AOT 无法在运行时编译 Expression>委托(即不支持 expr.Compile())。所有表达式必须能在编译期被 ILLink 静态分析并内联,否则链接器会剪掉相关代码,导致 NotSupportedException

使用场景:Entity Framework Core 的客户端求值、自定义查询构建器、运行时拼接条件表达式。

  • 允许:list.Where(x => x.Id > 5)(编译为普通委托,非表达式树)
  • 禁止:Expression> expr = x => x.Age > 18; var func = expr.Compile();
  • 替代方式:改用预定义委托、策略模式或代码生成(如 Source Generator 输出静态方法)

Native AOT 不支持托管堆上的运行时代码生成

包括 Reflection.EmitAssemblyBuilderTypeBuilder)、Delegate.CreateDelegate(部分重载)、以及依赖 JIT 的任何机制。AOT 输出的是纯原生机器码,没有运行时编译器。

典型踩坑点:某些序列化库(如旧版 Newtonsoft.json 的默认设置)、rpc 框架的代理生成、或手写 IL 织入逻辑。

  • 允许:Delegate.CreateDelegate(typeof(Func), obj, "MethodName")(目标方法必须已存在且可达)
  • 禁止:DynamicMethodILGeneratorRuntimeMethodHandle.GetFunctionPointer()
  • 兼容做法:用 System.Text.json(AOT 友好)、或启用 JsonSerializerOptions.PreferredObjectCreationHandling = ObjectCreationHandling.Reuse 避免反射构造

Native AOT 对泛型实例化的限制比 JIT 更严格

AOT 要求每个泛型闭包(如 ListDictionary)在编译期明确出现过,否则不会为其生成本地代码。隐式泛型实例(如仅通过反射触发)会被剪掉。

性能影响:过度保守的泛型使用会导致二进制体积膨胀;过于激进的剪裁则引发运行时 MissingMethodException

  • 必须显式触发:在代码中写 new List() 或标记 [RequiresUnreferencedCode] 提示工具链保留
  • 接口实现泛型类时,若实现类未被直接引用,需用 [DynamicDependency] 关联
  • typeof(T).IsGenericType 等反射检查仍可用,但不能据此决定是否实例化新泛型类型

AOT 的核心约束都源于“无运行时编译器”这一事实——所有类型、方法、委托绑定必须在链接前固化。最容易被忽略的是那些看似 innocuous 的反射调用,比如日志框架里根据命名空间自动扫描 Handler,或者配置系统里按字符串名查找转换器。这些逻辑在 JIT 下跑得欢,在 AOT 下往往悄无声息地失效。

text=ZqhQzanResources