You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为IServiceCollection实现严格模式避免依赖被意外覆盖

为IServiceCollection实现健壮的「严格注册模式」

核心目标

针对多人协作的.NET项目,实现IServiceCollection的「严格模式」:当任何AddXXX扩展方法尝试重复注册相同依赖时,立即抛出异常(或汇总异常),避免依赖被意外覆盖。

现有实现的潜在问题

你当前的实现已经覆盖了基础场景,但存在几个边缘情况需要优化:

  • 未考虑生命周期:相同服务类型、实现类型和ServiceKey,但生命周期不同(比如先注册Singleton,再注册Scoped),现有逻辑会判定为不重复,但实际业务中可能需要禁止这种情况
  • 工厂注册的服务无法正确判定:当使用工厂方式注册服务(如AddSingleton<IService>(sp => new Impl())),GetImplementationType()会返回null,现有逻辑会跳过重复检查
  • 未覆盖批量修改方法:比如AddRange、Insert等方法会绕过你的Add方法拦截,直接修改原始集合
  • 异常粒度问题:单个重复就抛出异常,无法一次性汇总所有重复注册问题

健壮实现方案

1. 完整包装IServiceCollection

实现一个完整的包装器类,拦截所有可能修改服务集合的方法,而不仅仅是单个Add方法。

public interface IStrictServiceCollection : IServiceCollection
{
    // 切换回普通模式
    IServiceCollection AsNormal();
}

public class StrictServiceCollection : IStrictServiceCollection
{
    private readonly IServiceCollection _innerCollection;
    private readonly bool _throwOnFirstViolation; // 控制是立即抛出还是汇总异常

    public StrictServiceCollection(IServiceCollection innerCollection, bool throwOnFirstViolation = true)
    {
        _innerCollection = innerCollection;
        _throwOnFirstViolation = throwOnFirstViolation;
    }

    // 实现IServiceCollection的所有成员,转发到_innerCollection,除了修改类方法
    public int Count => _innerCollection.Count;
    public bool IsReadOnly => _innerCollection.IsReadOnly;
    public ServiceDescriptor this[int index] { get => _innerCollection[index]; set => ValidateAndSet(index, value); }

    public IEnumerator<ServiceDescriptor> GetEnumerator() => _innerCollection.GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

    // 拦截Add方法
    public void Add(ServiceDescriptor item)
    {
        ValidateRegistration(item);
        _innerCollection.Add(item);
    }

    // 拦截AddRange方法
    public void AddRange(IEnumerable<ServiceDescriptor> items)
    {
        var violations = new List<InvalidOperationException>();
        foreach (var item in items)
        {
            try
            {
                ValidateRegistration(item);
            }
            catch (InvalidOperationException ex)
            {
                if (_throwOnFirstViolation)
                    throw;
                violations.Add(ex);
            }
        }

        if (violations.Any())
            throw new AggregateException("发现多个重复服务注册", violations);

        _innerCollection.AddRange(items);
    }

    // 拦截Insert方法
    public void Insert(int index, ServiceDescriptor item)
    {
        ValidateRegistration(item);
        _innerCollection.Insert(index, item);
    }

    // 拦截Set方法(索引器赋值)
    private void ValidateAndSet(int index, ServiceDescriptor value)
    {
        ValidateRegistration(value);
        _innerCollection[index] = value;
    }

    // 核心验证逻辑
    private void ValidateRegistration(ServiceDescriptor newDescriptor)
    {
        // 检查是否存在完全匹配的注册
        var existingMatches = _innerCollection.Where(s => 
            s.ServiceType == newDescriptor.ServiceType &&
            object.Equals(s.ServiceKey, newDescriptor.ServiceKey) &&
            s.Lifetime == newDescriptor.Lifetime &&
            AreImplementationsEquivalent(s, newDescriptor)
        ).ToList();

        if (existingMatches.Any())
        {
            throw new InvalidOperationException(
                $"服务{newDescriptor.ServiceType.FullName}(Key: {newDescriptor.ServiceKey}, 生命周期: {newDescriptor.Lifetime})已存在重复注册。" +
                $"现有实现:{GetImplementationDescription(existingMatches[0])},新实现:{GetImplementationDescription(newDescriptor)}");
        }
    }

    // 判断两个服务的实现是否等效
    private bool AreImplementationsEquivalent(ServiceDescriptor existing, ServiceDescriptor newDesc)
    {
        // 处理具体类型注册的情况
        if (existing.ImplementationType != null && newDesc.ImplementationType != null)
        {
            return existing.ImplementationType == newDesc.ImplementationType;
        }

        // 处理实例注册的情况
        if (existing.ImplementationInstance != null && newDesc.ImplementationInstance != null)
        {
            return existing.ImplementationInstance.GetType() == newDesc.ImplementationInstance.GetType();
        }

        // 处理工厂注册的情况:这里可以根据需求调整,比如比较工厂委托的引用,或者允许自定义判定
        if (existing.ImplementationFactory != null && newDesc.ImplementationFactory != null)
        {
            // 简单实现:比较委托引用;如果需要更灵活,可以允许注入自定义比较器
            return existing.ImplementationFactory == newDesc.ImplementationFactory;
        }

        // 混合类型的情况(比如一个是类型注册,一个是工厂注册),判定为重复
        return false;
    }

    // 获取服务实现的描述文本
    private string GetImplementationDescription(ServiceDescriptor descriptor)
    {
        if (descriptor.ImplementationType != null)
            return descriptor.ImplementationType.FullName;
        if (descriptor.ImplementationInstance != null)
            return $"实例:{descriptor.ImplementationInstance.GetType().FullName}";
        if (descriptor.ImplementationFactory != null)
            return "工厂委托";
        return "未知类型";
    }

    // 切换回普通模式
    public IServiceCollection AsNormal()
    {
        return _innerCollection;
    }
}

// 扩展方法
public static class ServiceCollectionStrictModeExtensions
{
    public static IStrictServiceCollection AsStrict(this IServiceCollection services, bool throwOnFirstViolation = true)
    {
        // 如果已经是严格模式,直接返回(避免重复包装)
        if (services is IStrictServiceCollection strictCollection)
            return strictCollection;
        
        return new StrictServiceCollection(services, throwOnFirstViolation);
    }
}

2. 关键优化点

  • 全方法拦截:覆盖了Add、AddRange、Insert和索引器赋值等所有可能修改集合的操作
  • 完善的重复判定:
    • 包含服务类型、ServiceKey、生命周期三重匹配
    • 支持类型注册、实例注册、工厂注册三种场景的等效判定
  • 异常控制:通过throwOnFirstViolation参数,支持立即抛出单个异常或汇总所有重复异常为AggregateException
  • 避免重复包装:在AsStrict扩展方法中检查当前集合是否已经是严格模式,防止多层包装

3. 使用方式

services.AddX()
        .AsStrict(throwOnFirstViolation: false) // 汇总所有重复异常
        .AddY()
        .AddZ()
        .AsNormal()
        .AddXXX();

可选增强

  • 自定义判定逻辑:允许用户注入Func<ServiceDescriptor, ServiceDescriptor, bool>来自定义重复判定规则
  • 忽略特定服务:添加IgnoreDuplicate<T>()或IgnoreDuplicate(Type serviceType)方法,允许特定服务跳过验证
  • 日志记录:在抛出异常前记录重复注册的详细信息,便于排查问题

内容的提问来源于stack exchange,提问作者Ivan Petrov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 04:27:24