Autofac: как ограничить время жизни объекта IDisposable без обхода контейнера IoC - PullRequest
10 голосов
/ 13 февраля 2010

В настоящее время я изучаю, как использовать 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 фабрика:

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's no longer needed!
    }
}

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

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

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

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

Вопрос:

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

Ответы [ 3 ]

15 голосов
/ 14 февраля 2010

Другие ответы здесь проницательны, но имеют проблему. В обоих случаях, если у 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 напрямую (в качестве зависимостей), получат обычное поведение.

3 голосов
/ 13 февраля 2010

Единственный способ - изменить регистрацию 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, должен решить, буду ли я требовать от потребителей распоряжаться утилизацией или оставить ее до контейнера.

2 голосов
/ 13 февраля 2010

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

public IApple
{
   void Consume();
}

public IDisposableApple : IApple, IDisposable
{
}

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

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

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

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

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

...