【问题标题】:Decorator for multiple interfaces - a circular dependency riddle in Autofac多个接口的装饰器 - Autofac 中的循环依赖之谜
【发布时间】:2017-07-05 21:30:00
【问题描述】:

我来自 Ninject,但我决定尝试一下 Autofac,因为它似乎更加活跃。到目前为止,我可以说注册装饰器并不像在 Ninject 中使用 .WhenInjectedExactlyInto 语法那么容易。无论如何,请多多包涵,因为我是 Autofac 新手。

问题来了:

我有类型 A 实现由 A_Decorator 装饰的接口 IAA_Decorator 实现了接口IAIB,而AB_Decorator 又实现了IAIBAB_Decorator 接受 IAIB 类型的两个依赖项(因此它是两者的装饰器),但它们都应该解析为 A_Decorator 的同一个实例。它看起来像这样:AB_Decorator(A_Decorator(A) as IA, A_Decorator(A) as IB)。当从 Autofac 容器请求 IAIB 类型的服务时,它们应该引用单个 AB_Decorator 实例。

用词来描述有点棘手,但这是我能想到的最简单的代码示例,它显示了这种情况(我已将实例 ID 和跟踪消息添加到构造函数以查看发生了什么):

using System;
using Autofac;

namespace AutofacExample
{
    internal interface IA { }

    internal interface IB { }

    class A : IA
    {
        static int _instanceCounter;
        readonly int Id = ++_instanceCounter;

        public A()
        {
            Console.WriteLine(this);
        }

        public override string ToString()
        {
            return $"{GetType().Name}[{nameof(Id)}={Id}]";
        }
    }

    class A_Decorator : IA, IB
    {
        static int _instanceCounter = 10;
        readonly int Id = ++_instanceCounter;

        /* decorated1 should reference instance of A */

        public A_Decorator(IA decoratedA)
        {
            Console.WriteLine($"{this}({nameof(decoratedA)}={decoratedA})");
        }

        public override string ToString()
        {
            return $"{GetType().Name}[{nameof(Id)}={Id}]";
        }
    }

    class AB_Decorator : IA, IB
    {
        static int _instanceCounter = 100;
        readonly int Id = ++_instanceCounter;

        /* Both decorated1 and decorated2 should reference the same instance of A_Decorator */

        public AB_Decorator(IA decoratedA, IB decoratedB)
        {
            Console.WriteLine($"{this}({nameof(decoratedA)}={decoratedA}, {nameof(decoratedB)}={decoratedB})");
        }

        public override string ToString()
        {
            return $"{GetType().Name}[{nameof(Id)}={Id}]";
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            ContainerBuilder builder = new ContainerBuilder();

            builder
                .RegisterType<A>()
                .Named<IA>(nameof(A))
                .SingleInstance();

            builder
                .RegisterType<A_Decorator>()
                .Named<IA>(nameof(A_Decorator))
                .Named<IB>(nameof(A_Decorator))
                .SingleInstance();

            builder
                .RegisterType<AB_Decorator>()
                .Named<IA>(nameof(AB_Decorator))
                .Named<IB>(nameof(AB_Decorator))
                .SingleInstance();

            /* A is decorated by A_Decorator as IA */
            builder
                .RegisterDecorator<IA>(
                    (c, decorated) =>
                        c.ResolveNamed<IA>(nameof(A_Decorator), TypedParameter.From(decorated)),
                    nameof(A))
                //.Keyed<IA>("innerA")
                //.Keyed<IB>("innerB")
                .SingleInstance();

            /* Trying to register AB_Decorator as IA creates circular dependency */
            //builder
            //    .RegisterDecorator<IA>(
            //        (c, decorated) =>
            //            c.ResolveNamed<IA>(nameof(AB_Decorator), TypedParameter.From(decorated)),
            //        "innerA")
            //    .SingleInstance();

            /* A_Decorator is decorated by AB_Decorator as IB */
            builder
                .RegisterDecorator<IB>(
                        (c, decorated) =>
                            c.ResolveNamed<IB>(nameof(AB_Decorator), TypedParameter.From(decorated)),
                        nameof(A_Decorator) /* "innerB" */)
                    .SingleInstance();

            IContainer container = builder.Build();

            IA a = container.Resolve<IA>();
            IB b = container.Resolve<IB>();

            Console.WriteLine($"{nameof(a)} == {nameof(b)} ? {ReferenceEquals(a, b)}");
            Console.WriteLine($"{nameof(a)} is {a.GetType().Name}");
            Console.WriteLine($"{nameof(b)} is {b.GetType().Name}");
        }
    }
}

不幸的是,请求IA 的实例给了我A_Decorator,而对于IB,我得到了AB_Decorator。尝试取消注释额外的装饰器注册块会导致循环依赖异常 (DependencyResolutionException: Circular component dependency detected: System.Object -&gt; AutofacExample.AB_Decorator -&gt; System.Object -&gt; AutofacExample.AB_Decorator),我无法尝试命名注册的各种组合。

有人知道这个问题的解决方案吗?提前致谢。

【问题讨论】:

    标签: c# dependency-injection decorator autofac circular-dependency


    【解决方案1】:

    给你:

    ContainerBuilder builder = new ContainerBuilder();
    
    builder
        .RegisterType<A>()
        .Named<IA>(nameof(A))
        .SingleInstance();
    
    builder
        .RegisterType<A_Decorator>()
        .Named<IA>(nameof(A_Decorator))
        .Named<IB>(nameof(A_Decorator))
        .WithParameter(new ResolvedParameter((pi, c) => pi.Name == "decoratedA", 
            (pi, c) => c.ResolveNamed<IA>(nameof(A))))
        .SingleInstance();
    
    builder
        .RegisterType<AB_Decorator>()
        .As<IA, IB>()
        .WithParameter(new ResolvedParameter((pi, c) => pi.Name == "decoratedA",
            (pi, c) => c.ResolveNamed<IA>(nameof(A_Decorator))))
        .WithParameter(new ResolvedParameter((pi, c) => pi.Name == "decoratedB",
            (pi, c) => c.ResolveNamed<IB>(nameof(A_Decorator))))
        .SingleInstance();
    
    IContainer container = builder.Build();
    

    打印:

    A[Id=1]
    A_Decorator[Id=11](decoratedA=A[Id=1])
    AB_Decorator[Id=101](decoratedA=A_Decorator[Id=11], decoratedB=A_Decorator[Id=11])
    a == b ? True
    a is AB_Decorator
    b is AB_Decorator
    

    API 令人困惑,因为在这种情况下您不需要RegisterDecorator()(它是用于一次装饰一整套组件)。

    (如果我们能烤整个就好了:

        .WithParameter(new ResolvedParameter((pi, c) => pi.Name == "decoratedA", 
            (pi, c) => c.ResolveNamed<IA>(nameof(A))))
    

    在 Autofac 中将成语简化为更简单的 WithParameter() 重载;如果您在这里看到胜利,我认为这是在项目的问题跟踪器上提出的一个很好的建议。)

    【讨论】:

    • 这个也可以,而且代码实际上更短。我接受了另一个答案,因为它对装饰器的使用更加明确并且有更多的解释,即使我最终使用你的来减少代码行 - 希望你不介意:)。此外,我绝对认为在 Autofac 中简化装饰器 API 是值得的,所以如果您决定提出建议,请随时使用这个问题。谢谢。
    • 还有一点:如果构造函数参数具有唯一类型(在这种情况下),我最终使用 new ResolvedParameter((pi, c) =&gt; pi.ParameterType == typeof(IA), (pi, c) =&gt; c.ResolveNamed&lt;IA&gt;(nameof(A))) 通过类型而不是名称来识别参数 - 重构的空间更小陷阱。 :)
    【解决方案2】:

    问题

    问题在于AB_Decorator 的装饰器注册。特别是解析AB_Decorator的lambda函数:

    ( c, decorated ) => c.ResolveNamed<IA>( nameof( AB_Decorator ), TypedParameter.From( decorated ) );
    

    AB_Decorator 的构造函数接受 2 个参数,这两个参数都应该是 A_Decorator 的同一个实例,它作为 decorated 提供给 lambda。但是,decorated 仅作为参数通过TypedParameter.From( decorated ) 传递一次。因此 Autofac 将尝试通过容器解析第二个参数。

    现在IB 的注册表明我们应该得到一个带有A_Decorator 包裹在AB_Decorator 中的单例实例。所以要解析IB,容器必须构造AB_Decorator。有问题,我们目前正在尝试将AB_Decorator 解析为IA,但我们需要一个IB 来完成为IA 构造的AB_Decorator 的构造函数参数。而IB 在容器中注册为AB_Decorator。所以你得到:

    AB_Decorator(A_Decorator(A) as IA, AB_Decorator(A_Decorator(A) as IA, AB_Decorator(etc...))
    

    解决方案

    在解析AB_Decorator 时,我们需要将decorated 传递给两个参数。像这样:

    builder
        .RegisterDecorator<IA>(
            ( c, decorated ) =>
    
                c.ResolveNamed<IA>( nameof( AB_Decorator ),
                    new TypedParameter( typeof( IA ), decorated ),
                    new TypedParameter( typeof( IB ), decorated )
                )
            ,"innerA"
        )
        .SingleInstance();
    
    
    builder
        .RegisterDecorator<IB>(
            ( c, decorated ) =>
    
                c.ResolveNamed<IB>( nameof( AB_Decorator ),
                    new TypedParameter( typeof( IA ), decorated ),
                    new TypedParameter( typeof( IB ), decorated )
                )
            , nameof( A_Decorator ) /* "innerB" */
        )
        .SingleInstance();
    

    现在我们向IAIB 参数发送decorated,即A_Decorator。直接构造TypedParameter 实例允许我在参数列表中指定我希望实例实现的类型,在本例中为AB_Decorator

    【讨论】:

    • 它有效,谢谢。您的解决方案生成的代码比 Nicholas Blumhardt 下面的代码多(需要 3 个 RegisterDecorator 块),但它更明确地说明了装饰器的使用,这很好。 :) 我接受这个,因为它的解释也更好。
    猜你喜欢
    • 2021-01-05
    • 1970-01-01
    • 2019-10-02
    • 2019-11-24
    • 2021-10-30
    • 2015-09-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多