解决MySql布尔型新旧版本兼容问题,采用枚举来表示布尔型的数据表。由正向工程赋值
|
# NewLife.Core AOT 兼容性分析报告
> 分析日期:2026-03-07
> 分析版本:NewLife.Core 11.13.x
> AOT 标准:.NET NativeAOT(PublishAot=true)
---
## 概述
本报告对 `NewLife.Core` 库进行全面的 AOT(Ahead-of-Time)兼容性扫描,识别所有在 NativeAOT 编译模式下会导致编译警告、运行时崩溃或功能失效的代码模式,并给出修复建议。
**结论:NewLife.Core 当前大量依赖动态反射机制,其核心设计哲学(运行时动态类型解析、插件扫描、脚本引擎)与 AOT 的静态分析要求存在根本性冲突。** 若要支持 AOT,需要按模块分层处理:部分模块可通过添加 AOT 注解修复,部分模块需要提供替代 AOT 友好实现,少数模块必须标记为 `[RequiresDynamicCode]` 不支持 AOT。
---
## 问题严重级别定义
| 级别 | 含义 |
|------|------|
| 🔴 **严重** | AOT 下会直接崩溃或无法编译,无简单绕过方案 |
| 🟠 **高** | AOT 下运行时异常,需要重构逻辑 |
| 🟡 **中** | AOT 下产生警告,可通过添加注解或源生成器解决 |
| 🟢 **低** | 轻微兼容性问题,有简单修复方案 |
---
## 一、脚本引擎(ScriptEngine)— 🔴 严重,完全不支持 AOT
**文件:** `NewLife.Core/Reflection/ScriptEngine.cs`
### 问题描述
ScriptEngine 是运行时 C# 代码编译引擎,包含以下完全不支持 AOT 的机制:
1. **`CodeDomProvider`** — 调用 `CodeDomProvider.CreateProvider("CSharp")` 在 AOT 下不存在
2. **`System.Reflection.Emit`** — `using System.Reflection.Emit;`,AOT 下 `Emit` 命名空间大部分 API 不可用
3. **`Assembly.Load(byte[])` 动态加载程序集** — AOT 下禁止动态加载代码
4. **`AppDomain.CurrentDomain.AssemblyResolve` 事件** — 部分功能在 AOT 下受限
5. **`rs.CompiledAssembly.GetTypes()` 运行时枚举类型**
```csharp
// ScriptEngine.cs:433 — 完全不可用于 AOT
var provider = CodeDomProvider.CreateProvider("CSharp", opts);
// ScriptEngine.cs:318 — AOT 下禁止动态 IL 加载
Assembly.Load(File.ReadAllBytes(item));
// ScriptEngine.cs:326 — 动态编译产物枚举
Type = rs.CompiledAssembly.GetTypes()[0];
```
**已由 `#if __WIN__` 条件编译保护**,仅 Windows 目标有效,但若 Windows 目标使用 AOT 仍会报错。
### 修复建议
- 整个 `ScriptEngine` 类添加 `[RequiresDynamicCode("ScriptEngine 依赖运行时代码生成,不支持 AOT")]`
- 添加 `[RequiresUnreferencedCode("...")]` 注解
- 文档明确标注该功能需要 `PublishAot=false`
---
## 二、反射子系统(Reflection 模块)— 🔴 严重
该模块是 NewLife.Core 最核心的基础设施,整个框架大量构建于此,AOT 问题影响范围极广。
### 2.1 动态类型查找 — `Type.GetType(string)`
**文件:** `NewLife.Core/Reflection/Reflect.cs`
```csharp
// Reflect.cs:28 — 通过字符串查找类型,AOT 剪裁后类型可能不存在
var type = Type.GetType(typeName);
// Reflect.cs:43 — 同上
var type = Type.GetType(typeName);
```
AOT 剪裁(trimming)会移除未被静态引用的类型,`Type.GetType(string)` 依赖运行时完整类型注册表,剪裁后可能返回 `null`。
**修复建议:** 对外暴露的 `GetType(string)` 方法添加 `[RequiresUnreferencedCode]` 注解,提示调用方需要保留类型元数据。
---
### 2.2 GetAllSubclasses — 全程序集类型扫描
**文件:** `NewLife.Core/Reflection/IReflect.cs`,`AssemblyX.cs`,`Reflect.cs`
```csharp
// IReflect.cs:909 — 扫描所有已加载程序集中的子类型
public virtual IEnumerable<Type> GetAllSubclasses(Type baseType)
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) // 🔴
foreach (var type in GetSubclasses(asm, baseType))
yield return type;
}
// AssemblyX.cs:156 — 获取所有类型列表
ts = Asm.GetTypes(); // 🟠
```
AOT 下 `AppDomain.CurrentDomain.GetAssemblies()` 依然可用,但 trimming 后很多类型已被移除,扫描结果不完整。依赖此 API 的功能(插件发现、OAuthClient 子类发现等)在 AOT 下会静默失效。
**AssemblyX.cs 中多处 Assembly 动态加载(🔴 严重):**
```csharp
// AssemblyX.cs:130 — AssemblyResolve 事件回调中按文件路径加载程序集
private static Assembly? OnAssemblyResolve(...)
{
var asm = Assembly.LoadFrom(file); // 🔴
}
// AssemblyX.cs:649-655 — 扫描目录后逐个 LoadFrom 加载
foreach (var item in ss)
{
asm = Assembly.LoadFrom(item); // 🔴
}
// AssemblyX.cs:744-745 — OnResolve 中按路径加载未注册程序集
var asm = Assembly.LoadFrom(file); // 🔴(在 AssemblyPaths 中查找后加载)
// AssemblyX.cs:155-198 — GetTypes + GetNestedTypes 完整类型遍历
ts = Asm.GetTypes(); // 🟠
item.GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic | ...) // 🟠
```
`Assembly.LoadFrom` 在 NativeAOT 下完全不支持(`PlatformNotSupportedException`),`GetTypes()` + `GetNestedTypes()` 依赖运行时完整类型元数据,在 trimming 后结果不完整。
**受影响的调用方:**
- `Web/OAuthClient.cs:96` — `typeof(OAuthClient).GetAllSubclasses(true)` 动态发现 OAuth 提供商
- `Model/IPlugin.cs:93` — 动态创建插件实例
- `Serialization/Json/JsonTest.cs:25` — 动态发现 IJsonHost 实现
**修复建议:**
- `GetAllSubclasses`/`GetSubclasses`/`FindPlugins` 添加 `[RequiresUnreferencedCode]` 注解
- 为 AOT 场景提供手动注册机制(显式注册子类型,代替自动扫描)
---
### 2.3 动态对象创建 — `Activator.CreateInstance`、`ConstructorInfo.Invoke`
**文件:** `NewLife.Core/Reflection/IReflect.cs`,`NewLife.Core/Model/ObjectContainer.cs`
```csharp
// IReflect.cs:495 — 非公开构造函数创建,AOT 下可能被剪裁
_ => Activator.CreateInstance(type, true),
// IReflect.cs:499 — 带参数创建实例
return Activator.CreateInstance(type, parameters);
// ObjectContainer.cs:212,242 — 扫描构造函数并动态调用
var constructors = type.GetConstructors();
// ...
return constructorInfo.Invoke(pv); // 🔴
```
AOT 下 `Activator.CreateInstance(Type, ...)` 需要保留目标类型的构造函数元数据,否则运行时失败。`GetConstructors()` + `Invoke` 模式是 AOT 的典型高风险模式。
**修复建议:**
- `ObjectContainer.CreateInstance` 方法添加 `[RequiresDynamicCode]` 和 `[RequiresUnreferencedCode]`
- 对于已知类型,使用工厂委托 `Func<IServiceProvider, Object>` 代替动态构造
- 长期:引入源生成器自动为注册类型生成工厂代码
---
### 2.4 DynamicObject — 动态绑定器
**文件:** `NewLife.Core/Reflection/DynamicXml.cs`,`NewLife.Core/Reflection/DynamicInternal.cs`
```csharp
// DynamicXml.cs:7 — 继承 DynamicObject 不支持 AOT
public class DynamicXml : DynamicObject
// DynamicInternal.cs:8 — 同上
public class DynamicInternal : DynamicObject
// DynamicInternal.cs:53 — InvokeMember 在 AOT 下不支持
result = Real.GetType().InvokeMember(binder.Name,
BindingFlags.InvokeMethod | ..., null, Real, args, ...);
```
`DynamicObject` 依赖 DLR(Dynamic Language Runtime)动态绑定机制,AOT 下 DLR 不可用(`System.Dynamic` 命名空间中的核心功能在 NativeAOT 下受限)。`InvokeMember` 在 AOT 下完全不可用。
**修复建议:**
- `DynamicXml` 和 `DynamicInternal` 类标记 `[RequiresDynamicCode]`
- 如有 AOT 使用场景,改用强类型包装器代替 `DynamicObject`
---
### 2.5 MakeGenericType — 运行时泛型构造
**文件:** 遍布序列化、配置、反射子系统
```csharp
// Configuration/ConfigHelper.cs:191
typeof(Dictionary<,>).MakeGenericType(pi.PropertyType.GetGenericArguments())
// Configuration/ConfigHelper.cs:323
typeof(List<>).MakeGenericType(elementType).CreateInstance()
// Reflection/IReflect.cs:843
baseType = baseType.MakeGenericType(type.GenericTypeArguments);
// Serialization/Json/JsonReader.cs:94
type = typeof(List<>).MakeGenericType(elmType);
// Serialization/ServiceTypeResolver.cs:33,38
typeof(List<>).MakeGenericType(...) / typeof(Dictionary<,>).MakeGenericType(...)
```
`MakeGenericType` 在 AOT 下需要目标泛型实例化(如 `List<String>`、`Dictionary<String, Int32>`)在编译时已存在。对于**未提前枚举的类型组合**,AOT 下运行时会抛出异常。
常见组合(`List<String>`、`Dictionary<String, Object>`)通常已在编译时存在,风险较低;但对于用户自定义类型的动态组合,存在运行时失败风险。
**修复建议:**
- 添加 `[RequiresDynamicCode("使用 MakeGenericType 构造运行时泛型类型")]` 注解
- 对于有限的常见类型组合,可改用 `switch` 或预先注册的方式
---
## 三、序列化子系统(Serialization 模块)— 🟠 高
### 3.1 Expression.Compile() — 动态委托生成
**文件:** `NewLife.Core/Serialization/SpanSerializer.cs`
```csharp
// SpanSerializer.cs:63 — 编译属性取值 Lambda
return Expression.Lambda<Func<Object, Object?>>(body, target).Compile(); // 🟠
// SpanSerializer.cs:78 — 编译属性赋值 Lambda
return Expression.Lambda<Action<Object, Object?>>(assign, target, value).Compile(); // 🟠
```
`Expression.Compile()` 在 AOT 下依赖解释执行模式(`CompileToMethod` 已不可用),性能会大幅下降(无 JIT),甚至在某些 AOT 配置下抛出异常。
**修复建议:**
- 对 AOT 场景,改用直接反射(`PropertyInfo.GetValue`/`SetValue`)或通过源生成器预生成访问代码
- 添加运行时检测:`#if NET7_0_OR_GREATER` 可使用 `RuntimeFeature.IsDynamicCodeSupported` 选择降级路径
- 在 `BuildGetter`/`BuildSetter` 方法上添加 `[RequiresDynamicCode]`
---
### 3.2 BinaryFormatter — 已废弃,不支持 AOT
**文件:** `NewLife.Core/Serialization/Binary/BinaryUnknown.cs`,`JsonTest.cs`
```csharp
// BinaryUnknown.cs:35,60 — BinaryFormatter 在 .NET 9+ 已移除
var bf = new BinaryFormatter();
```
`BinaryFormatter` 在 .NET 5 开始标记废弃,.NET 9 默认禁用(需要 `AppContext.SetSwitch("System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization", true)`),NativeAOT 下完全不可用。
**修复建议:**
- 替换为 `System.Text.Json`、自定义二进制序列化或 `MemoryPack`
- `JsonTest.cs` 是测试代码,可直接移除相关测试用例
---
### 3.3 XmlSerializer — 动态代码生成
**文件:** `NewLife.Core/Xml/SerializableDictionary.cs`
```csharp
// SerializableDictionary.cs:88,98 — 动态生成序列化代码
var xs = new XmlSerializer(type);
```
`XmlSerializer` 在构造时会动态生成序列化/反序列化代码(在 .NET Framework 下运行时生成 DLL),AOT 下需要预生成(使用 `XmlSerializer` 的源生成模式或手动预编译)。
**修复建议:**
- 使用 `[XmlSerializerGenerator]` 源生成器预生成序列化代码(.NET 8+)
- 或改用 `System.Text.Json` + 手动序列化
---
### 3.4 DefaultJsonTypeInfoResolver — 基于反射的 JSON 元数据
**文件:** `NewLife.Core/Serialization/Json/IJsonHost.cs`
```csharp
// IJsonHost.cs:326 — DefaultJsonTypeInfoResolver 依赖反射,剪裁不安全
opt.TypeInfoResolver = new System.Text.Json.Serialization.Metadata.DefaultJsonTypeInfoResolver
{
Modifiers = { ... }
};
```
`DefaultJsonTypeInfoResolver` 使用反射扫描类型元数据,在 trimming 启用时会产生 `IL2026` 警告,AOT 下反射扫描到的属性可能已被剪裁。
**SystemJson 的 `Read` 方法:**
```csharp
// IJsonHost.cs:398 — 使用运行时 Type 对象反序列化,不安全
return JsonSerializer.Deserialize(json, type, opt);
```
**修复建议:**
- 为 AOT 场景提供 `JsonSerializerContext`(源生成器)实现
- `SystemJson` 类标记 `[RequiresUnreferencedCode]`
---
### 3.5 FastJson — 全反射 JSON 序列化
**文件:** `NewLife.Core/Serialization/Json/JsonReader.cs`,`JsonWriter.cs`
```csharp
// JsonReader.cs:246 — 通过字符串反序列化 Type 对象
if (type == typeof(Type) && value is String str)
return Type.GetType(str) ?? Type.GetType("System." + str);
// JsonReader.cs:94,148 — 运行时泛型构造
type = typeof(List<>).MakeGenericType(elmType);
type = typeof(Dictionary<,>).MakeGenericType(types[0], types[1]);
```
FastJson 完全基于反射,不支持 AOT。
**修复建议:**
- FastJson 整体标记 `[RequiresUnreferencedCode]` 和 `[RequiresDynamicCode]`
- AOT 场景应使用 `SystemJson`(配合源生成器)
---
### 3.6 XmlHelper — 方法名字符串动态查找 — 🟠 高
**文件:** `NewLife.Core/Xml/XmlHelper.cs`
```csharp
// XmlHelper.cs:241 — 拼接类型名查找 XmlConvert 的 ToString 重载
var method = typeof(XmlConvert).GetMethodEx("ToString", type);
// XmlHelper.cs:258 — 拼接字符串 "To" + 类型名查找转换方法
var method = typeof(XmlConvert).GetMethodEx("To" + type.Name, typeof(String));
```
`GetMethodEx` 是 `Type.GetMethod(name, paramTypes)` 的扩展包装。`"To" + type.Name` 在运行时动态构造方法名(如 `"ToInt32"`、`"ToBoolean"`),AOT trimming 会按静态引用图剪裁 `XmlConvert` 的重载,若应用中没有静态调用 `XmlConvert.ToInt32(string)`,该方法可能在编译时被移除,导致运行时 `GetMethodEx` 返回 `null` 并抛出 `XException`。
**受影响的方法:**
- `XmlHelper.XmlConvertToString(Object)` — 将基础类型转为 XML 字符串
- `XmlHelper.XmlConvertFromString(Type, String)` — 从 XML 字符串还原基础类型值
**修复建议:**
- 改用显式 `switch(TypeCode)` 分支调用具体的 `XmlConvert` 方法,完全消除反射
- 参考 `TypeCode` 枚举覆盖所有基础类型,静态分支对 AOT 安全且性能更好
```csharp
// 建议替换为 switch 分支(AOT 安全)
internal static String? XmlConvertToString(Object value)
{
return value.GetType().GetTypeCode() switch
{
TypeCode.Boolean => XmlConvert.ToString((Boolean)value),
TypeCode.Int32 => XmlConvert.ToString((Int32)value),
TypeCode.Double => XmlConvert.ToString((Double)value),
// ... 其他基础类型
_ => throw new XException($"Type {value.GetType()} does not support XmlConvert")
};
}
```
---
## 四、数据层(Data 模块)— 🟡 中
### 4.1 DbTable — 运行时类型反序列化
**文件:** `NewLife.Core/Data/DbTable.cs`
```csharp
// DbTable.cs:286,288,812,814 — 从字符串还原 .NET 类型
ts[i] = Type.GetType("System." + tc) ?? typeof(Object);
ts[i] = Type.GetType(binary.Read<String>() + "") ?? typeof(Object);
```
从持久化数据中读取类型名称并动态解析,在 AOT 下字符串对应的类型可能已被剪裁。
**修复建议:**
- 对系统类型(`System.*`)可改用 `TypeCode` 枚举映射,无需 `Type.GetType`
- 自定义类型反序列化添加 `[RequiresUnreferencedCode]` 注解
---
### 4.2 BinaryTree — 表达式编译
**文件:** `NewLife.Core/Data/BinaryTree.cs`
```csharp
// BinaryTree.cs:136 — 通过字符串查找方法,反射,AOT 风险
ops.Add(left => Expression.Call(typeof(Math).GetMethod("Sqrt"), left));
// BinaryTree.cs:141 — 同上
ops.Add(left => Expression.Call(
typeof(BinaryTree).GetMethod(nameof(Cbrt),
BindingFlags.NonPublic | BindingFlags.Static), left));
// BinaryTree.cs:190 — 表达式编译
var compiled = Expression.Lambda<Func<Double>>(exp).Compile();
```
`typeof(Math).GetMethod("Sqrt")` 虽然在 AOT 下方法通常存在,但结合 `Expression.Compile()` 的使用,整个数学表达式解析器在 AOT 下存在风险。
**修复建议:**
- 使用 `nameof` + 强类型委托代替字符串查找(`typeof(Math).GetMethod("Sqrt")` → 直接引用 `Math.Sqrt`)
- `Expression.Compile()` 替换为预先静态编译的逻辑(switch 分发)
---
## 五、缓存系统(Caching 模块)— 🟡 中
### 5.1 MemoryCache — 类型名称解析
**文件:** `NewLife.Core/Caching/MemoryCache.cs`
```csharp
// MemoryCache.cs:914 — 从字符串名称获取类型
var type = Type.GetType("System." + code);
```
用于从序列化数据恢复缓存值的类型信息,AOT 下可能找不到类型。
**修复建议:** 改用 `TypeCode` 枚举映射代替字符串类型查找。
---
### 5.2 Redis — GetType().CreateInstance() 自克隆 — 🟡 中
**文件:** `NewLife.Core/Caching/Redis.cs`
```csharp
// Redis.cs:540 — 通过运行时类型反射克隆自身,用于创建子库实例
public virtual Redis CreateSub(Int32 db)
{
var rds = GetType().CreateInstance() as Redis; // 🟡
rds.Server = Server;
rds.Db = db;
// ... 复制属性
return rds;
}
```
`GetType().CreateInstance()` 调用的是扩展方法 `Activator.CreateInstance(type, true)`,获取的是**运行时的实际派生类型**(如 `FullRedis`),AOT 下派生类型的无参构造函数可能因 trimming 被移除。
**修复建议:**
- 将 `CreateSub` 方法改为 `virtual`,要求派生类 override 并手动 `new` 自身,避免反射克隆
- 或使用 `[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]` 注解 `Redis` 类,保留所有子类无参构造
---
## 六、公共模块(Common)— 🟢 低
### 6.1 Runtime — 运行时环境检测
**文件:** `NewLife.Core/Common/Runtime.cs`
```csharp
// Runtime.cs:21 — 检测 Mono 运行时
Mono = Type.GetType("Mono.Runtime") != null;
// Runtime.cs:26 — 检测 Unity 运行时
Unity = Type.GetType("UnityEngine.Application, UnityEngine") != null;
// Runtime.cs:91 — 检测 ASP.NET Core
var asm = AppDomain.CurrentDomain.GetAssemblies()
.FirstOrDefault(e => e.GetName().Name == "Microsoft.AspNetCore");
```
运行时环境检测代码在 AOT 下行为不一致(Mono 和 Unity 场景下 AOT 检测有意义,但 `Type.GetType` 可能返回 null)。AOT 目标通常不是 Mono/Unity,此处风险较低,但应明确处理。
**修复建议:**
- 可使用条件编译 `#if UNITY_*` 替代运行时检测
- `AppDomain.CurrentDomain.GetAssemblies()` 扫描可改为检测 `Type.GetType("Microsoft.AspNetCore.Hosting.IWebHostEnvironment")`(仍有风险但更安全)
---
## 七、Web 与插件模块 — 🔴 严重
### 7.1 PluginHelper — 动态程序集加载
**文件:** `NewLife.Core/Web/PluginHelper.cs`
```csharp
// PluginHelper.cs:51 — 从文件动态加载程序集
var asm = Assembly.LoadFrom(file); // 🔴
// PluginHelper.cs:24,68 — 通过字符串查找类型
var type = Type.GetType(typeName); // 🔴
```
`Assembly.LoadFrom` 在 NativeAOT 下完全不支持,动态程序集加载是 AOT 的根本限制。
**修复建议:**
- 整个 `PluginHelper` 类标记 `[RequiresDynamicCode]`
- AOT 场景应使用静态注册机制代替插件文件加载
---
### 7.2 OAuthClient — 动态子类扫描
**文件:** `NewLife.Core/Web/OAuthClient.cs`
```csharp
// OAuthClient.cs:96 — 扫描所有程序集中的 OAuthClient 子类
foreach (var item in typeof(OAuthClient).GetAllSubclasses(true))
```
AOT 剪裁会移除未被直接引用的子类,动态扫描结果不完整。
**修复建议:**
- 提供静态注册 API:`OAuthClient.Register<GitHubAuthClient>()`
- 或使用源生成器在编译时生成子类注册列表
---
## 八、HTTP 处理器(Http 模块)— 🟠 高
### 8.1 IHttpHandler — Delegate.DynamicInvoke
**文件:** `NewLife.Core/Http/IHttpHandler.cs`
```csharp
// IHttpHandler.cs:52,69 — DynamicInvoke 在 AOT 下不可用
if (pis.Length == 0) return handler.DynamicInvoke(); // 🔴
return handler.DynamicInvoke(args); // 🔴
// IHttpHandler.cs:86 — 运行时反射访问 Task<T>.Result
var resultProperty = taskType.GetProperty("Result", BindingFlags.Public | BindingFlags.Instance);
```
`Delegate.DynamicInvoke` 在 AOT 下不支持(依赖 DLR),会抛出 `PlatformNotSupportedException`。
**修复建议:**
- 改用强类型委托调用代替 `DynamicInvoke`
- `Task<T>.Result` 访问改用 `((Task<T>)task).Result` 强类型转换,或使用 `await`
---
### 8.2 ControllerHandler — 运行时方法查找与调用
**文件:** `NewLife.Core/Http/ControllerHandler.cs`
```csharp
// ControllerHandler.cs:42 — 通过名称字符串查找方法
method = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | ...);
// ControllerHandler.cs:47 — 通过反射调用方法
var result = controller.InvokeWithParams(method, context.Parameters as IDictionary);
```
HTTP 路由到控制器方法的动态分发机制完全基于反射,AOT 下方法可能被剪裁。
**修复建议:**
- 改用源生成器预生成路由分发代码
- 或要求控制器方法通过委托显式注册
---
## 九、扩展模块 — 🟡 中
### 9.1 SpeakProvider / SpeechRecognition — 动态程序集加载
**文件:** `NewLife.Core/Extension/SpeakProvider.cs`,`Windows/SpeechRecognition.cs`
```csharp
// SpeakProvider.cs:26,32 — 动态加载语音程序集
asm ??= Assembly.Load("System.Speech, Version=4.0.0.0, ...");
asm ??= Assembly.Load("System.Speech");
// SpeakProvider.cs:17 — 字符串类型解析
_type = Type.GetType(typeName);
```
已在 `#if __WIN__` 或环境判断内,但仍是 AOT 不支持的模式。
**修复建议:** 整体标记 `[RequiresDynamicCode]`,AOT 场景跳过语音功能。
---
### 9.3 配置提供者工厂 — 动态 CreateInstance — 🟡 中
**文件:** `NewLife.Core/Configuration/IConfigProvider.cs`
```csharp
// IConfigProvider.cs:393 — 从注册类型字典动态创建配置提供者实例
public static IConfigProvider? Create(String? name)
{
// ...
if (!_providers.TryGetValue(ext, out var type)) throw new Exception(...);
var config = type.CreateInstance() as IConfigProvider; // 🟡
return config;
}
```
`_providers` 字典存储的是 `Type` 对象(通过 `Register<TProvider>(name)` 在静态构造中注册)。`type.CreateInstance()` 等同于 `Activator.CreateInstance(type, true)`,AOT 下这些 `Type` 对象本身可以保留,但无参构造函数若未被静态引用则可能已被 trimming 剪裁。
**内置注册的实现类型:**
- `IniConfigProvider`、`XmlConfigProvider`、`JsonConfigProvider`、`HttpConfigProvider`
这些类型在静态构造中已经通过 `Register<T>` 保留了类型信息,但 AOT trimmer 并不能从 `ConcurrentDictionary<string, Type>` 中推断 trimming 边界。
**修复建议:**
- 添加 `[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]` 到 `_providers` 字典的值类型
- 或改用工厂委托 `ConcurrentDictionary<String, Func<IConfigProvider>>`,彻底消除反射
```csharp
// AOT 安全的工厂方式
Register("xml", () => new XmlConfigProvider());
Register("json", () => new JsonConfigProvider());
```
**文件:** `NewLife.Core/Extension/EnumHelper.cs`
```csharp
// EnumHelper.cs:56 — 通过字段名字符串查找字段
var field = type.GetField(value.ToString(), BindingFlags.Public | BindingFlags.Static);
// EnumHelper.cs:61 — 读取 DescriptionAttribute
var description = field.GetCustomAttribute<DescriptionAttribute>(false);
```
枚举字段反射在 AOT 下通常比较安全(枚举字段名和特性不会被剪裁),但 `GetCustomAttribute<T>()` 在极端剪裁配置下可能丢失特性数据。
**修复建议:** 添加 `[RequiresUnreferencedCode]` 注解以告知调用方在剪裁时需要保留枚举元数据。
---
## 十、P/Invoke(DllImport)模块 — 🟢 低
**全量扫描确认的文件(共 10 个,含 DllImport 数量):**
| 文件 | DllImport 数量 |
|------|---------------|
| `Common/Runtime.cs` | 2 |
| `Common/MachineInfo.cs` | 2 |
| `Extension/ProcessHelper.cs` | 5 |
| `Log/CodeTimer.cs` | 3 |
| `Net/NetHelper.cs` | 1 |
| `Net/TcpConnectionInformation2.cs` | 1 |
| `Security/Certificate.cs` | 13 |
| `Windows/ConsoleHelper.cs` | 6 |
| `Windows/ControlHelper.cs` | 1 |
| `Windows/PowerStatus.cs` | 1 |
`Log/CodeTimer.cs` 用于精确统计线程 CPU 周期数和线程时间,调用 Win32 API:
```csharp
// CodeTimer.cs:95
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern Boolean QueryThreadCycleTime(IntPtr threadHandle, ref UInt64 cycleTime);
// CodeTimer.cs:99
[DllImport("kernel32.dll")]
static extern IntPtr GetCurrentThread();
// CodeTimer.cs:102
[DllImport("kernel32.dll", SetLastError = true)]
static extern Boolean GetThreadTimes(IntPtr hThread, out Int64 lpCreationTime, ...);
```
P/Invoke(`[DllImport]`)本身支持 AOT,但建议迁移到更 AOT 友好的 `[LibraryImport]`(.NET 7+)。
```csharp
// 现有写法(可用但次优)
[DllImport("kernel32.dll")]
static extern IntPtr GetCurrentProcess();
// 推荐写法(AOT 更友好,源生成器处理 marshaling)
[LibraryImport("kernel32.dll")]
static partial IntPtr GetCurrentProcess();
```
主要风险在于 `Marshal.PtrToStructure<T>` 的使用:
```csharp
// ProcessHelper.cs:169 — 泛型版已支持 AOT,低风险
val = (T)Marshal.PtrToStructure(ptr, typeof(T))!;
// 推荐改用泛型重载
val = Marshal.PtrToStructure<T>(ptr);
```
**修复建议:** 低优先级,可在专项 P/Invoke 重构中批量处理。
---
## 十一、ObjectContainer(IoC 容器)— 🟠 高
**文件:** `NewLife.Core/Model/ObjectContainer.cs`
```csharp
// ObjectContainer.cs:212 — 扫描所有构造函数
var constructors = type.GetConstructors();
// ObjectContainer.cs:242 — 动态调用构造函数
return constructorInfo.Invoke(pv);
```
整个 IoC 容器的实例创建机制依赖运行时构造函数扫描和动态调用。这是 AOT 的核心挑战之一,与 `Microsoft.Extensions.DependencyInjection` 在 .NET 8+ 中已添加 AOT 支持的做法相同。
**修复建议:**
- 参考 `Microsoft.Extensions.DependencyInjection.ActivatorUtilities` 的 AOT 源生成器方案
- 要求注册类型时提供工厂委托,避免运行时构造函数扫描
- 提供 `[ServiceRegistration]` 源生成器自动生成工厂代码
---
## 十二、汇总与优先级
### 按模块分类
| 模块 | 严重级别 | 主要问题 | 可否简单修复 |
|------|----------|----------|-------------|
| `ScriptEngine` | 🔴 严重 | CSharpCodeProvider + Emit | 否,需标记不支持 AOT |
| `DynamicXml`/`DynamicInternal` | 🔴 严重 | DynamicObject + InvokeMember | 否,需重构或标记 |
| `PluginHelper` | 🔴 严重 | Assembly.LoadFrom | 否,需重新设计插件机制 |
| `Delegate.DynamicInvoke` (IHttpHandler) | 🔴 严重 | DynamicInvoke | 是,改为强类型调用 |
| `BinaryFormatter` | 🔴 严重 | 已废弃 API | 是,替换为其他序列化 |
| `ObjectContainer` | 🟠 高 | 构造函数动态扫描+调用 | 中等,需引入工厂模式 |
| `GetAllSubclasses` | 🟠 高 | 程序集枚举 | 中等,需手动注册机制 |
| `ControllerHandler` | 🟠 高 | 运行时方法查找+调用 | 中等,需源生成器支持 |
| `SpanSerializer` | 🟠 高 | Expression.Compile() | 部分,可降级为直接反射 |
| `BinaryTree` | 🟠 高 | Expression.Compile() | 是,改用 switch 分发 |
| `XmlSerializer` (动态) | 🟠 高 | 运行时代码生成 | 中等,需源生成器预生成 |
| `XmlHelper` 方法名字符串查找 | 🟠 高 | GetMethodEx("To"+type.Name) | 是,改用 switch(TypeCode) 分支 |
| `ConfigProvider.Create()` 工厂 | 🟡 中 | type.CreateInstance() 动态创建 | 是,改用工厂委托 |
| `Redis.CreateSub` 自克隆 | 🟡 中 | GetType().CreateInstance() | 是,override 或加 DynamicallyAccessedMembers |
| `FastJson` | 🟡 中 | 全反射序列化 | 需整体添加 AOT 注解 |
| `DefaultJsonTypeInfoResolver` | 🟡 中 | 反射元数据扫描 | 是,改用 JsonSerializerContext |
| `DbTable` 类型解析 | 🟡 中 | Type.GetType() | 是,改用 TypeCode 映射 |
| `MakeGenericType` | 🟡 中 | 运行时泛型构造 | 部分,需注解+类型约束 |
| `SpeakProvider` | 🟡 中 | Assembly.Load | 是,标记不支持 AOT |
| `Type.GetType()` 系列 | 🟡 中 | 字符串类型查找 | 是,添加注解 |
| `EnumHelper` | 🟢 低 | GetCustomAttribute | 是,添加注解 |
| `DllImport` P/Invoke(10个文件含 CodeTimer) | 🟢 低 | 建议迁移 LibraryImport | 是,可批量处理 |
| `OAuthClient` 子类扫描 | 🟢 低 | GetAllSubclasses | 是,改为手动注册 |
---
### AOT 适配路线图建议
#### 第一阶段:注解标记(低成本,减少警告)
为以下 API 添加 `[RequiresUnreferencedCode]` 和 `[RequiresDynamicCode]` 注解:
- `ScriptEngine` 整个类
- `DynamicXml`、`DynamicInternal` 整个类
- `PluginHelper` 整个类
- `SpeakProvider` 相关方法
- `GetAllSubclasses`、`GetSubclasses`、`FindPlugins`
- `FastJson.Read`、`FastJson.Write`(带 Type 参数
- `ObjectContainer.CreateInstance`
#### 第二阶段:替换高风险 API(中等成本)
- `Delegate.DynamicInvoke` → 强类型委托调用
- `BinaryFormatter` → 移除或替换
- `Type.GetType()` → TypeCode 枚举映射(对系统类型)
- `BinaryTree.Expression.Compile()` → switch 分发静态逻辑
#### 第三阶段:架构性重构(高成本,长期方向)
- `ObjectContainer` 引入源生成器工厂
- `ControllerHandler` 引入源生成器路由分发
- `SystemJson` 配套 `JsonSerializerContext` 源生成器
- 插件系统改为静态注册机制
- OAuthClient 子类改为显式注册
---
## 附录:AOT 关键 API 参考
| 需要迁移的 API | AOT 友好替代方案 |
|----------------|----------------|
| `Type.GetType(string)` | 预注册类型字典 / 源生成器 |
| `Assembly.Load(string/byte[])` | 静态引用 / 不支持 |
| `Assembly.GetTypes()` | 源生成器枚举已知类型 |
| `Activator.CreateInstance(Type)` | 工厂委托 / `ActivatorUtilities` |
| `ConstructorInfo.Invoke(...)` | 工厂委托 |
| `MethodInfo.Invoke(...)` | 强类型委托 `Delegate.CreateDelegate` |
| `Delegate.DynamicInvoke(...)` | 强类型委托调用 |
| `Expression.Compile()` | 预编译静态委托 / 解释执行 |
| `MakeGenericType` (动态类型) | 源生成器 / 预注册有限组合 |
| `DynamicObject` | 强类型包装器 |
| `BinaryFormatter` | `System.Text.Json` / 自定义 |
| `XmlSerializer(Type)` | `[XmlSerializerGenerator]` 源生成器 |
| `DefaultJsonTypeInfoResolver` | `JsonSerializerContext`(源生成器)|
| `[DllImport]` | `[LibraryImport]`(.NET 7+)|
| `InvokeMember` | 反射(带注解)或强类型 |
| `CodeDomProvider` | 不支持 AOT,标记 `[RequiresDynamicCode]` |
---
*本报告基于源码静态分析生成,未覆盖条件编译分支内的所有变体。建议在启用 `<PublishAot>true</PublishAot>` 和 `<IsTrimmable>true</IsTrimmable>` 的目标项目上实际编译以获取完整的 IL2026/IL3050 警告列表。*
|