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.