2011-07-21 14 views
12

Sé que esto se ha discutido hasta el cansancio ... pero tengo un problema con la forma en que Windsor rastrea los objetos transitorios identificables.Castle Windsor Transient Disposables

Entiendo los beneficios de dejar que Windsor administre mis IDiposables ... pero no me gusta.

¿Qué sucede si quiero envolver mi componente en un bloque de uso? El codificador supondría que el recurso se limpiaría al final del bloque de uso, ¿verdad? Wrong - Dispose se invocará, pero Windsor retendrá la instancia hasta que se lance explícitamente. Todo esto está bien y bien para mí, ya que sé lo que estoy haciendo ... pero ¿qué pasa con otro desarrollador que está codificando una clase y quiere usar un IDisposable de la misma manera que cualquier otro IDisposable usualmente utilizado, en un bloque de uso?

using(factory.CreateInstance()) 
{ 
    .... 
} 

se ve mucho más claro para mí que:

MyDisposable instance; 
try 
{ 
    instance = factory.GetInstance(); 
} 
finally 
{ 
    factory.Release(instance); 
} 

Con el fin de disponer verdaderamente mis casos y tenerlos elegibles para GC, necesito hacer referencia a la WindsorContainer o utilizar una fábrica de mecanografiado que expone un comunicado método. Eso significa que la única forma aceptable de usar componentes IDisposable es usar una fábrica de tipografía. Esto no es bueno, en mi opinión ... ¿y si alguien agrega la interfaz IDisposable a un componente existente? Todos los lugares que esperan que se inyecte el componente deberán cambiarse. Eso es realmente malo en mi opinión. (Por supuesto, en un escenario sin DI, tendría que cambiar para llamar a Dispose también ... pero con Windsor, cada lugar tendrá que cambiar para usar una fábrica de tipeo, que es un cambio mucho mayor).

Ok, es justo, puedo usar un ReleasePolicy personalizado ¿verdad? ¿Qué tal esto?

public class CustomComponentsReleasePolicy : AllComponentsReleasePolicy 
{ 
    public override void Track(object instance, Burden burden) 
    { 
     if (burden.Model.LifestyleType == LifestyleType.Pooled) 
      base.Track(instance, burden); 
    } 
} 

Ok, genial, mi IDisposable Transient components será GC'd ahora.

¿Qué sucede si quiero usar TypedFactory para que mi clase pueda producir muchas instancias de un tipo?

public interface IMyFactory 
{ 
    MyDisposable GetInstance(); 
    void Release(MyDisposable instance); 
} 

[Singleton] 
public class TestClass 
{ 
    public TestClass(IMyFactory factory) { } 
} 

Ok, así, por ejemplo, llamando lanzamiento en la fábrica no hará nada para llamar a Dispose() en MyDisposable, ya que no se realiza un seguimiento MyDisposable ....

¿Cómo puedo superar estas dificultades?

Gracias.

+1

Creo que voy a necesitar un blog acerca de este –

+0

Gracias. Lo leeré :) – Jeff

Respuesta

10

Antes que nada, ¿cómo sabe las preocupaciones de cierre relacionadas con un objeto que no creó? No tiene control sobre la creación del objeto porque no lo creó usted mismo (la fábrica lo hizo por usted). Cuando combina la resolución ioc con la eliminación de los consumidores (llamando .Desea en lugar de factory.Release) está introduciendo el requisito de que usted objeta sabe cómo fue creado, pero no se creó a sí mismo. Consideremos el siguiente ejemplo:

“Componente” es algo a resolver a través del contenedor, pero desea disponer del mismo

public class Component : IDisposable 
{ 
    private readonly IAmSomething _something; 

    public Component(IAmSomething something) 
    { 
     _something = something; 
    } 

    public void Dispose() 
    { 
     // Problem 1: the component doesnt know that an implementation of IAmSomething might be disposable 
     // Problem 2: the component did not create "something" so it does not have the right to dispose it 
     // Problem 3: What if an implementation of "something" has a depenency on a disposable instance deep down in its object graph? 

     // this is just bad.. 
     IDisposable disposable = _something as IDisposable; 

     if (disposable != null) 
      disposable.Dispose(); 

    } 
} 

public interface IAmSomething 
{ 

} 

public class SomethingA : IAmSomething 
{ 

} 

public class SomethingB : IAmSomething, IDisposable 
{ 
    public void Dispose() 
    { 
    } 
} 

Como se muestra arriba cierre definitivo puede ser complejo y sencillo, no veo cómo me Puedo manejar esto con gracia, especialmente cuando Windsor hace esto por mí. Si su base de código está llena con el anti-patrón de localizador de servicios (http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/) puedo ver cómo esto se convierte en un problema (no estoy diciendo que el código sea), pero entonces realmente se trata de problemas mucho más grandes.

using(factory.CreateInstance()) 
{ 
    .... 
} 

se ve mucho más claro para mí que: ...

bien la instrucción using es una convención, no hay ningún error de tiempo de compilación si lo omite, por lo que desde mi punto de vista, el try/finalmente con lanzamiento es solo otra convención, aunque un poco más detallada. Por ejemplo, podría acortar el try/finally mediante la creación de un ayudante, como por ejemplo:

[TestFixture] 
public class SomeCastleTests 
{ 
    [Test] 
    public void Example() 
    { 
     var container = new WindsorContainer(); 

     // you would of course use a typed factory instead in real word 

     using (var usage = new ComponentUsage<IAmSomething>(container.Resolve<IAmSomething>, container.Release)) 
     { 
      // use.. 
      usage.Component 
     } 
    } 
} 

public class ComponentUsage<T> : IDisposable where T : class 
{ 
    private Func<T> _create; 
    private Action<T> _release; 

    private T _instance; 

    public ComponentUsage(Func<T> create, Action<T> release) 
    { 
     _create = create; 
     _release = release; 
    } 

    public T Component 
    { 
     get 
     { 
      if (_instance == null) 
       _instance = _create(); 

      return _instance; 
     } 
    } 

    public void Dispose() 
    { 
     if (_instance != null) 
     { 
      _release(_instance); 
      _instance = null; 
     } 
    } 
} 

Esto no es bueno, en mi opinión ... ¿y si alguien añade la interfaz IDisposable a un componente existente? Cada lugar que espera el componente que se inyectará tendrá que cambiar.

No entiendo esta afirmación. Cómo se libera un componente y cuándo lo necesita son diferentes preocupaciones, si un componente se proporciona como una dependencia de constructor, agregar IDisposable no cambia nada. La clase que obtiene la dependencia a través del constructor no la creó y, por lo tanto, no es responsable de liberarla.

0

Como piggy back, puede resolver algunos de los otros puntos que ha mencionado al crear un interceptor que libera su objeto por usted. Lo hago por una unidad de trabajo. El interceptor se ve así:

public class UnitOfWorkReleaseOnDispose : IInterceptor 
{ 
    private readonly IUnitOfWorkFactory unitOfWorkFactory; 

    public UnitOfWorkReleaseOnDispose(IUnitOfWorkFactory unitOfWorkFactory) 
    { 
     this.unitOfWorkFactory = unitOfWorkFactory; 
    } 

    public void Intercept(IInvocation invocation) 
    { 
     invocation.Proceed(); 
     this.unitOfWorkFactory.DestroyUnitOfWork((IUnitOfWork)invocation.Proxy); 
    } 
} 

Mi código de registro es el siguiente:

  Component.For<IUnitOfWork>() 
       .ImplementedBy<TransactionalUnitOfWork>() 
       .LifestyleTransient() 
       .Interceptors<UnitOfWorkReleaseOnDispose>() 
        .Proxy.Hook(h => h.Service<InterfaceMethodsOnlyProxyGenerationHook<IDisposable>>()) 

gancho El proxy sólo está allí para decir que sólo quiero proxy de los métodos de interfaz IDiposable. Esa clase es el siguiente:

public class InterfaceMethodsOnlyProxyGenerationHook<TInterface> : IProxyGenerationHook 
{ 
    public void MethodsInspected() 
    { 

    } 

    public void NonProxyableMemberNotification(Type type, System.Reflection.MemberInfo memberInfo) 
    { 

    } 

    public bool ShouldInterceptMethod(Type type, System.Reflection.MethodInfo methodInfo) 
    { 
     return typeof(TInterface) == type; 
    } 
} 

que también requiere ser registrado desde que estoy usando la sobrecarga de gancho que estoy usando:

  Component.For<IProxyGenerationHook>() 
       .ImplementedBy<InterfaceMethodsOnlyProxyGenerationHook<IDisposable>>() 
       .LifestyleSingleton() 
Cuestiones relacionadas