Activator.CreateInstance 是性能瓶颈,因其绕过 JIT 内联、触发安全检查且无法 AOT 优化;高频场景应改用 Activator.CreateInstance<T>()、缓存 Expression 委托或 Source Generator 预生成代码。

为什么 Activator.CreateInstance 是性能瓶颈
反射创建对象(比如用 Activator.CreateInstance(typeof(T)))在 .NET 中开销大,不只是因为动态解析类型,更关键的是每次调用都绕过 JIT 的内联优化、触发安全检查、且无法被 AOT 编译器提前处理。尤其在高频路径(如 Web API 模型绑定、序列化循环)中,它会成为 CPU 热点。
实操建议:
- 用
Expression.Lambda编译一次、缓存委托,比反复反射快 5–10 倍;但注意表达式树编译本身有成本,必须复用Func<object>委托实例,不能每次重编译 - .NET 6+ 推荐直接用
Activator.CreateInstance<T>()—— 这是泛型重载,底层走的是 JIT 内联路径,不经过反射引擎,性能接近 new T() - 避免对非 public 构造函数使用反射创建;若必须,改用
FormatterServices.GetUninitializedObject(仅适用于无构造逻辑的 DTO),但要清楚它跳过字段初始化,可能引发 NRE
用 Source Generator 替代运行时反射获取属性元数据
很多框架(如 JSON 序列化、ORM 映射)依赖 typeof(T).GetProperties() 获取字段列表,这在启动时就执行一次还好,但如果在请求中反复调用(比如每个响应体都要查一遍 Person 的属性),就会累积 GC 压力和 CPU 时间。
实操建议:
- 为需要元数据的类型标记自定义特性(如
[GenerateSerializer]),用 Source Generator 在编译期生成静态类Person_Metadata,里面包含PropertyNames数组和GetPropertyGetter<T>静态方法 - Generator 输出代码里不要写
typeof(T).GetProperty(...)—— 这又绕回反射;应直接硬编码字段访问,例如return instance.Name; - 注意 Generator 无法访问程序集引用的第三方类型(如 NuGet 包里的类),这类场景需 fallback 到反射,并加日志告警,避免静默降级
ISourceGenerator 生成工厂方法时如何避免泛型爆炸
如果为每个泛型组合(Repository<User>、Repository<Order>)都生成独立工厂,会导致输出代码量激增、编译变慢,甚至触发 Roslyn 的源生成大小限制(默认 10MB)。
实操建议:
- 按“可构造性”分组:只对
new()约束的泛型类型生成工厂;对where T : class且无 public 构造函数的,跳过生成,保留反射 fallback - 用
string.GetHashCode()对类型全名做轻量哈希,作为生成类名后缀(如RepositoryFactory_2a7f),避免类名冲突又不引入额外依赖 - 生成的工厂方法体内,优先用
new T();若类型含参数化构造函数,才生成带参数的Create(TArg1, TArg2)重载,并把参数类型写死进签名 —— 不要用params object[],那等于又回到反射调用
JSON 序列化中绕过 JsonSerializerOptions.TypeInfoResolver 的反射开销
.NET 7 的 TypeInfoResolver 默认用反射构建 JsonTypeInfo<T>,虽然比旧版快,但在微服务高频小对象场景(如 gRPC-JSON 转换),仍可观测到 JsonSerializer.GetTypeInfo 占用 3%–5% CPU 时间。
实操建议:
- 启用
JsonSerializerOptions.PreferUtf8Encoding = true并配合JsonSerializerContext预生成上下文(如MyJsonContext),让所有目标类型在编译期注册,运行时零反射 - 若使用 System.Text.Json + Source Generators,确保项目文件中设置了
<IsTrimmable>true</IsTrimmable>和<PublishTrimmed>true</PublishTrimmed>,否则生成的代码可能因 IL trimming 被误删 - 不要在
JsonSerializerOptions上设置Converters同时又用JsonSerializerContext—— 两者机制冲突,后者会忽略前者,且无编译警告
partial 类声明,或错判了一个构造函数可见性,都会导致运行时行为突变,且 IDE 不报错。


















