Подтвердить что ты не робот

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 не отслеживается....

Как я могу преодолеть эти трудности?

Спасибо.

4b9b3361

Ответ 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()