c# - ¿Cómo se puede reconciliar IDisposable e IoC?
inversion-of-control unity-container (7)
Creo que, en general, el mejor enfoque es simplemente no desechar algo que se ha inyectado; debes asumir que el inyector está haciendo la asignación y la desasignación.
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.
¡Usar 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.
¿Pueden los expertos en IoC describir buenas formas de manejar esta ambigüedad?
Definitivamente no desea llamar a Dispose () en un objeto que se inyectó en su clase. No puede asumir que usted es el único consumidor. Su mejor opción es envolver su objeto no administrado en alguna interfaz administrada:
public class ManagedFileReader : IManagedFileReader
{
public string Read(string path)
{
using (StreamReader reader = File.OpenRead(path))
{
return reader.ReadToEnd();
}
}
}
Eso es solo un ejemplo, usaría File.ReadAllText (ruta) si estuviera tratando de leer un archivo de texto en una cadena.
Otro enfoque es inyectar una fábrica y administrar el objeto usted mismo:
public void DoSomething()
{
using (var resourceThatShouldBeDisposed = injectedFactory.CreateResource())
{
// do something
}
}
En el marco de Unity, hay dos formas de registrar las clases inyectadas: como singletons (siempre obtienes la misma instancia de la clase cuando la resuelves), o cuando obtienes una nueva instancia de la clase en cada resolución.
En el caso posterior, usted 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.
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 que se inyecte una instancia única, deseará deshacerse de ella (ya que se construyó 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.
Esto también me ha desconcertado a menudo. 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 por mí mismo: ¿Es esto realmente 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 que me proporcione 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.
Entonces, si hay una excepción a esta regla que me exige organizar la vida, 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.
Entonces, mientras los desarrolladores deban preocuparse 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 factible, utilizo una fábrica para indicar que el usuario del contrato que se inyecta 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
}
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 finalice 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 frente al cierre del 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.
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 .
.. A medida que resuelve 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.