Autofac: como limitar a vida útil de um objeto idispotável sem passar pelo contêiner do COI
-
20-09-2019 - |
Pergunta
Atualmente, estou aprendendo a usar o Autofac e estou preso ao descartar IDisposable
objetos deterministicamente. Deixe -me primeiro apresentar a situação antes de declarar meu problema.
Posicão inicial:
Digamos que meu modelo de objeto seja definido pelas seguintes interfaces:
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
}
Além disso, defino um delegado que será usado como um IApple
fábrica:
delegate IApple AppleFactory;
Configuração do Autofac:
Agora, eu registraria os tipos acima da seguinte forma - observe que estou omitindo o código de ambas as classes Apple
e Horse
, já que eles são triviais para implementar:
var builder = new Autofac.ContainerBuilder();
builder.RegisterType<Apple>().As<IApple>();
builder.RegisterType<Horse>().As<IHorse>();
builder.RegisterType<HorseKeeper>().As<IHorseKeeper>();
builder.RegisterGeneratedFactory<AppleFactory>();
Meu problema:
Eu não sei como implementar o método IHorseKeeper.Feed
. Aqui está o que eu tenho atualmente:
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!
}
}
Esse é o tipo de código que eu gostaria de ter, pois é completamente autofac-agnóstico. Poderia muito bem trabalhar com outro contêiner do COI, desde que AppleFactory
funciona conforme o esperado.
No entanto, porque o Autofac lida com o AppleFactory
Para mim, vai acompanhar todos IApple
objetos que produz para mim e, portanto, desejarão Dispose
eles próprios no final da vida do contêiner. Ou seja, o produzido apple
será descartado duas vezes.
Suponho que o registro IApple
Como .ExternallyOwned()
não é uma solução viável, pois pode haver casos em que é mais fácil deixar o Autofac lidar com o IApple
S 'Lifetime.
Descarte determinístico com Autofac requer a criação de um recipiente aninhado usando container.BeginLifetimeScope()
, porém eu não quero usar isso dentro HorseKeeper.FeedHorse
Porque então HorseKeeper
Torna-se dependente do Autofac, e eu gostaria de manter meu código ioc-agnóstico.
Pergunta:
Como faço para implementar HorseKeeper.FeedHorse
De uma maneira agnóstica do COI (Autofac), garantindo que os objetos gerados na fly-fly sejam descartados adequadamente?
Solução
As outras respostas aqui são perspicazes, mas têm um problema. Nos dois casos, se a Apple tiver outras dependências que precisam de descarte, a limpeza correta não acontecerá.
O Autofac 2 fornece um novo recurso para ajudar aqui, chamado "instâncias de propriedade". Percebi que seu código de registro é Autofac 1.4; portanto, se você não conseguir atualizar, me avise (existem outras maneiras menos transparentes de fazer isso.)
Registre a Apple como de costume (não de propriedade externamente):
builder.RegisterType<Apple>().As<IApple>();
Declare a AppleFactory como:
public delegate Owned<IApple> AppleFactory();
No AutoFAC 2, você não precisa mais ligar para o RegisterGeneratedFactory () - isso é automático.
Então, em cavalos, alimente o cavalo assim:
public void FeedHorse()
{
using (var apple = appleFactory())
{
horse.Eat(apple.Value);
}
}
(Observe a propriedade .Value para obter o IAPPLE subjacente.
No final do uso do Block the Apple, além de todas as suas dependências, serão limpas.
Quaisquer outros componentes que usam o IAPPLE diretamente (como dependências) receberão o comportamento usual.
Outras dicas
A única maneira é modificar o registro da Apple com o ExternallyOwned
modificador. Isso instrui o AutoFAC a não rastrear o objeto para descarte, mas permita que alguém externo (seu código) lide com o descarte. Mas, como você afirma, agora você terá que garantir que todas as instâncias da Apple estejam descartadas manualmente, pois você não receberá ajuda automática do Autofac.
builder.RegisterType<Apple>().As<IApple>().ExternallyOwned();
Com este registro, seu código de alimentação funcionará conforme o esperado, no entanto.
Observação: na discussão se a interface deve herdar IDisposable
ou não: IMO, quando uma interface herda IDisposable
, isso é uma indicação para o desenvolvedor "consumindo" de que a instância deve ser descartada em algum momento. No caso de IAPPLE, uma vez que essa interface também é idisposta, o desenvolvedor deve garantir instâncias (e devo também ser registrado como proprietário externo). Por outro lado, se a aula da Apple parecia assim:
class Apple: IApple, IDisposable
{ }
Os consumidores do IAPPLE agora não têm conhecimento do fato de que as instâncias são idiposáveis. Nesse caso, deixaremos o disposição do contêiner.
Portanto, minha conclusão é que cabe a mim como desenvolvedor da Apple e Iapple escolher se exigirei que os consumidores lidem com o descarte ou o deixem em um contêiner.
Se às vezes você deseja gerenciar a vida inteira das instâncias da Apple e, às vezes, deixar o contêiner lidar com ele, pode definir duas interfaces:
public IApple
{
void Consume();
}
public IDisposableApple : IApple, IDisposable
{
}
E depois registre a aula duas vezes:
builder.RegisterType<Apple>().As<IApple>();
builder.RegisterType<Apple>().As<IDisosableApple>().ExternallyOwned();
Em seguida, você pode injetar um disposleacleAppleFactory em classes que precisam criar e descartar maçãs.
Para as aulas que precisam apenas de uma maçã com a mesma vida útil do contêiner, você injeta o IAPPLE.
No entanto, o fato de você precisar ambos pode indicar que está misturando novos e injetáveis. A Apple pode simplesmente ser um objeto "novo", ou seja, um que não precisa ser gerenciado pelo contêiner do COI.