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

使用接口继承时如何避免Autofac中的嵌套装饰器?

问题:Autofac装饰器在接口继承场景下的嵌套问题

我有一个基于.NET 7.0的应用,使用Autofac作为DI容器,包含两个接口:IReadonlyRepository<T> 和 IRepository<T>,其中IRepository<T>继承自IReadonlyRepository<T>。Repository<T>类实现了这两个接口,同时有一个LoggingRepository<T>装饰器,用于包装IRepository<T>,记录仓库方法调用并委托给被包装的实例。

LoggingRepository 代码

class LoggingRepository<T> : IRepository<T>
{
    private readonly IRepository<T> _repository;

    public LoggingRepository(IRepository<T> repository)
    {
        _repository = repository;
    }

    public IEnumerable<T> List()
    {
        Console.WriteLine($"Calling {_repository.GetType().Name}<{typeof(T).Name}>.List()");
        return _repository.List();
    }
}

当前DI注册代码

builder.RegisterGeneric(typeof(Repository<>))
    .As(typeof(IRepository<>))
    .As(typeof(IReadonlyRepository<>))
    .InstancePerLifetimeScope();

builder.RegisterGenericDecorator(typeof(LoggingRepository<>), typeof(IRepository<>));
builder.RegisterGenericDecorator(typeof(LoggingRepository<>), typeof(IReadonlyRepository<>));

遇到的问题

当请求IReadonlyRepository<T>实例时,会出现LoggingRepository<T>嵌套装饰的情况:一个LoggingRepository<T>装饰另一个LoggingRepository<T>,再包装Repository<T>。预期是仅用单个LoggingRepository<T>装饰Repository<T>。


解决方案

核心原因

IRepository<T>继承自IReadonlyRepository<T>,且同时给两个接口注册了装饰器,Autofac会对每个接口的服务链分别应用装饰器,导致嵌套。正确做法是让两个接口复用同一套装饰后的服务,或限制装饰器仅作用于原始实现。

方案一:复用装饰后的服务(推荐)

通过命名注册区分原始仓库和装饰后的服务,让IReadonlyRepository<T>直接指向已被装饰的IRepository<T>实例:

// 1. 命名注册原始仓库,避免被直接解析
builder.RegisterGeneric(typeof(Repository<>))
    .Named("OriginalRepository", typeof(IRepository<>))
    .InstancePerLifetimeScope();

// 2. 给IRepository<T>注册装饰器,基于命名的原始服务
builder.RegisterGenericDecorator(
    typeof(LoggingRepository<>),
    typeof(IRepository<>),
    fromKey: "OriginalRepository");

// 3. 让IReadonlyRepository<T>解析为已装饰的IRepository<T>实例
builder.RegisterGeneric(typeof(IRepository<>))
    .As(typeof(IReadonlyRepository<>))
    .InstancePerLifetimeScope();

方案二:条件注册限制装饰器作用范围

使用条件注册,让装饰器仅应用于原始的Repository<T>类型,避免重复装饰:

builder.RegisterGeneric(typeof(Repository<>))
    .As(typeof(IRepository<>))
    .As(typeof(IReadonlyRepository<>))
    .InstancePerLifetimeScope();

// 仅当被装饰的实例是Repository<>时,才应用LoggingRepository<>
builder.RegisterGenericDecorator(
    typeof(LoggingRepository<>),
    typeof(IRepository<>),
    context => context.ImplementationType == typeof(Repository<>));

// 让IReadonlyRepository<T>复用已装饰的IRepository<T>服务
builder.RegisterGeneric(typeof(IRepository<>))
    .As(typeof(IReadonlyRepository<>))
    .InstancePerLifetimeScope();

说明

两种方案的核心都是让IReadonlyRepository<T>获取的是已经被LoggingRepository<T>装饰过的IRepository<T>实例,而非对IReadonlyRepository<T>单独应用装饰器,从而避免嵌套。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 20:50:37