.Net 中的 ActivatorUtilitiesConstructor 特性

.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. 标记优先级:标记的构造函数会被优先考虑
  2. 兼容性检查:只考虑标记的构造函数能否从容器解析所有参数
  3. 回退机制:如果标记的构造函数参数无法全部解析,会尝试其他构造函数

常见场景

场景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] 的构造函数

替代方案

如果不想使用特性,也可以:

  1. 使用工厂方法注册:
csharp 复制代码
services.AddScoped(sp => 
    new MyService(
        sp.GetRequiredService<IService1>(),
        sp.GetRequiredService<IService2>()
    ));
  1. 简化设计(推荐):尽量保持单个构造函数,使用 Options 模式处理配置。

总结

[ActivatorUtilitiesConstructor] 是一个解决构造函数选择问题的工具 ,但在良好设计的应用中应该很少需要。优先考虑通过单一构造函数 和 Options 模式来简化设计,这会使代码更清晰且易于测试。

相关推荐
xiangcns2 天前
PDMS PiplineTool(三十)一堆小优化+一个彩蛋功能
.net·二次开发·pdms
EatFan2 天前
MCP 从概念到落地:Java(Spring AI Alibaba)与 .NET 双栈接入实操对比
java·人工智能·后端·spring·.net·java后端·mcp
CSharp精选营3 天前
.NET 面试必问的 8 道 GC 题,建议收藏
.net·面试题·gc·c#面试题
驰骋工作流4 天前
C#/.NET 开源BPM工作流引擎6大常用对比选型分析驰骋BPM驰骋低代码低代码工作流引擎
开源·c#·.net
淡海水5 天前
02-02-原理篇-分代GC
算法·unity·c#·游戏引擎·.net·gc
1-1=05 天前
.NET 依赖注入 续
.net
淡海水5 天前
02-03-原理篇-大对象堆
unity·c#·游戏引擎·.net·gc
LateFrames5 天前
WPF 升级选型:用 .NET 8 还是 .NET 10
.net·wpf·.net8·.net10
淡海水6 天前
02-01-原理篇-Mark-Sweep与变种算法
算法·unity·c#·游戏引擎·.net·gc