簡體   English   中英

用於多個接口的裝飾器 - Autofac中的循環依賴性謎語

[英]Decorator for multiple interfaces - a circular dependency riddle in Autofac

我來自Ninject,但我決定嘗試Autofac,因為它似乎更積極地開發。 到目前為止,我可以說使用.WhenInjectedExactlyInto語法注冊裝飾器並不像在Ninject中那么容易。 無論如何,請耐心等待我,因為我是一個Autofac新手。

這是問題所在:

我有A實現接口IA ,由A_Decorator A_Decorator實現接口IAIB ,並且應該由AB_Decorator修飾, AB_Decorator也同時實現IAIB AB_Decorator接受類型IAIB兩個依賴項(因此它是兩者的裝飾器)但它們都應該解析為A_Decorator的相同實例。 它看起來像這樣: AB_Decorator(A_Decorator(A) as IA, A_Decorator(A) as IB) 從Autofac容器請求類型IA或類型IB服務時,它們應引用單個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 -> AutofacExample.AB_Decorator -> System.Object -> AutofacExample.AB_Decorator )並且我無法嘗試各種組合已命名的注冊。

有誰知道解決這個問題? 提前致謝。

問題

問題在於AB_Decorator的裝飾器注冊。 特別是解析AB_Decorator的lambda函數:

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

AB_Decorator的構造AB_Decorator接受2個參數,這兩個參數都應該是A_Decorator的相同實例, 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...))

我們需要傳遞decorated成解決當兩個參數AB_Decorator 像這樣:

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();

現在我們將decoratedA_Decorator發送到IAIB參數。 直接構造TypedParameter實例允許我在參數列表中指定我希望實例實現的類型,在本例中為AB_Decorator

干得好:

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()重載; 如果你在這里看到勝利,我認為這是一個很好的建議來提升項目的問題跟蹤器。)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM