2011-07-21 32 views
12

Wiem, że zostało to omówione na ad nauseum ... ale mam problem ze sposobem, w jaki Windsor śledzi Transient IDisposable objects.Castle Windsor Transient Disposables

Rozumiem korzyści płynące z pozwolenia Windsor na zarządzanie swoimi IDiposabiliami ... ale nie podoba mi się to.

Co się stanie, jeśli chcę zawinąć mój komponent w blok przy użyciu? Koder mógłby założyć, że zasób zostanie oczyszczony na końcu bloku, prawda? Wrong - Dispose zostanie wywołany, ale Windsor będzie trzymał się instancji, dopóki nie zostanie wyraźnie zwolniony. To wszystko jest dla mnie dobre i dobre, ponieważ wiem, co robię ... ale co z innym programistą, który koduje klasę i chce używać IDisposable tak, jak zwykle używa się każdego innego IDisposable - w bloku używanym?

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

wygląda o wiele jaśniej mi niż:

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

Aby prawdziwie rozporządzania moich wystąpień i mieć je kwalifikować się do GC, muszę odwołać WindsorContainer lub użyj fabrykę wpisywanych że naraża uwolnienie metoda. Oznacza to, że jedynym akceptowalnym sposobem użycia komponentów IDisposable jest użycie fabryki maszynowej. To nie jest dobre, moim zdaniem ... co jeśli ktoś doda interfejs IDisposable do istniejącego komponentu? Każde miejsce, w którym oczekuje się wstrzyknięcia komponentu, będzie musiało ulec zmianie. To naprawdę złe w mojej opinii. (Przyznane, w scenariuszu bez DI, musiałoby się zmienić na wywołanie Dispose także ... ale z Windsor każde miejsce będzie musiało się zmienić, aby użyć maszynki do pisania, która jest znacznie większą zmianą).

OK, wystarczy, mogę użyć niestandardowej wersji ReleasePolicy? Co powiesz na to?

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

Ok, świetnie, mój IDisposable Elementy przejściowe będą teraz GC.

Co zrobić, jeśli chcę korzystać z TypedFactory, aby moja klasa mogła wygenerować wiele wystąpień typu?

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

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

Ok, dobrze, dla jednego, nazywając Release on fabrycznie nie zrobi nic, aby zadzwonić Dispose() na MyDisposable, ponieważ MyDisposable nie jest śledzone ....

Jak mogę przezwyciężyć te trudności?

Dzięki.

+1

myślę, że będę musiał blog o tym –

+0

Dzięki. Przeczytam to :) – Jeff

Odpowiedz

10

Po pierwsze, skąd wiadomo, że obawy o unieruchomienie związane z obiektem nie zostały utworzone? Nie masz kontroli nad tworzeniem obiektu, ponieważ nie stworzyłeś go sam (fabryka zrobiła to za ciebie). Po połączeniu rozwiązania ioc z usuwaniem przez konsumenta (wywoływanie .Dispose zamiast factory.Release) wprowadzasz wymóg, że obiekt wie, jak został utworzony, ale sam się nie stworzył. Rozważmy następujący przykład:

„część” jest coś rozwiązać przez pojemnik, ale chcesz wyrzucać z siebie

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() 
    { 
    } 
} 

Jak pokazano powyżej likwidacyjnych może być skomplikowane i proste, nie wiem, jak ja poradzę sobie z tym z wdziękiem, zwłaszcza gdy Windsor zrobi to za mnie. Jeśli twoja baza kodowa jest zaśmiecona anty-wzorcem lokalizatora serwisów (http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/), widzę, jak to staje się problemem (nie mówię, że jest to kod), ale tak naprawdę, jak dużo większych problemów.

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

wygląda o wiele jaśniej mi niż ...

Dobrze stosując twierdzenie jest konwencją, nie ma błędu czas kompilacji, jeśli pominąć, więc z mojego punktu widzenia try/wreszcie z wydaniem jest kolejną konwencją, choć nieco bardziej gadatliwą. Można na przykład skrócić try/wreszcie tworząc pomocnika, takich jak:

[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; 
     } 
    } 
} 

To nie jest dobre, moim zdaniem ... co jeśli ktoś doda interfejs IDisposable do istniejącego komponentu? Każde miejsce, w którym oczekuje się, że komponent, który ma zostać wstrzyknięty, będzie wymagało zmiany.

Nie rozumiem tego stwierdzenia. W jaki sposób komponent jest wydawany, a kiedy jest potrzebny, to różne problemy, jeśli komponent jest dostarczany jako zależność od konstruktora, dodanie IDisposable niczego nie zmienia. Klasa, która pobiera zależność przez konstruktor, nie utworzyła go i dlatego nie jest odpowiedzialna za jego zwolnienie.

0

Jako piggy back możesz rozwiązać niektóre z wymienionych punktów, tworząc przechwytywacz, który uwalnia twój obiekt. Robię to w związku z problemem dotyczącym jednostki pracy. Interceptor wygląda następująco:

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); 
    } 
} 

Mój kod rejestracyjny wygląda następująco:

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

hak Pełnomocnik jest tylko tam, aby powiedzieć, że chcę tylko do pełnomocnika IDiposable metody interfejsu. Że klasa wygląda następująco:

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; 
    } 
} 

który wymaga również być zarejestrowany, ponieważ używam przeciążenie haku że używam:

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