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

相关推荐
无风听海24 分钟前
.NET 10之可空引用类型
数据结构·.net
码云数智-园园1 小时前
基于 JSON 配置的 .NET 桌面应用自动更新实现指南
.net
无风听海1 小时前
.NET 10 之dotnet run的功能
.net
岩屿1 小时前
Ubuntu下安装Docker并部署.NET API(二)
运维·docker·容器·.net
码云数智-大飞1 小时前
.NET 中高效实现 List 集合去重的多种方法详解
.net
easyboot1 小时前
使用tinyply.net保存ply格式点云
.net
张人玉1 小时前
WPF 多语言实现完整笔记(.NET 4.7.2)
笔记·.net·wpf·多语言实现·多语言适配
波波00721 小时前
Native AOT 能改变什么?.NET 预编译技术深度剖析
开发语言·.net
Crazy Struggle2 天前
.NET 中如何快速实现 List 集合去重?
c#·.net
极客智造2 天前
ImageSharp 实战应用指南:.NET 跨平台图像处理落地实践
图像处理·.net