Я в настоящее время изучаю, как использовать 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;
Теперь, я зарегистрировался бы, вышеупомянутые типы следующим образом - отмечают, что я опускаю код обоих классов Apple
и Horse
, так как они тривиальны для реализации:
var builder = new Autofac.ContainerBuilder();
builder.RegisterType().As();
builder.RegisterType().As();
builder.RegisterType().As();
builder.RegisterGeneratedFactory();
Я не вполне знаю, как реализовать метод 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-агностически. Это могло точно также работать с другим контейнером МОК, пока AppleFactory
работы как ожидалось.
Однако, потому что Autofac обрабатывает AppleFactory
для меня это будет отслеживать все IApple
объекты это производит для меня и поэтому захочет Dispose
их самостоятельно в конце времени жизни контейнера. Т.е., произведенный apple
будет расположен дважды.
Я предполагаю регистрацию IApple
как .ExternallyOwned()
не эффективное решение, поскольку могли бы быть случаи, где легче позволить Autofac обработать IApple
время жизни.
Детерминированное распоряжение с Autofac требует создания вложенного контейнерного использования container.BeginLifetimeScope()
, однако я не хочу использовать эту внутреннюю часть HorseKeeper.FeedHorse
потому что тогда HorseKeeper
становится зависящим от Autofac, и я хотел бы сохранить своего агностика МОК кода.
Как я реализую HorseKeeper.FeedHorse
в МОК (Autofac) - агностический путь при обеспечении, что на лету сгенерированные объекты расположены правильно?
Другие ответы здесь полезны, но имеют проблему. В обоих случаях, если у 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.
В конце блока using яблоко и все его зависимости будут очищено.
Любые другие компоненты, которые используют IApple напрямую (в качестве зависимостей), получат обычное поведение.
Единственный способ - изменить регистрацию Apple с помощью модификатора ExternalOwned
. Это указывает Autofac не отслеживать объект для удаления, а позволить кому-то внешнему (вашему коду) обрабатывать удаление. Но, как вы заявляете, теперь вам нужно будет убедиться, что все экземпляры Apple удаляются вручную, поскольку вы не получите автоматической помощи от Autofac.
builder.RegisterType<Apple>().As<IApple>().ExternallyOwned();
Однако с этой регистрацией ваш код Feed будет работать должным образом.
Примечание : при обсуждении, должен ли интерфейс наследовать IDisposable
или нет: IMO, когда интерфейс наследует IDisposable
, это указание на "потребляющего" разработчика что экземпляр должен быть удален в определенный момент времени. В случае с IApple, поскольку этот интерфейс также является IDisposable, разработчик должен убедиться, что утилизированы экземпляры (и должен также затем быть зарегистрирован как ExternalOwned). С другой стороны, если бы класс Apple выглядел так:
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.
Однако тот факт, что вам нужны оба, может указывать на то, что вы смешиваете новые и инъекции . Apple может быть просто «новым» объектом, то есть тем, которым не нужно управлять контейнером IoC.