.Net 中的 ActivatorUtilitiesConstructor 特性
[ActivatorUtilitiesConstructor] 是 .NET 依赖注入中的一个特性,用于指导 Microsoft.Extensions.DependencyInjection(MSDI)在类型有多个构造函数时,选择哪个构造函数进行实例化。
主要用途
1. 解决构造函数选择歧义
当一个类有多个构造函数时,MSDI 默认选择 参数最多且都能从容器中解析 的构造函数。但有时这会导致问题:
csharp
public class MyService
{
// 默认情况下,DI 会选择这个构造函数(参数最多)
public MyService(IService1 s1, IService2 s2, string configValue)
{
// configValue 无法从容器解析,会抛出异常!
}
// 实际上我们想用这个
public MyService(IService1 s1, IService2 s2)
{
// 只有可解析的参数
}
}
2. 明确指定构造函数
使用 [ActivatorUtilitiesConstructor] 明确告诉 DI 使用哪个:
csharp
public class MyService
{
public MyService(IService1 s1, IService2 s2, string configValue)
{
// 这个不会被 DI 使用
}
[ActivatorUtilitiesConstructor]
public MyService(IService1 s1, IService2 s2)
{
// DI 会优先使用这个构造函数
}
}
工作原理
- 标记优先级:标记的构造函数会被优先考虑
- 兼容性检查:只考虑标记的构造函数能否从容器解析所有参数
- 回退机制:如果标记的构造函数参数无法全部解析,会尝试其他构造函数
常见场景
场景1:有可选参数时
csharp
public class ReportService
{
private readonly ILogger _logger;
private readonly string _format;
public ReportService(ILogger<ReportService> logger)
{
_logger = logger;
_format = "Default";
}
[ActivatorUtilitiesConstructor]
public ReportService(ILogger<ReportService> logger, IOptions<ReportOptions> options)
{
_logger = logger;
_format = options.Value.Format;
}
}
场景2:第三方库扩展
csharp
// 扩展第三方库的类
public class ExtendedThirdPartyService : ThirdPartyService
{
// 第三方库可能没有无参构造函数
public ExtendedThirdPartyService() : base("default")
{
}
[ActivatorUtilitiesConstructor]
public ExtendedThirdPartyService(IConfiguration config)
: base(config.GetValue<string>("ApiKey"))
{
}
}
使用注意事项
1. 仅用于 ActivatorUtilities.CreateInstance
csharp
// 这个特性主要影响以下方法:
var instance = ActivatorUtilities.CreateInstance<MyService>(serviceProvider);
var instance = ActivatorUtilities.CreateInstance(serviceProvider, typeof(MyService));
2. 与直接容器解析的区别
csharp
// 使用特性 - 受 [ActivatorUtilitiesConstructor] 影响
services.AddTransient<MyService>();
var service = serviceProvider.GetService<MyService>();
// 直接注册实例工厂 - 不使用该特性
services.AddTransient(sp => new MyService("hardcoded"));
3. 多个标记会报错
csharp
public class BadExample
{
[ActivatorUtilitiesConstructor]
public BadExample(IService1 s1) { }
[ActivatorUtilitiesConstructor] // ❌ 运行时错误:多个标记
public BadExample(IService2 s2) { }
}
实际示例
csharp
public class PaymentProcessor
{
private readonly IPaymentGateway _gateway;
private readonly bool _useSandbox;
// 用于测试或特定场景
public PaymentProcessor(IPaymentGateway gateway)
{
_gateway = gateway;
_useSandbox = false;
}
// 生产环境使用 - 从配置读取
[ActivatorUtilitiesConstructor]
public PaymentProcessor(IPaymentGateway gateway, IConfiguration config)
{
_gateway = gateway;
_useSandbox = config.GetValue<bool>("Payment:UseSandbox");
}
}
// 注册
services.AddScoped<IPaymentGateway, StripeGateway>();
services.AddScoped<PaymentProcessor>();
// 使用时,DI 会自动选择带 [ActivatorUtilitiesConstructor] 的构造函数
替代方案
如果不想使用特性,也可以:
- 使用工厂方法注册:
csharp
services.AddScoped(sp =>
new MyService(
sp.GetRequiredService<IService1>(),
sp.GetRequiredService<IService2>()
));
- 简化设计(推荐):尽量保持单个构造函数,使用 Options 模式处理配置。
总结
[ActivatorUtilitiesConstructor] 是一个解决构造函数选择问题的工具 ,但在良好设计的应用中应该很少需要。优先考虑通过单一构造函数 和 Options 模式来简化设计,这会使代码更清晰且易于测试。