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 фабрика:

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!
    }
}

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

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

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

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

вопрос:

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

3 ответов


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

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

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


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

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

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

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

class Apple: IApple, IDisposable
{ }

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

таким образом, мой вывод заключается в том, что я как разработчик Apple и IApple должен выбрать, буду ли я требовать от потребителей обрабатывать утилизацию или оставить ее до контейнера.


Если вы иногда хотите управлять временем жизни экземпляров 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.