2009-06-12 11 views
41

Finalmente estoy envolviendo mi cabeza con IoC y DI en C#, y estoy luchando con algunos de los bordes. Estoy usando el contenedor de Unity, pero creo que esta pregunta se aplica de manera más amplia.¿Cómo concilia IDisposable e IoC?

Usando un contenedor IoC para dispensar instancias que implementen IDisposable me asusta! ¿Cómo se supone que debes saber si debes desechar()? Es posible que la instancia se haya creado solo para usted (y por lo tanto, debe desecharla), o podría ser una instancia cuya vida útil se administre en otro lugar (y, por lo tanto, será mejor que no lo haga). ¡Nada en el código te lo dice, y de hecho esto podría cambiar según la configuración! Esto me parece mortal.

¿Puede algún experto en IoC describir buenas formas de manejar esta ambigüedad?

+1

Mi solución es utilizar un IoC con una gestión adecuada y bien codificado-vida: autofac y el castillo de Windsor tiene tal (aunque trabajan de forma ligeramente diferente); La Unidad 2.1 simplemente falla cuando se trata de transitorios bajo los administradores de por vida predeterminados. – user2864740

Respuesta

7

AutoFac maneja esto al permitir la creación de un contenedor anidado. Cuando el contenedor termina, automáticamente descarta todos los objetos IDisposable dentro de él. Más here.

.. Al resolver los servicios, Autofac rastrea los componentes desechables (IDisposable) que se resuelven. Al final de la unidad de trabajo, se deshace del alcance de por vida asociado y Autofac limpiará/eliminará automáticamente los servicios resueltos.

+0

Muy interesante. No sabía nada sobre AutoFac. No creo que sea demasiado difícil hacer algo como esto con Unity. Voy a tener que pensar en esto un poco. ¡Gracias! –

+1

Puede ser difícil hacer eso con Unity, ya que la eliminación determinística se ha incorporado a la arquitectura de Autofac desde el principio. –

+1

Castle Windsor es el otro contenedor que le permite hacer eso, ya sea utilizando sub-contenedor, estilo de vida delimitado o liberando explícitamente sus componentes usando el método 'container.Release' –

2

Creo que, en general, el mejor enfoque es simplemente no deshacerse de algo que ha sido inyectado; debes asumir que el inyector está haciendo la asignación y la desasignación.

+3

Esto es muy inconveniente, ya que con frecuencia quiero inyectar objetos desechables. En general, la semántica de IDisposable requiere que el creador/propietario del objeto la elimine en el momento correcto. Si el contenedor es responsable, entonces el usuario de la instancia necesita decirle al contenedor cuando haya terminado. Esto es fácil de olvidar –

+1

Downvoted, ya que la última parte de su respuesta es mala. La mayoría de los contenedores tienen una forma de manejar esto, ya sea contenedores anidados o algo así como el método StructureMaps 'ReleaseAndDisposeAllHttpScopedObjects'. Aún necesita averiguar cómo van a desechar los desechables ... a menos que quiera quedarse sin conexiones y cosas así. – Andy

2

Esto depende del marco DI. Algunos marcos le permiten especificar si desea una instancia compartida (siempre utilizando la misma referencia) para cada dependencia inyectada. En este caso, lo más probable es que no desee deshacerse.

Si puede especificar que desea una instancia única inyectada, deseará deshacerse de ella (ya que fue creada específicamente para usted). Aunque no estoy tan familiarizado con Unity, tendrías que consultar los documentos para saber cómo hacer que esto funcione allí. Es parte del atributo con MEF y algunos otros que he probado, sin embargo.

17

Definitivamente no desea llamar a Dispose() en un objeto que se ha inyectado en su clase. No puede asumir que usted es el único consumidor. Su mejor opción es envolver el objeto no administrado de alguna interfaz administrada:

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

Eso es sólo un ejemplo, me gustaría utilizar File.ReadAllText (ruta) si estuviera tratando de leer un archivo de texto en una cadena.

Otro método consiste en inyectar una fábrica y gestionar el objeto mismo:

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

Pero sí quiero inyectar objetos IDisposibles, y además, puedo o puedo No es necesario desechar() de ellos en el sitio de la inyección, según la configuración. Su primer ejemplo funciona cuando se accede al recurso subyacente de forma transitoria, pero no ayuda si el recurso es más persistente. En el segundo ejemplo, me parece extraño inyectar una fábrica de esta manera; esa es una de las principales funciones del modelo IoC en primer lugar. Si aplico IoC a este concepto, parece que termino en la pregunta original. Creo que el contenedor en sí tiene que participar en esto, en la AutoFac. –

+8

@Mr. Putty - la inyección de dependencia no elimina la necesidad de fábricas, solo hace que un (ab) uso de ellas sea innecesario. Por ejemplo, cuando no se conoce el tipo de dependencia concreta hasta el tiempo de ejecución, o para las dependencias condicionales costosas (objetos que ni siquiera se necesitan y que requieren una gran cantidad de recursos para crear), es posible que desee inyectar una fábrica en lugar de el objeto mismo. Dependiendo de la compatibilidad de su DI framework para IDisposable, la inyección de fábrica puede ser la mejor manera, sin duda es más transparente que muchas de las alternativas. (+1) –

+0

Creo que este es un buen consejo. Nunca inyecte IDisposables directamente; utilice envoltorios administrados en su lugar, o bien, use un tiempo de vida que permita la eliminación explícita en casos específicos, como un contexto db, como HttpContextScoped en StructureMap o PerRequestLifetimeManager en Unity. –

1

En el marco de la Unidad, hay dos maneras de registrar las clases inyectadas: como simple (se obtiene siempre la misma instancia de la clase cuando lo resuelve), o tal como obtener una nueva instancia de la clase en cada resolución.

En el último caso, tiene la responsabilidad de eliminar la instancia resuelta una vez que no la necesita (lo cual es un enfoque bastante razonable). Por otro lado, cuando desecha el contenedor (la clase que maneja las resoluciones de objeto), todos los objetos individuales se eliminan también automáticamente.

Por lo tanto, aparentemente no hay problemas con los objetos desechables inyectados con el marco de Unity.No sé sobre otros frameworks, pero supongo que mientras un framework de inyección de dependencia sea lo suficientemente sólido, seguramente maneja este problema de una forma u otra.

+1

Gran artículo sobre esto: http://www.ladislavmrnka.com/2011/03/unity-build-in-lifetime-managers/ – TrueWill

+2

Es un enfoque * terrible *, nada "razonable" acerca de haber inyectado-y-dejado de registrar -objetos que necesitan ser eliminados. Esto es complicado porque además no es posible que los componentes desechen manualmente sus dependencias inyectadas porque la vida útil es desconocida: singleton, transitoria, etc. Unity (2.x) falla completamente en que bajo tiempos de vida estándar los transitorios nunca son eliminados por el contenedor . – user2864740

+0

"En este último caso ..." El cliente no sabe si es el primero o el último caso, y no debería saberlo. Debe permanecer inconsciente de la vida útil elegida del objeto inyectado. No estoy de acuerdo con la observación del usuario2864740 de que Unity falla. Creo que es una característica del marco con la que tenemos que lidiar, desafortunadamente. –

2

Poner una fachada en frente del contenedor puede resolver esto también. Además, puede ampliarlo para realizar un seguimiento de un ciclo de vida más rico, como paradas de servicio y puestas en marcha o transiciones de estado de ServiceHost.

Mi contenedor tiende a vivir en un IExtension que implementa la interfaz IServiceLocator. Es una fachada para la unidad y permite un fácil acceso en los servicios de WCf. Además, tengo acceso a los eventos del servicio de ServiceHostBase.

El código que finaliza intentará ver si un singleton registrado o cualquier tipo creado implementa cualquiera de las interfaces que la fachada realiza un seguimiento.

Todavía no permite deshacerse de manera oportuna ya que está vinculado a estos eventos, pero ayuda un poco.

Si desea deshacerse de manera oportuna (es decir, ahora v.s. al cerrar el servicio). Debe saber que el artículo que obtiene es desechable, es parte de la lógica comercial disponer de él, por lo que IDisposable debe ser parte de la interfaz del objeto. Y probablemente debería haber verificación de las expectativas de los estatutos relacionados con el método de eliminación.

+0

¿No pierde la ventaja de la inyección de constructor de esta manera? –

4

Esto también me ha dejado perplejo. Aunque no estoy contento con eso, siempre llegué a la conclusión de que nunca mejor devolver un objeto IDisposable de forma transitoria.

Recientemente, reformulé la pregunta para mí: ¿Esto realmente es un problema de IoC, o un problema de .NET Framework? La eliminación es incómoda de todos modos. No tiene un propósito funcional significativo, solo técnico. Entonces, es más un problema de marco con el que tenemos que lidiar, que un problema de IoC.

Lo que me gusta de DI es que puedo solicitar un contrato dándome la funcionalidad, sin tener que preocuparme por los detalles técnicos. No soy el dueño. Sin conocimiento sobre la capa en la que se encuentra. Sin conocimiento sobre qué tecnologías se requieren para cumplir con el contrato, no se preocupe por la duración. Mi código se ve bien y limpio, y es altamente comprobable. Puedo implementar responsabilidades en las capas a las que pertenecen.

De modo que si hay una excepción a esta regla que me exige organizar la duración, hagamos esa excepción. Si me gusta o no. Si el objeto que implementa la interfaz me obliga a desecharlo, quiero saberlo, ya que desde entonces me activé para usar el objeto lo más breve posible. Un truco al resolverlo usando un contenedor para niños que se desecha un tiempo más tarde todavía podría causarme mantener el objeto con vida por más tiempo del que debería. La vida útil permitida del objeto se determina al registrar el objeto. No por la funcionalidad que crea un contenedor hijo y se aferra a eso durante un cierto período.

De modo que mientras los desarrolladores tengamos que preocuparnos por la eliminación (¿cambiará alguna vez?) Intentaré inyectar el menor número posible de objetos desechables transitorios. 1. Intento que el objeto no sea identificable, por ejemplo, al no mantener los objetos desechables en el nivel de clase, pero en un ámbito más pequeño. 2. Intento que el objeto sea reutilizable para que se pueda aplicar un administrador de vida diferente.

Si esto no es posible, utilizo una fábrica para indicar que el usuario del contrato inyectado es el propietario y debe asumir la responsabilidad por ello.

Hay una advertencia: cambiar un implementador de contrato de no desechable a desechable será un cambio radical. En ese momento, la interfaz ya no estará registrada, sino la interfaz de fábrica.Pero creo que esto se aplica también a otros escenarios. Olvidarse de usar un contenedor para niños dará problemas de memoria a partir de ese momento. El enfoque de fábrica provocará una excepción de resolución de IoC.

un código de ejemplo:

using System; 
using Microsoft.Practices.Unity; 

namespace Test 
{ 
    // Unity configuration 
    public class ConfigurationExtension : UnityContainerExtension 
    { 
     protected override void Initialize() 
     { 
      // Container.RegisterType<IDataService, DataService>(); Use factory instead 
      Container.RegisterType<IInjectionFactory<IDataService>, InjectionFactory<IDataService, DataService>>(); 
     } 
    } 

    #region General utility layer 

    public interface IInjectionFactory<out T> 
     where T : class 
    { 
     T Create(); 
    } 

    public class InjectionFactory<T2, T1> : IInjectionFactory<T2> 
     where T1 : T2 
     where T2 : class 

    { 
     private readonly IUnityContainer _iocContainer; 

     public InjectionFactory(IUnityContainer iocContainer) 
     { 
      _iocContainer = iocContainer; 
     } 

     public T2 Create() 
     { 
      return _iocContainer.Resolve<T1>(); 
     } 
    } 

    #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<IDataService> _dataServiceFactory; 

     public DomainService(IInjectionFactory<IDataService> 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 
}