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

相关推荐
缺点内向1 天前
C#: 告别繁琐!轻松移除Word文档中的文本与图片水印
c#·自动化·word·.net
2501_930707781 天前
使用 C# .NET 从 PowerPoint 演示文稿中提取背景图片
c#·powerpoint·.net
向上的车轮1 天前
为什么.NET(C#)转 Java 开发时常常在“吐槽”Java:checked exception
java·c#·.net
波波0071 天前
每日一题:.NET 的 GC是如何分代工作的?
算法·.net·gc
波波0072 天前
每日一题:中间件是如何工作的?
中间件·.net·面试题
无风听海2 天前
.NET 10之可空引用类型
数据结构·.net
码云数智-园园3 天前
基于 JSON 配置的 .NET 桌面应用自动更新实现指南
.net
无风听海3 天前
.NET 10 之dotnet run的功能
.net
岩屿3 天前
Ubuntu下安装Docker并部署.NET API(二)
运维·docker·容器·.net
码云数智-大飞3 天前
.NET 中高效实现 List 集合去重的多种方法详解
.net