Разрешить параметры в соответствии с родительским деревом, которое инициировало разрешение?

У меня есть код, похожий на этот:

class A: IA { ... }
class B: IB {
    public B(IA a, ...) { ... }
    ...
}
class C1 {
    public C1(IA a, IB b, ...) { ... }
}
class C2 {
    public C2(IA a, IB b, ...) { ... }
}

Я хочу только два экземпляра A — один для C1 и один для C2. Мне нужны два экземпляра B. Экземпляр B, переданный C1, должен получить тот же экземпляр A, созданный для C1. C2 и его параметры должны иметь другой экземпляр A. Как я могу настроить этот сценарий в Autofac? Похоже, что функция времени жизни принадлежащего экземпляра должна была справиться с этим, но опубликованный пример был только одним слоем. глубокий, а не два.

Моя реальная ситуация намного сложнее. У меня есть дюжина наследников IB и полдюжины определений C, которые принимают различные комбинации наследников IB. Я хотел избежать использования именованных экземпляров, потому что это значительно раздуло бы мой загрузчик и затруднило бы его обслуживание.

Второстепенный вопрос: поддерживает ли DryIoc это? Меня можно было бы уговорить переключиться.


person Brannon    schedule 12.05.2015    source источник


Ответы (2)


Вы ищете что-то под названием «экземпляр на графике». Он изначально не поддерживается autofac. Если вы не застряли с autofac, вы можете использовать Замок Виндзор, как объяснено в этом связанном/повторяющемся вопросе.

Если вы ищете решение с самим autofac, вы можете реализовать его с помощью InstancePerMatchingLifetimeScope или InstancePerLifetimeScope.

Вот как вы настраиваете свой контейнер

private IContainer Build()
{
    ContainerBuilder builder = new ContainerBuilder();

    builder.RegisterType<A>().As<IA>().InstancePerMatchingLifetimeScope("SomeTag");
    builder.RegisterType<B>().As<IB>().InstancePerMatchingLifetimeScope("SomeTag");
    builder.RegisterType<C1>();

    return builder.Build();
}

Вот как вы его используете

[Test]
public void PerGraphLifeStyle()
{
    var container = Build();

    C1 c1;
    C1 c2;
    using (var scope = container.BeginLifetimeScope("SomeTag"))
    {
        c1 = scope.Resolve<C1>();
        Assert.AreSame(c1.A, c1.B.A);
    }

    using (var scope = container.BeginLifetimeScope("SomeTag"))
    {
        c2 = scope.Resolve<C1>();
        Assert.AreSame(c1.A, c1.B.A);
    }

    Assert.AreNotSame(c1.A, c2.A);
}

Классы-пустышки, которые я создал для тестирования, приведены ниже.

internal interface IA
{
}

class A : IA
{

}

internal interface IB
{
    IA A { get; set; }
}

class B : IB
{
    public B(IA a)
    {
        A = a;
    }

    public IA A { get; set; }
}
class C1
{
    public IA A { get; set; }
    public IB B { get; set; }

    public C1(IA a, IB b)
    {
        A = a;
        B = b;
    }
}
person Sriram Sakthivel    schedule 12.05.2015
comment
Спасибо за отличную демонстрацию кода. Мне придется подумать об этом немного подробнее, так как я на самом деле не вызываю Resolve ни для одного из моих объектов C. Они все пути вниз по цепочке. Я разрешаю только пару высокоуровневых объектов из своего контейнера, а остальные (около 600) заполняются через фабрики и построение необходимых зависимостей конструктора. - person Brannon; 13.05.2015
comment
@Brannon Я знаю, что это больно, если ты сам не звонишь Resolve для этих объектов. Один из способов — скрыть этот код внутри фабричного класса. Таким образом, вы вызываете factory.CreateC1() factory, который инкапсулирует контейнер, создает LifetimeScope и вызывает решение для вас. - person Sriram Sakthivel; 13.05.2015
comment
Autofac напрямую поддерживает экземпляр на график с временем жизни InstancePerOwned (очень похоже на то, как это делает DryIoc): c.RegisterType<A>().InstancePerOwned<C>(); // A is reused in C - person dadhi; 25.05.2015
comment
@dadhi Это то же самое, что и экземпляр на графике? Это заставляет нас использовать тип Owned<T> в нашей сущности, верно? Это означает, что мы тесно связаны с нашим контейнером IOC, что плохо. Поправьте меня если я ошибаюсь. - person Sriram Sakthivel; 25.05.2015
comment
У этого подхода есть свои плюсы и минусы, но это другое: плюсы: Использование Owned легко предоставляет вам IDispose. минусы: Да, вам нужно ссылаться на контейнер, чтобы использовать собственную оболочку. DryIoc поддерживает это без обертки, но чтобы иметь возможность Dispose - вам все равно нужен ResolutionScoped<T>. - person dadhi; 25.05.2015
comment
Обновление: теперь DryIoc rc1 может внедрять IDisposable scope, поэтому вы можете управлять удалением области действия без дополнительных оболочек. - person dadhi; 03.06.2015

В DryIoc (мой) это может быть достигнуто непосредственно с помощью Reuse.InResolutionScopeOf:

container.Register<C1>(setup: Setup.With(openResolutionScope: true), serviceKey: Some.Blah);
container.Register<C2>(setup: Setup.With(openResolutionScope: true), serviceKey: Some.Blah);

container.Register<IA, A>(reuse: Reuse.InResolutionScopeOf(serviceKey: Some.Blah));

Вот и все. openResolutionScope: true создает область в графе объектов. serviceKey требуется для идентификации/нахождения области в графе. Например, если C1 и C2 реализуют один и тот же интерфейс IC, то служебный ключ не нужен. Вы просто говорите Reuse.InResolutionScopeOf<IC>(), и это будет один IA для каждого IC объекта на графике.

person dadhi    schedule 15.05.2015
comment
Если вы являетесь автором DryIoc, вам необходимо раскрыть его. В противном случае люди здесь расценили бы это как спам или что-то в этом роде. Спасибо. - person Sriram Sakthivel; 02.09.2015