Autofac: как ограничить время жизни объекта IDisposable, не проходя вокруг контейнера IoC

В настоящее время я изучаю, как использовать Autofac, и я зацикливаюсь с дестинированием объектов IDisposable. Позвольте мне сначала представить ситуацию, прежде чем я изложу свою проблему.

Исходное положение:

Скажем, моя объектная модель определяется через следующие интерфейсы:

interface IApple : IDisposable
{
    void Consume();
}

interface IHorse
{
    void Eat(IApple apple);   // is supposed to call apple.Consume()
}

interface IHorseKeeper
{
    void FeedHorse();   // is supposed to call horse.Eat(apple)
                        //   where 'horse' is injected into IHorseKeeper
                        //   and 'apple' is generated by IHorseKeeper on-the-fly
}

Далее, я определяю делегат, который будет использоваться как IApple factory:

delegate IApple AppleFactory;

Конфигурация Autofac:

Теперь я зарегистрировал бы следующие типы следующим образом: обратите внимание, что я опускаю код обоих классов Apple и Horse, поскольку они тривиальны для реализации:

var builder = new Autofac.ContainerBuilder();

builder.RegisterType<Apple>().As<IApple>();
builder.RegisterType<Horse>().As<IHorse>();
builder.RegisterType<HorseKeeper>().As<IHorseKeeper>();
builder.RegisterGeneratedFactory<AppleFactory>();

Моя проблема:

Я не знаю, как реализовать метод IHorseKeeper.Feed. Вот что я сейчас имею:

class HorseKeeper : IHorseKeeper
{
    private readonly IHorse horse;
    private readonly AppleFactory appleFactory;

    public HorseKeeper(IHorse horse, AppleFactory appleFactory)
    //                 ^^^^^^^^^^^^  ^^^^^^^^^^^^^^^^^^^^^^^^^
    //                         constructor injection
    {
        this.horse = horse;
        this.appleFactory = appleFactory;
    }

    public void FeedHorse()
    {
        using (var apple = appleFactory())
        {
            horse.Eat(apple);
        }  // <- Dispose() apple now (ASAP), as it no longer needed!
    }
}

Это тот код, который я хотел бы иметь, поскольку он полностью Autofac-агностик. Он также может работать с другим контейнером IoC, если AppleFactory работает так, как ожидалось.

Однако, поскольку Autofac обрабатывает AppleFactory для меня, он будет отслеживать все объекты IApple, которые он создает для меня, и поэтому захочет Dispose сам по себе в конце жизненного цикла контейнера. То есть, произведенный Apple будет располагаться дважды.

Я полагаю, что регистрация IApple как .ExternallyOwned() не является жизнеспособным решением, так как могут быть случаи, когда проще разрешить Autofac обрабатывать время жизни IApple s.

Детерминированное удаление с помощью Autofac требует создания вложенного контейнера с использованием container.BeginLifetimeScope(), однако я не хочу использовать его внутри HorseKeeper.FeedHorse потому что тогда HorseKeeper становится зависимым от Autofac, и я хотел бы сохранить мой код IoC-агностиком.

Вопрос:

Как реализовать HorseKeeper.FeedHorse в IoC (Autofac) -агностическом способе, гарантируя, что на лету сгенерированные объекты расположены правильно?

Ответ 1

Другие ответы здесь проницательны, но есть проблема. В обоих случаях, если у Apple есть другие зависимости, требующие удаления, правильная очистка не произойдет.

Autofac 2 предоставляет новую функцию, которая здесь называется "принадлежащие экземпляры". Я заметил, что ваш регистрационный код - Autofac 1.4, поэтому, если вы не можете обновить, сообщите мне (есть и другие, менее прозрачные способы сделать это.)

Зарегистрируйте Apple как обычно (не извне):

builder.RegisterType<Apple>().As<IApple>();

Объявить AppleFactory как:

public delegate Owned<IApple> AppleFactory();

В Autofac 2 вам больше не нужно вызывать RegisterGeneratedFactory() - это автоматически.

Затем, в HorseKeeper, кормите лошадь следующим образом:

public void FeedHorse()
{
    using (var apple = appleFactory())
    {
        horse.Eat(apple.Value);
    }
}

(Обратите внимание на свойство .Value, чтобы получить базовый IApple.

В конце блока использования яблоко, а также все его зависимости будут очищены.

Любые другие компоненты, которые используют IApple напрямую (как зависимости), получат обычное поведение.

Ответ 2

Единственный способ - изменить регистрацию Apple с помощью модификатора ExternallyOwned. Это предписывает Autofac не отслеживать объект для утилизации, а позволяет кому-то внешнему (ваш код) обрабатывать удаление. Но, как вы заявляете, теперь вам нужно убедиться, что все экземпляры Apple расположены вручную, так как вы не получите автоматической помощи от Autofac.

builder.RegisterType<Apple>().As<IApple>().ExternallyOwned();

С этой регистрацией ваш код фида будет работать, как ожидалось.

Примечание: при обсуждении, должен ли интерфейс наследовать IDisposable или нет: IMO, когда интерфейс наследует IDisposable, это указывает на "потребляющего" разработчика, что экземпляр должен быть удаленным в определенный момент времени. В случае IApple, поскольку этот интерфейс также IDisposable, разработчик должен убедиться, что он удаляет экземпляры (и должен также быть зарегистрирован как ExternallyOwned). С другой стороны, если класс Apple выглядел так:

class Apple: IApple, IDisposable
{ }

потребители IApple теперь полностью не знают о том, что экземпляры IDisposable. В этом случае мы разрешим дескриптор контейнера.

Итак, я пришел к выводу, что разработчик Apple и IApple решил, будет ли я требовать от потребителей обработки утилиты или оставить его в контейнере.

Ответ 3

Если вы иногда хотите самостоятельно управлять временем жизни экземпляров Apple, а иногда позволяете контейнеру обрабатывать его, то вы можете определить два интерфейса:

public IApple
{
   void Consume();
}

public IDisposableApple : IApple, IDisposable
{
}

И затем дважды зарегистрируйте класс:

builder.RegisterType<Apple>().As<IApple>();
builder.RegisterType<Apple>().As<IDisosableApple>().ExternallyOwned(); 

Затем вы можете добавить DisposableAppleFactory в классы, которые должны создавать и уничтожать яблоки.

Для классов, которым требуется только яблоко с тем же временем жизни, что и контейнер, вы вместо этого вводите IApple.

Однако тот факт, что вы оба хотите, может указывать на то, что вы смешиваете новинки и инъекции. Apple может просто быть "новым" объектом, то есть тем, который не нуждается в управлении контейнером IoC.