.NET AOT 与裁剪进阶
大约 16 分钟约 4916 字
.NET AOT 与裁剪进阶
简介
.NET Native AOT(Ahead-of-Time)编译将 C# 代码直接编译为本地机器码,无需 JIT 运行时,实现快速启动和小内存占用。裁剪(Trimming)在发布时移除未使用的代码,进一步减小二进制体积。本文将深入探讨 AOT 编译的裁剪模式、反射兼容性、源生成器适配、常见警告处理以及实战优化技巧。
特点
AOT 编译基础
项目配置
<!-- AOT 发布的项目文件配置 -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<!-- 启用 Native AOT -->
<PublishAot>true</PublishAot>
<!-- 裁剪模式 -->
<TrimMode>full</TrimMode>
<!-- AOT 特定选项 -->
<IlcOptimizationPreference>Speed</IlcOptimizationPreference>
<IlcGenerateStackTraceData>true</IlcGenerateStackTraceData>
<!-- 控制异常过滤器支持(增加体积但保留功能) -->
<TrimExceptionKeepILConditional>true</TrimExceptionKeepILConditional>
<!-- 根程序集(不会被裁剪的入口) -->
<TrimmerRootAssembly>MyApp</TrimmerRootAssembly>
</PropertyGroup>
</Project>
<!-- 不同的发布配置 -->
<!-- 发布命令:
dotnet publish -c Release -r win-x64 # Windows x64
dotnet publish -c Release -r linux-x64 # Linux x64
dotnet publish -c Release -r linux-arm64 # Linux ARM64
dotnet publish -c Release -r osx-arm64 # macOS ARM64
-->AOT 与 JIT 的区别
/// <summary>
/// AOT 和 JIT 编译的区别示意
/// </summary>
public class AotVsJit
{
/// <summary>
/// JIT 模式(默认):
/// - 运行时 JIT 编译器将 IL 转为机器码
/// - 支持动态代码生成(Reflection.Emit)
/// - 支持运行时类型发现
/// - 启动较慢,但可以使用 Profile-Guided Optimization
/// </summary>
/// <summary>
/// AOT 模式:
/// - 编译时直接生成原生机器码
/// - 不需要 JIT 运行时
/// - 启动极快(毫秒级)
/// - 内存占用小
/// - 不支持 Reflection.Emit
/// - 对反射有限制
/// </summary>
public static void Demonstrate()
{
// 检测当前是否运行在 AOT 模式
bool isAot = System.Runtime.CompilerServices.RuntimeFeature
.IsDynamicCodeSupported == false;
Console.WriteLine($"AOT 模式: {isAot}");
if (isAot)
{
Console.WriteLine("运行在 Native AOT 模式");
Console.WriteLine($"进程内存: {GC.GetGCMemoryInfo().MemoryLoadBytes}");
}
}
}裁剪模式详解
Partial vs Full 裁剪
using System.Diagnostics.CodeAnalysis;
/// <summary>
/// 裁剪模式对比与选择
/// </summary>
public class TrimmingModes
{
/// <summary>
/// partial 模式(.NET 7 默认):
/// - 只裁剪标记为 trimmable 的程序集
/// - 未标记的程序集保持完整
/// - 安全性高,但体积减少有限
/// </summary>
/// <summary>
/// full 模式(.NET 8+ 推荐):
/// - 裁剪所有程序集
/// - 需要正确添加裁剪注解
/// - 体积减少最大
/// </summary>
/// <summary>
/// 使用 DynamicallyAccessedMembers 标记需要的成员
/// </summary>
public class TrimmedService
{
// 标记:此类型通过反射访问时,保留所有公共方法
[DynamicDependency(DynamicallyAccessedMemberTypes.PublicMethods, typeof(TrimmedService))]
public void MethodKeptByAnnotation() { }
// 未标记的方法在 full 模式下可能被裁剪
public void MethodMayBeTrimmed() { }
/// <summary>
/// 通过 DynamicallyAccessedMembers 在类型级别保留
/// </summary>
public static void ProcessWithReflection(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
Type type)
{
// 编译器知道 type 的公共属性会被反射访问
var properties = type.GetProperties();
foreach (var prop in properties)
{
Console.WriteLine(prop.Name);
}
}
}
/// <summary>
/// 使用 DynamicDependency 保留特定方法
/// </summary>
public class DependencyExample
{
[DynamicDependency("MethodNeededAtRuntime")]
public void MethodNeededAtRuntime() { }
[DynamicDependency(nameof(GenericMethod))]
public void GenericMethod<T>(T value) { }
// 保留所有实现了特定接口的方法
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(IMyInterface))]
public interface IMyInterface
{
void Execute();
}
}
}裁剪注解详解
using System.Diagnostics.CodeAnalysis;
/// <summary>
/// 裁剪相关的特性注解大全
/// </summary>
public class TrimmingAnnotations
{
/// <summary>
/// DynamicallyAccessedMembers — 标记类型上需要保留的成员
/// </summary>
public class MemberRetention
{
// 参数级别:告诉裁剪器此 Type 参数的哪些成员需要保留
public static void CreateInstance(
[DynamicallyAccessedMembers(
DynamicallyAccessedMemberTypes.PublicConstructors |
DynamicallyAccessedMemberTypes.PublicProperties)]
Type type)
{
var instance = Activator.CreateInstance(type);
foreach (var prop in type.GetProperties())
{
Console.WriteLine($"{prop.Name} = {prop.GetValue(instance)}");
}
}
// 返回值级别:告知返回类型的成员会被访问
[return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]
public static Type GetServiceType(string typeName)
{
return Type.GetType(typeName)!;
}
// 字段/属性级别
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.NonPublicMethods)]
private Type _typeRequiringPrivateMethods = typeof(object);
}
/// <summary>
/// DynamicDependency — 保留特定成员的依赖
/// </summary>
public class DependencyExamples
{
// 保留特定方法的依赖
[DynamicDependency("ProcessData", typeof(DataProcessor))]
public void CallProcessData()
{
// 通过反射调用
typeof(DataProcessor).GetMethod("ProcessData")?.Invoke(null, null);
}
}
/// <summary>
/// RequiresUnreferencedCode — 标记不兼容裁剪的方法
/// </summary>
public class UnreferencedCodeExamples
{
[RequiresUnreferencedCode("此方法使用反射序列化,不兼容裁剪")]
public static string Serialize(object obj)
{
// 使用反射的序列化,可能裁剪掉属性
var type = obj.GetType();
var properties = type.GetProperties();
var parts = new List<string>();
foreach (var prop in properties)
{
parts.Add($"\"{prop.Name}\":\"{prop.GetValue(obj)}\"");
}
return "{" + string.Join(",", parts) + "}";
}
// 调用方可以抑制警告
[UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode")]
public static void CallSerialize()
{
var json = Serialize(new { Name = "Test", Value = 42 });
Console.WriteLine(json);
}
}
/// <summary>
/// RequiresDynamicCode — 标记需要 JIT 的方法
/// </summary>
public class DynamicCodeExamples
{
[RequiresDynamicCode("Expression.Compile 需要 JIT")]
public static Func<T, bool> CreatePredicate<T>(string propertyName, object value)
{
var param = System.Linq.Expressions.Expression.Parameter(typeof(T));
var prop = System.Linq.Expressions.Expression.Property(param, propertyName);
var constant = System.Linq.Expressions.Expression.Constant(value);
var equal = System.Linq.Expressions.Expression.Equal(prop, constant);
return System.Linq.Expressions.Expression.Lambda<Func<T, bool>>(equal, param).Compile();
}
}
}
public class DataProcessor
{
public static void ProcessData() { }
}反射在 AOT 中的处理
常见反射场景的 AOT 兼容方案
using System.Diagnostics.CodeAnalysis;
/// <summary>
/// 反射在 AOT 下的替代方案
/// </summary>
public class ReflectionInAot
{
/// <summary>
/// 问题场景1:通过类型名称创建实例
/// AOT 不兼容方式
/// </summary>
public object CreateInstanceBad(string typeName)
{
var type = Type.GetType(typeName);
return Activator.CreateInstance(type!); // AOT 下可能返回 null
}
/// <summary>
/// AOT 兼容方式:使用注册表模式
/// </summary>
public interface IPlugin
{
string Name { get; }
void Execute();
}
// 使用源生成器或手动注册
public class PluginRegistry
{
private readonly Dictionary<string, Func<IPlugin>> _factories = new();
public void Register(string name, Func<IPlugin> factory)
{
_factories[name] = factory;
}
public IPlugin? Create(string name)
{
return _factories.TryGetValue(name, out var factory) ? factory() : null;
}
}
// 手动注册(AOT 安全)
public class PluginRegistration
{
public static void RegisterAll(PluginRegistry registry)
{
registry.Register("email", () => new EmailPlugin());
registry.Register("sms", () => new SmsPlugin());
registry.Register("push", () => new PushPlugin());
}
}
/// <summary>
/// 问题场景2:通过反射设置属性值
/// </summary>
public class ConfigurationBinder
{
// AOT 不兼容:使用反射绑定配置
[RequiresUnreferencedCode("使用反射绑定配置")]
public static T BindReflection<T>(Dictionary<string, string> config) where T : new()
{
var result = new T();
var type = typeof(T);
foreach (var kvp in config)
{
var prop = type.GetProperty(kvp.Key);
prop?.SetValue(result, Convert.ChangeType(kvp.Value, prop.PropertyType));
}
return result;
}
// AOT 兼容:使用源生成器或委托
public static T BindAot<T>(Dictionary<string, string> config, Action<T, string, string> setter)
where T : new()
{
var result = new T();
foreach (var kvp in config)
{
setter(result, kvp.Key, kvp.Value);
}
return result;
}
}
}
public class EmailPlugin : ReflectionInAot.IPlugin
{
public string Name => "email";
public void Execute() => Console.WriteLine("发送邮件");
}
public class SmsPlugin : ReflectionInAot.IPlugin
{
public string Name => "sms";
public void Execute() => Console.WriteLine("发送短信");
}
public class PushPlugin : ReflectionInAot.IPlugin
{
public string Name => "push";
public void Execute() => Console.WriteLine("推送通知");
}源生成器适配 AOT
用源生成器替代反射
using System.Diagnostics.CodeAnalysis;
/// <summary>
/// 源生成器模式替代运行时反射
/// 以下展示如何设计 AOT 友好的代码模式
/// </summary>
public class SourceGenerationPatterns
{
/// <summary>
/// 模式1:使用 GeneratedRegex 替代 Regex
/// </summary>
public partial class RegexExamples
{
// AOT 友好:编译时生成正则匹配代码
[System.Text.RegularExpressions.GeneratedRegex(@"^\d{4}-\d{2}-\d{2}$")]
private static partial System.Text.RegularExpressions.Regex DateRegex();
public static bool IsDate(string input) => DateRegex().IsMatch(input);
[System.Text.RegularExpressions.GeneratedRegex(@"^[\w.-]+@[\w.-]+\.\w+$")]
private static partial System.Text.RegularExpressions.Regex EmailRegex();
public static bool IsEmail(string input) => EmailRegex().IsMatch(input);
}
/// <summary>
/// 模式2:使用 LoggerMessage 替代运行时日志格式化
/// </summary>
public static partial class LogMessages
{
[LoggerMessage(Level = LogLevel.Information, Message = "处理请求 {RequestId} 耗时 {Elapsed}ms")]
public static partial void LogRequestProcessed(this ILogger logger, string requestId, long elapsed);
[LoggerMessage(Level = LogLevel.Error, Message = "处理失败: {Error}")]
public static partial void LogProcessingError(this ILogger logger, Exception ex, string error);
}
/// <summary>
/// 模式3:使用静态泛型代替反射类型查找
/// </summary>
public interface IAotHandler
{
string Handle(string input);
}
// 使用泛型约束代替反射
public class HandlerDispatcher
{
private readonly Dictionary<string, Func<IAotHandler>> _handlers = new();
public void Register<THandler>(string messageType) where THandler : IAotHandler, new()
{
_handlers[messageType] = () => new THandler();
}
public string Handle(string messageType, string input)
{
if (_handlers.TryGetValue(messageType, out var factory))
{
return factory().Handle(input);
}
throw new KeyNotFoundException($"未注册的处理程序: {messageType}");
}
}
}System.Text.Json 的 AOT 兼容
JsonSerializer 的源生成器模式
using System.Text.Json;
using System.Text.Json.Serialization;
/// <summary>
/// System.Text.Json 在 AOT 下的使用
/// </summary>
public class JsonAotExamples
{
/// <summary>
/// 步骤1:定义 JsonTypeInfo(由源生成器生成)
/// </summary>
[JsonSerializable(typeof(UserDto))]
[JsonSerializable(typeof(OrderDto))]
[JsonSerializable(typeof(List<UserDto>))]
[JsonSerializable(typeof(Dictionary<string, object>))]
public partial class MyJsonContext : JsonSerializerContext
{
}
/// <summary>
/// 步骤2:使用生成的上下文进行序列化
/// </summary>
public class JsonService
{
// AOT 友好的序列化
public string SerializeUser(UserDto user)
{
return JsonSerializer.Serialize(user, MyJsonContext.Default.UserDto);
}
// AOT 友好的反序列化
public UserDto? DeserializeUser(string json)
{
return JsonSerializer.Deserialize(json, MyJsonContext.Default.UserDto);
}
// 异步流式读取
public async Task<UserDto?> ReadFromStreamAsync(Stream stream)
{
return await JsonSerializer.DeserializeAsync(stream, MyJsonContext.Default.UserDto);
}
}
/// <summary>
/// 自定义转换器(AOT 兼容)
/// </summary>
public class DateTimeConverter : JsonConverter<DateTime>
{
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return DateTime.Parse(reader.GetString()!);
}
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString("yyyy-MM-ddTHH:mm:ssZ"));
}
}
/// <summary>
/// ASP.NET Core 中的配置
/// </summary>
public static class WebApplicationBuilderExtensions
{
public static WebApplicationBuilder ConfigureAotJson(this WebApplicationBuilder builder)
{
// 方式1:使用源生成的 JsonSerializerOptions
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolver = MyJsonContext.Default;
});
// 方式2:添加自定义转换器
options => options.Converters.Add(new DateTimeConverter());
return builder;
}
}
}
public class UserDto
{
public int Id { get; set; }
public string Name { get; set; } = "";
public string Email { get; set; } = "";
}
public class OrderDto
{
public int OrderId { get; set; }
public decimal Amount { get; set; }
public DateTime CreatedAt { get; set; }
public List<UserDto> Items { get; set; } = new();
}JsonSchema 与多态序列化
using System.Text.Json;
using System.Text.Json.Serialization;
/// <summary>
/// 多态序列化在 AOT 下的处理
/// </summary>
public class PolymorphicJsonAot
{
/// <summary>
/// 使用 JsonPolymorphic 和 JsonDerivedType 特性
/// </summary>
[JsonPolymorphic(TypeDiscriminatorPropertyName = "$type")]
[JsonDerivedType(typeof(Dog), "dog")]
[JsonDerivedType(typeof(Cat), "cat")]
[JsonDerivedType(typeof(Bird), "bird")]
public abstract class Animal
{
public string Name { get; set; } = "";
public int Age { get; set; }
}
public class Dog : Animal
{
public string Breed { get; set; } = "";
}
public class Cat : Animal
{
public bool Indoor { get; set; }
}
public class Bird : Animal
{
public bool CanFly { get; set; }
}
/// <summary>
/// 注册多态类型到源生成上下文
/// </summary>
[JsonSerializable(typeof(Animal))]
[JsonSerializable(typeof(Dog))]
[JsonSerializable(typeof(Cat))]
[JsonSerializable(typeof(Bird))]
[JsonSerializable(typeof(List<Animal>))]
public partial class AnimalJsonContext : JsonSerializerContext { }
/// <summary>
/// 多态序列化/反序列化
/// </summary>
public void PolymorphicExample()
{
var animals = new List<Animal>
{
new Dog { Name = "Buddy", Breed = "Golden" },
new Cat { Name = "Whiskers", Indoor = true },
new Bird { Name = "Tweety", CanFly = true }
};
// 序列化(自动包含 $type 鉴别器)
string json = JsonSerializer.Serialize(animals, AnimalJsonContext.Default.ListAnimal);
// 反序列化(自动还原为具体类型)
var deserialized = JsonSerializer.Deserialize(json, AnimalJsonContext.Default.ListAnimal);
foreach (var animal in deserialized!)
{
Console.WriteLine($"{animal.GetType().Name}: {animal.Name}");
}
}
}HttpClient 的 AOT 适配
使用源生成的 HttpClient
using System.Net.Http.Json;
/// <summary>
/// HttpClient 在 AOT 下的正确使用方式
/// </summary>
public class HttpClientAot
{
/// <summary>
/// 方式1:使用 AddHttpClient + 源生成的 JSON 上下文
/// </summary>
public class UserService
{
private readonly HttpClient _httpClient;
public UserService(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<UserDto?> GetUserAsync(int id)
{
// AOT 友好:使用 JsonTypeInfo
return await _httpClient.GetFromJsonAsync(
$"/api/users/{id}",
JsonAotExamples.MyJsonContext.Default.UserDto);
}
public async Task<UserDto?> CreateUserAsync(UserDto user)
{
var response = await _httpClient.PostAsJsonAsync(
"/api/users",
user,
JsonAotExamples.MyJsonContext.Default.UserDto);
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync(
JsonAotExamples.MyJsonContext.Default.UserDto);
}
}
/// <summary>
/// 方式2:使用 IHttpClientFactory + Resilience Pipeline
/// </summary>
public static class HttpClientRegistration
{
public static IServiceCollection AddAotHttpClient(this IServiceCollection services)
{
services.AddHttpClient("api", client =>
{
client.BaseAddress = new Uri("https://api.example.com");
client.DefaultRequestHeaders.Add("Accept", "application/json");
});
return services;
}
}
/// <summary>
/// 注意事项:
/// - 避免使用 HttpClientJsonExtensions 的非泛型重载
/// - 不要使用 dynamic 类型作为请求/响应
/// - 使用 HttpClientHandler 而非 WinHttpHandler(跨平台)
/// </summary>
}EF Core 的 AOT 支持
EF Core AOT 配置
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
/// <summary>
/// EF Core 在 .NET 9+ 的 AOT 支持
/// </summary>
public class EFCOREAOT
{
/// <summary>
/// DbContext 配置(AOT 兼容)
/// </summary>
public class AppDbContext : DbContext
{
public DbSet<UserEntity> Users => Set<UserEntity>();
public DbSet<OrderEntity> Orders => Set<OrderEntity>();
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite("Data Source=app.db");
// AOT 模式下必须启用编译查询缓存
optionsBuilder.EnableThreadSafetyChecks(false);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// 使用 Fluent API 配置(AOT 安全)
modelBuilder.Entity<UserEntity>(entity =>
{
entity.ToTable("users");
entity.HasKey(e => e.Id);
entity.Property(e => e.Name).HasMaxLength(100).IsRequired();
entity.Property(e => e.Email).HasMaxLength(200);
entity.HasIndex(e => e.Email).IsUnique();
});
modelBuilder.Entity<OrderEntity>(entity =>
{
entity.ToTable("orders");
entity.HasKey(e => e.Id);
entity.Property(e => e.Amount).HasColumnType("decimal(18,2)");
entity.HasOne(e => e.User)
.WithMany(u => u.Orders)
.HasForeignKey(e => e.UserId);
});
}
}
/// <summary>
/// 使用编译查询(AOT 友好)
/// </summary>
public static class CompiledQueries
{
// 预编译查询,避免运行时表达式解析
private static readonly Func<AppDbContext, int, UserEntity?> _getUserById =
EF.CompileQuery((AppDbContext db, int id) =>
db.Users.FirstOrDefault(u => u.Id == id));
private static readonly Func<AppDbContext, string, List<UserEntity>> _searchUsers =
EF.CompileQuery((AppDbContext db, string keyword) =>
db.Users.Where(u => u.Name.Contains(keyword)).ToList());
public static UserEntity? GetUser(AppDbContext db, int id)
{
return _getUserById(db, id);
}
public static List<UserEntity> Search(AppDbContext db, string keyword)
{
return _searchUsers(db, keyword);
}
}
/// <summary>
/// 注意:EF Core AOT 的限制
/// - 不支持 Lazy Loading(需要代理生成)
/// - 不支持 Add-Migration 命令的运行时执行
/// - 部分第三方 Provider 可能不支持
/// - 建议在独立工具项目中管理 Migration
/// </summary>
}
public class UserEntity
{
public int Id { get; set; }
public string Name { get; set; } = "";
public string Email { get; set; } = "";
public List<OrderEntity> Orders { get; set; } = new();
}
public class OrderEntity
{
public int Id { get; set; }
public decimal Amount { get; set; }
public int UserId { get; set; }
public UserEntity User { get; set; } = null!;
}NativeLibrary 解析
P/Invoke 与 Native Library 的 AOT 处理
using System.Runtime.InteropServices;
/// <summary>
/// Native Library 在 AOT 中的处理
/// </summary>
public static class NativeLibraryAot
{
/// <summary>
/// 方式1:使用 DllImport(最简单,AOT 天然支持)
/// </summary>
public static class NativeMethods
{
[DllImport("kernel32", SetLastError = true)]
public static extern nint GetProcAddress(nint module, string name);
[DllImport("libc", SetLastError = true)]
public static extern int getpid();
}
/// <summary>
/// 方式2:使用 LibraryImport(.NET 7+,AOT 优化的 P/Invoke)
/// 编译时生成 marshal 代码,而非运行时
/// </summary>
public static partial class OptimizedNativeMethods
{
// LibraryImport 在编译时生成 marshal 代码
[LibraryImport("kernel32", StringMarshalling = StringMarshalling.Utf16)]
public static partial nint GetProcAddress(nint module, string name);
[LibraryImport("libc")]
public static partial int getpid();
[LibraryImport("mylib", StringMarshalling = StringMarshalling.Utf8)]
public static partial int ProcessString(string input);
}
/// <summary>
/// 方式3:自定义 NativeLibrary 解析
/// </summary>
public static void ConfigureNativeResolver()
{
// 注册自定义解析逻辑
NativeLibrary.SetDllImportResolver(
typeof(NativeLibraryAot).Assembly,
(name, assembly, searchPath) =>
{
// 自定义搜索逻辑
if (name == "mylib")
{
string path = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? "mylib.dll"
: RuntimeInformation.IsOSPlatform(OSPlatform.Linux)
? "libmylib.so"
: "libmylib.dylib";
return NativeLibrary.Load(path);
}
return nint.Zero; // 使用默认解析
});
}
}调试裁剪问题
识别和修复裁剪警告
using System.Diagnostics.CodeAnalysis;
/// <summary>
/// 常见裁剪警告及修复方法
/// </summary>
public class TrimmingWarnings
{
/// <summary>
/// 警告 IL2026: RequiresUnreferencedCode
/// 原因:调用了标记为 RequiresUnreferencedCode 的方法
/// </summary>
public class Warning_IL2026
{
// 修复方式1:标记调用方也不兼容裁剪
[RequiresUnreferencedCode("使用 Newtonsoft.Json 反射序列化")]
public static string NewtonsoftSerialize(object obj)
{
return Newtonsoft.Json.JsonConvert.SerializeObject(obj);
}
// 修复方式2:抑制警告(确认安全后)
[UnconditionalSuppressMessage("Trimming", "IL2026")]
public static string SafeSerialize(object obj)
{
// 确认输入类型已知且不会被裁剪
return Newtonsoft.Json.JsonConvert.SerializeObject(obj);
}
}
/// <summary>
/// 警告 IL2072: DynamicallyAccessedMembers
/// 原因:Type 参数缺少注解
/// </summary>
public class Warning_IL2072
{
// 问题代码
public static object? CreateBad(Type type)
{
return Activator.CreateInstance(type); // IL2072
}
// 修复:添加注解
public static object? CreateGood(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
Type type)
{
return Activator.CreateInstance(type);
}
}
/// <summary>
/// 警告 IL3050: RequiresDynamicCode
/// 原因:使用了需要 JIT 的功能(如 Expression.Compile)
/// </summary>
public class Warning_IL3050
{
// 修复:使用源生成器替代 Expression
[RequiresDynamicCode("Expression.Compile 需要 JIT")]
public static Func<T, bool> CreatePredicateOld<T>(string property, object value)
{
// Expression 树编译需要 JIT
var param = System.Linq.Expressions.Expression.Parameter(typeof(T));
var prop = System.Linq.Expressions.Expression.Property(param, property);
var constant = System.Linq.Expressions.Expression.Constant(value);
var equal = System.Linq.Expressions.Expression.Equal(prop, constant);
return System.Linq.Expressions.Expression.Lambda<Func<T, bool>>(equal, param).Compile();
}
// AOT 友好:使用手写比较逻辑
public static Func<T, bool> CreatePredicateAot<T>(Func<T, object> getter, object value)
{
return obj => Equals(getter(obj), value);
}
}
/// <summary>
/// 裁剪分析工具
/// </summary>
public static void AnalyzeTrimming()
{
// 在项目文件中启用详细裁剪警告
// <TrimmerSingleWarn>false</TrimmerSingleWarn>
// <NoWarn>IL####</NoWarn> // 抑制特定警告
// 发布时生成裁剪报告
// dotnet publish -c Release -r win-x64 /p:PublishAot=true /p:TrimmerSingleWarn=false
// 查看被裁剪的内容
// 在 obj/Release/net9.0/win-x64/publish/ 目录找到裁剪日志
}
}体积优化技巧
减小 AOT 二进制大小
/// <summary>
/// AOT 体积优化策略
/// </summary>
public class SizeOptimization
{
/// <summary>
/// 优化1:控制优化方向
/// </summary>
/// 项目文件:
/// <IlcOptimizationPreference>Size</IlcOptimizationPreference>
/// 体积优先(可能牺牲少量性能)
/// <IlcOptimizationPreference>Speed</IlcOptimizationPreference>
/// 性能优先(体积更大)
/// <summary>
/// 优化2:移除全球化支持(使用 invariant 模式)
/// </summary>
/// <InvariantGlobalization>true</InvariantGlobalization>
/// 可减少约 1-2MB(去掉 ICU 数据)
/// <summary>
/// 优化3:移除调试信息
/// </summary>
/// <IlcGenerateStackTraceData>false</IlcGenerateStackTraceData>
/// <StripSymbols>true</StripSymbols>
/// <summary>
/// 优化4:使用 ASP.NET Core 的 AOT 兼容 API
/// </summary>
public static WebApplication CreateMinimalApi()
{
var builder = WebApplication.CreateBuilder();
// AOT 兼容的 Minimal API
var app = builder.Build();
// 使用强类型参数绑定(AOT 安全)
app.MapPost("/users", (UserDto user) =>
{
return Results.Ok(user);
});
// 避免使用 dynamic 或匿名类型
// 错误:app.MapGet("/api", () => new { Id = 1, Name = "test" });
// 正确:
app.MapGet("/api", () => new UserDto { Id = 1, Name = "test" });
return app;
}
/// <summary>
/// 优化5:使用 ComHost 减小 ASP.NET Core 体积
/// </summary>
/// <UseComHost>true</UseComHost>
/// <summary>
/// 优化6:条件编译移除调试代码
/// </summary>
[System.Diagnostics.Conditional("DEBUG")]
public static void DebugOnlyCode()
{
// 这段代码在 Release AOT 中不会包含
Console.WriteLine("调试信息");
}
}
/// <summary>
/// 典型 AOT 应用体积参考 (.NET 9)
/// </summary>
public class AotSizeReference
{
/// 控制台 Hello World:
/// 默认: ~8MB
/// 裁剪 + Size 优化: ~3-4MB
/// + InvariantGlobalization: ~2-3MB
///
/// ASP.NET Core Minimal API:
/// 默认: ~20MB
/// 裁剪 + Size 优化: ~12-15MB
/// + InvariantGlobalization: ~10-13MB
///
/// 对比:
/// JIT 框架依赖: ~1MB (需要运行时)
/// JIT 自包含: ~70MB+
/// Native AOT: ~3-20MB
}性能注意事项
- 启动时间:AOT 的最大优势,适合 Serverless、CLI 工具、微服务冷启动
- 吞吐量:某些场景 AOT 可能比 JIT 慢(缺少 PGO),需要基准测试验证
- 内存占用:AOT 不需要 JIT 编译器的内存,通常减少 20-30%
- 反射开销:需要通过注解保留的类型会增加二进制体积
- 调试体验:AOT 编译后的调试信息较少,开发阶段建议使用 JIT
- 平台支持:AOT 支持 Windows/Linux/macOS,但不支持 WASM(使用 Blazor Wasm 的 AOT 是不同方案)
总结
.NET Native AOT 提供了将 C# 应用编译为原生代码的能力,结合裁剪技术可以显著减小部署体积。关键挑战在于处理反射和动态代码的兼容性,通过源生成器和裁剪注解可以有效解决。建议从控制台应用和 Minimal API 开始尝试 AOT,逐步积累经验后再应用于更复杂的场景。
关键知识点
- PublishAot 启用 AOT 编译,TrimMode 控制裁剪程度
- DynamicallyAccessedMembers 是最核心的裁剪注解
- RequiresUnreferencedCode/RequiresDynamicCode 标记不兼容的方法
- System.Text.Json 必须使用 JsonSerializerContext 源生成模式
- LibraryImport 替代 DllImport 实现 AOT 优化的 P/Invoke
- EF Core 在 .NET 9+ 支持有限 AOT,推荐使用编译查询
- GeneratedRegex 替代运行时正则编译
- LoggerMessage 替代运行时日志格式化
常见误区
| 误区 | 正确理解 |
|---|---|
| AOT 应用不需要任何配置 | 需要正确处理反射、JSON 序列化等动态特性 |
| 裁剪只是去掉未使用的代码 | 裁剪还会移除类型元数据、泛型实例化信息等 |
| 所有 NuGet 包都兼容 AOT | 很多旧包依赖反射,需要检查 AOT 兼容性 |
| AOT 一定比 JIT 快 | 启动更快,但稳态性能可能不如 PGO 优化的 JIT |
| 裁剪警告可以全部抑制 | 抑制警告可能导致运行时 MissingMethodException |
| AOT 不支持 ASP.NET Core | .NET 8+ 的 Minimal API 完全支持 AOT |
进阶路线
- 入门阶段:掌握 AOT 发布命令、TrimMode 配置、基本警告处理
- 进阶阶段:理解裁剪注解、源生成器替代反射
- 高级阶段:System.Text.Json 源生成、HttpClient AOT、EF Core AOT
- 专家阶段:自定义裁剪策略、NativeLibrary 集成、性能调优
- 架构级别:全 AOT 微服务架构、Lambda/Serverless AOT 部署
适用场景
- AWS Lambda / Azure Functions 冷启动优化
- 嵌入式设备和 IoT 应用
- CLI 命令行工具(需要快速响应)
- 微服务架构中需要快速扩缩容的场景
- 对部署体积有严格要求的场景
- 游戏服务器(需要低延迟启动)
落地建议
- 从独立控制台工具开始尝试 AOT,积累经验
- 建立裁剪警告的 CI 检查,禁止使用 UnconditionalSuppressMessage 掩盖问题
- 将 Newtonsoft.Json 迁移到 System.Text.Json(源生成模式)
- 评估第三方依赖的 AOT 兼容性,建立兼容包白名单
- 在开发阶段使用 JIT,仅在发布时启用 AOT
排错清单
复盘问题
- 项目中哪些反射调用最难以迁移到源生成器?
- 如何建立 AOT 兼容性的自动化测试?
- AOT 编译后的体积是否满足部署要求?哪些优化还有空间?
- 第三方依赖的 AOT 兼容性如何系统性评估?
- 是否需要在 CI 中同时测试 JIT 和 AOT 两种模式?
