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

相关推荐
light blue bird4 小时前
主子端台二分法任务汇总组件
前端·数据库·.net·桌面端winform
rockey6276 小时前
基于AScript的python3脚本语言发布啦!
python·c#·.net·script·python3·eval·expression·function·动态脚本
TeamDev9 小时前
如何在 DotNetBrowser 中使用本地 AI 模型
前端·后端·.net
唐青枫12 小时前
内存为什么越来越高?C#.NET GC 详解:分代回收、LOH、终结器与性能优化实战
c#·.net
日落飞雪13 小时前
重塑 .NET 国际化工作流:时光恒Net多语言生成系统,让出海更简单
.net
日落飞雪13 小时前
从 .NET 11 到 ARM64:时光恒Net 系统 v1.06 进化全纪实,定义自动化翻译新高度!
.net·wpf开发·net国际化·net本地化·net多语言
rockey6271 天前
AScript之eval函数详解
c#·.net·script·eval·expression·动态脚本
周杰伦fans2 天前
AutoCAD .NET 二次开发:深入理解 EntityJig 的工作原理与正确实现
开发语言·.net
William_cl3 天前
【C#/.NET 进阶】ASP.NET 架构与最佳实践:DI 依赖注入(IoC 核心)从入门到避坑
c#·asp.net·.net
武藤一雄3 天前
WPF:MessageBox系统消息框
前端·microsoft·c#·.net·wpf