Como você concilia IDisposable e IoC?

Eu finalmente estou enrolando minha cabeça em torno de IoC e DI em C #, e estou lutando com algumas das bordas. Estou usando o contêiner Unity, mas acho que essa questão se aplica mais amplamente.

Usando um contêiner IoC para dispensar instâncias que implementam IDisposable me assusta! Como você deve saber se deve Dispose ()? A instância pode ter sido criada apenas para você (e, portanto, você deve Dispose ()), ou pode ser uma instância cuja vida seja gerenciada em outro lugar (e, portanto, é melhor que você não o faça). Nada no código diz, e na verdade isso pode mudar com base na configuração! Isso parece mortal para mim.

Algum especialista em IoC pode descrever boas maneiras de lidar com essa ambigüidade?

O AutoFac lida com isso, permitindo a criação de um contêiner nested. Quando o container termina, ele automaticamente descarta todos os objects IDisposable dentro dele. Mais aqui .

Conforme você resolve os serviços, o Autofac rastreia os componentes descartáveis ​​(IDisposable) que são resolvidos. No final da unidade de trabalho, você descarta o escopo vitalício associado e o Autofac automaticamente limpa / descarta os serviços resolvidos.

Você definitivamente não deseja chamar Dispose () em um object que foi injetado em sua class. Você não pode supor que é o único consumidor. Sua melhor aposta é envolver seu object não gerenciado em alguma interface gerenciada:

 public class ManagedFileReader : IManagedFileReader { public string Read(string path) { using (StreamReader reader = File.OpenRead(path)) { return reader.ReadToEnd(); } } } 

Isso é apenas um exemplo, eu usaria File.ReadAllText (path) se eu estivesse tentando ler um arquivo de texto em uma string.

Outra abordagem é injetar uma fábrica e gerenciar o object você mesmo:

 public void DoSomething() { using (var resourceThatShouldBeDisposed = injectedFactory.CreateResource()) { // do something } } 

Isso me intrigou com frequência também. Apesar de não estar feliz com isso, sempre cheguei à conclusão de que nunca era melhor devolver um object IDisponível de maneira transitória.

Recentemente, eu reformulei a pergunta para mim: isso é realmente um problema do IoC ou um problema do framework .net? Dispor é estranho de qualquer maneira. Não tem propósito funcional significativo, apenas técnico. Então, é mais uma questão de framework com a qual temos que lidar, do que uma questão de IoC.

O que eu gosto sobre DI é que eu posso pedir um contrato me fornecendo funcionalidade, sem ter que se preocupar com os detalhes técnicos. Eu não sou o dono. Nenhum conhecimento sobre qual camada está dentro Nenhum conhecimento sobre quais tecnologias são necessárias para cumprir o contrato, não se preocupa com a vida. Meu código parece legal e limpo e é altamente testável. Eu posso implementar responsabilidades nas camadas onde elas pertencem.

Então, se houver uma exceção a essa regra que requer que eu organize a vida útil, vamos fazer essa exceção. Quer eu goste ou não. Se o object que implementa a interface exigir que eu o descarte, quero saber sobre isso desde então, sou acionado para usar o object o mais breve possível. Um truque ao resolvê-lo usando um contêiner filho que é descartado algum tempo depois ainda pode me fazer manter o object vivo por mais tempo do que deveria. A vida útil permitida do object é determinada ao registrar o object. Não pela funcionalidade que cria um contêiner filho e o mantém por um determinado período.

Então, enquanto nós, desenvolvedores, precisarmos nos preocupar com o descarte (isso mudará alguma vez?) Vou tentar injetar o mínimo possível de objects descartáveis ​​transitórios. 1. Eu tento tornar o object não descartável, por exemplo, não mantendo objects descartáveis ​​no nível de class, mas em um escopo menor. 2. Eu tento tornar o object reutilizável para que um gerenciador de tempo de vida diferente possa ser aplicado.

Se isso não for viável, eu uso uma fábrica para indicar que o usuário do contrato injetado é proprietário e deve assumir a responsabilidade por ele.

Há uma ressalva: mudar um implementador de contrato de não descartável para descartável será uma mudança urgente. Nesse momento, a interface não será mais registrada, mas a interface para a fábrica. Mas acho que isso se aplica também a outros cenários. Esquecer de usar um contêiner filho, a partir desse momento, causará problemas de memory. A abordagem de fábrica causará uma exceção de resolução do IoC.

Algum código de exemplo:

 using System; using Microsoft.Practices.Unity; namespace Test { // Unity configuration public class ConfigurationExtension : UnityContainerExtension { protected override void Initialize() { // Container.RegisterType(); Use factory instead Container.RegisterType, InjectionFactory>(); } } #region General utility layer public interface IInjectionFactory where T : class { T Create(); } public class InjectionFactory : IInjectionFactory where T1 : T2 where T2 : class { private readonly IUnityContainer _iocContainer; public InjectionFactory(IUnityContainer iocContainer) { _iocContainer = iocContainer; } public T2 Create() { return _iocContainer.Resolve(); } } #endregion #region data layer public class DataService : IDataService, IDisposable { public object LoadData() { return "Test data"; } protected virtual void Dispose(bool disposing) { if (disposing) { /* Dispose stuff */ } } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } } #endregion #region domain layer public interface IDataService { object LoadData(); } public class DomainService { private readonly IInjectionFactory _dataServiceFactory; public DomainService(IInjectionFactory dataServiceFactory) { _dataServiceFactory = dataServiceFactory; } public object GetData() { var dataService = _dataServiceFactory.Create(); try { return dataService.LoadData(); } finally { var disposableDataService = dataService as IDisposable; if (disposableDataService != null) { disposableDataService.Dispose(); } } } } #endregion } 

Eu acho que, em geral, a melhor abordagem é simplesmente não descartar algo que foi injetado; você tem que assumir que o injetor está fazendo a alocação e desalocação.

Isso depende da estrutura de DI. Algumas estruturas permitem que você especifique se deseja uma instância compartilhada (sempre usando a mesma referência) para cada dependência injetada. Nesse caso, você provavelmente não deseja descartar.

Se você pode especificar que você quer uma instância única injetada, então você vai querer desfazer (uma vez que estava sendo construído especificamente para você). Mas eu não estou tão familiarizado com o Unity – você teria que checar os documentos sobre como fazer isso funcionar. É parte do atributo com MEF e alguns outros que eu tentei, no entanto.

Colocar uma fachada na frente do contêiner pode resolver isso também. Além disso, você pode estendê-lo para acompanhar um ciclo de vida mais rico, como desligamentos de serviço e startups ou transições de estado de ServiceHost.

Meu contêiner tende a viver em um IExtension que implementa a interface IServiceLocator. É uma fachada para unidade e permite fácil access em serviços WCF. Além disso, tenho access aos events de serviço do ServiceHostBase.

O código que você acabar tentará ver se algum singleton registrado ou qualquer tipo criado implementa qualquer uma das interfaces que a fachada controla.

Ainda não permite a disposição em tempo hábil como você está vinculado a esses events, mas ajuda um pouco.

Se você quiser desfazer-se de uma maneira oportuna (também conhecida como agora, após o desligamento do serviço). Você precisa saber que o item obtido é descartável, é parte da lógica de negócios descartá-lo, portanto, IDisposable deve fazer parte da interface do object. E provavelmente deve haver verificação de expectativas relacionadas ao método de descarte ser chamado.

No framework Unity, existem duas maneiras de registrar as classs injetadas: como singletons (você obtém sempre a mesma instância da class ao resolvê-la) ou obtém uma nova instância da class em cada resolução.

No último caso, você tem a responsabilidade de descartar a instância resolvida, uma vez que não é necessária (o que é uma abordagem bastante razoável). Por outro lado, quando você descarta o contêiner (a class que lida com as resoluções de object), todos os objects singleton são automaticamente descartados também.

Portanto, aparentemente não há problemas com objects descartáveis ​​injetados com o framework Unity. Eu não sei sobre outras estruturas, mas suponho que, desde que uma estrutura de injeção de dependência seja sólida o suficiente, ela certamente lida com essa questão de uma forma ou de outra.