.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 模式来简化设计,这会使代码更清晰且易于测试。

相关推荐
专注VB编程开发20年5 小时前
MQTT傻瓜化调用组件,零成本学习.NET开发,上位机开发
学习·机器学习·.net
步步为营DotNet7 小时前
深度剖析.NET 中CancellationToken:精准控制异步操作的关键
java·前端·.net
一个帅气昵称啊7 小时前
.Net优雅实现AI知识库基于Ollama模型,Qdrant作为向量数据库实现RAG流程AI检索增强
人工智能·ai·.net·rag·qdrant
我是唐青枫17 小时前
C#.NET ConcurrentDictionary<TKey, TValue> 深度解析:原理与实践
c#·.net
Traced back1 天前
C#/.NET 常用控件、属性、方法和语句大全(或许全)
开发语言·c#·.net
步步为营DotNet1 天前
深度探究.NET中WeakReference:灵活内存管理的利器
java·jvm·.net
我是唐青枫2 天前
C#.NET ConcurrentBag<T> 设计原理与使用场景
c#·.net
该用户已不存在2 天前
不止是初始化,4个C# 构造函数解析与实例
后端·c#·.net
ou.cs2 天前
WPF TreeView 自动展开所有节点:附加行为(Attached Behavior)保姆级实现教程
c#·.net·wpf