Castle Windsor Transient Disposables
Я знаю, что это обсуждалось ad nauseum... но у меня есть проблема с тем, как Windsor отслеживает объекты Transient IDisposable.
Я понимаю преимущества, позволяющие Windsor управлять моими IDiposables... но мне это не нравится.
Что произойдет, если я захочу обернуть мой компонент в блок использования? Кодер сделал бы предположение, что ресурс будет очищен в конце используемого блока, верно? Неправильно - Dispose будет вызываться, но Windsor будет удерживать экземпляр, пока он не будет явно выпущен. Для меня все хорошо и хорошо, так как я знаю, что я делаю... но как насчет другого разработчика, который кодирует класс и хочет использовать IDisposable, как обычно используется любой другой IDisposable - в блоке использования?
using(factory.CreateInstance())
{
....
}
выглядит намного понятнее, чем:
MyDisposable instance;
try
{
instance = factory.GetInstance();
}
finally
{
factory.Release(instance);
}
Чтобы действительно удалить мои экземпляры и предоставить им право на GC, мне нужно обратиться к WindsorContainer или использовать типизированный factory, который предоставляет метод выпуска. Это означает, что единственным приемлемым способом использования IDisposable-компонентов является использование типизированного factory. Это нехорошо, на мой взгляд... что, если кто-то добавит интерфейс IDisposable к существующему компоненту? Каждое место, которое ожидает, что компонент будет введен, будет необходимо изменить. Это очень плохо, на мой взгляд. (Разумеется, в сценарии без DI, ему нужно будет также изменить вызов Dispose... но с Windsor каждое место нужно будет изменить, чтобы использовать напечатанный factory, что намного больше изменилось).
Хорошо, честно говоря, я могу использовать пользовательскую версию ReleasePolicy? Как насчет этого?
public class CustomComponentsReleasePolicy : AllComponentsReleasePolicy
{
public override void Track(object instance, Burden burden)
{
if (burden.Model.LifestyleType == LifestyleType.Pooled)
base.Track(instance, burden);
}
}
Хорошо, отлично, мои IDisposable Transient компоненты теперь будут GC'd.
Что делать, если я хочу использовать TypedFactory, чтобы мой класс мог создавать множество экземпляров типа?
public interface IMyFactory
{
MyDisposable GetInstance();
void Release(MyDisposable instance);
}
[Singleton]
public class TestClass
{
public TestClass(IMyFactory factory) { }
}
Хорошо, для одного, вызов Release на factory ничего не сделает для вызова Dispose() в MyDisposable, поскольку MyDisposable не отслеживается....
Как я могу преодолеть эти трудности?
Спасибо.
Ответы
Ответ 1
Прежде всего, как вы знаете проблемы с отключением, связанные с объектом, который вы не создали? У вас нет контроля над созданием объекта, потому что вы его сами не создали (factory сделал это для вас). Когда вы совмещаете разрешение ioc с потребительским распоряжением (вызов .Dispose вместо factory.Release), вы вводите требование о том, чтобы объект объекта знал, как он был создан, но он не создавал себя. Рассмотрим следующий пример:
"Компонент" - это то, что вы разрешаете через контейнер, но вы хотите избавиться от себя.
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()
{
}
}
Как показано выше, снятие с эксплуатации может быть сложным, и я просто не вижу, как я могу справиться с этим изящно, особенно когда Windsor делает это для меня. Если ваша кодовая база усеяна анти-шаблоном сервиса-локатора (http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/), я вижу, как это становится проблемой (я не говорю что вы код), но тогда вы действительно, как много больших проблем.
using(factory.CreateInstance())
{
....
}
выглядит намного понятнее, чем:...
Хорошо, что оператор using - это соглашение, нет ошибки времени компиляции, если вы ее опустите, поэтому с моей точки зрения try/finally с выпуском - это просто еще одно соглашение, хотя и немного более подробное. Вы могли бы, например, сократить try/finally, создав хелпер, например:
[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;
}
}
}
Это не хорошо, на мой взгляд... что, если кто-то добавит IDisposable интерфейс к существующему компоненту? Каждое место, которое ожидает компонент, который нужно ввести, должен будет измениться.
Я не понимаю этого утверждения. Как компонент выпущен, и когда вам это нужно, это разные проблемы, если компонент предоставляется как зависимость конструктора, добавление IDisposable ничего не меняет. Класс, который получает зависимость через конструктор, не создал его и поэтому не несет ответственности за его освобождение.
Ответ 2
Как копилка, вы можете решить некоторые из других моментов, о которых вы упомянули, создав перехватчик, который освобождает ваш объект за вас. Я делаю это для беспокойства единицы работы. Перехватчик выглядит следующим образом:
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);
}
}
Мой регистрационный код выглядит следующим образом:
Component.For<IUnitOfWork>()
.ImplementedBy<TransactionalUnitOfWork>()
.LifestyleTransient()
.Interceptors<UnitOfWorkReleaseOnDispose>()
.Proxy.Hook(h => h.Service<InterfaceMethodsOnlyProxyGenerationHook<IDisposable>>())
Ключ прокси-сервера существует только для того, чтобы сказать, что я хочу только проксировать методы интерфейса IDiposable. Этот класс выглядит следующим образом:
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;
}
}
Что также необходимо зарегистрировать, так как я использую перегрузку крюка, который я использую:
Component.For<IProxyGenerationHook>()
.ImplementedBy<InterfaceMethodsOnlyProxyGenerationHook<IDisposable>>()
.LifestyleSingleton()