Почему autofac дважды утилизирует мой одноразовый? - PullRequest
3 голосов
/ 18 февраля 2012

Привет, мне кажется, это неправильно.Это так, как это было задумано?

Мой одноразовый класс:

class C : IDisposable
{
    public void Dispose()
    {
        Console.WriteLine("Disposing C");
    }
}

Регистрация:

cb.RegisterInstance(new C());

Использование:

using (IContainer container = BuildContainer())
{
    var c = container.Resolve<C>();
    Console.WriteLine("C resolved");
}

Выход:

C resolved
Disposing C
Disposing C

Я думаю, что вызывать Dispose несколько раз для одного и того же объекта плохо.

Примечание. Когда я регистрирую класс следующим образом

cb.Register(c => new C());

Он утилизируется только один раз.Почему разница?

Ответы [ 4 ]

12 голосов
/ 18 февраля 2012

Я думаю, что неправильно вызывать Dispose несколько раз для одного и того же объекта.

Это не так, Dispose считается безопасным для многократного вызова. Из документации: «Объект не должен вызывать исключение, если его метод Dispose вызывается несколько раз.» Поскольку предполагается, что это безопасно, вы не должны полагаться на другие библиотеки, вызывающие его только один раз, и нет ничего плохого в изменениях, которые, по вашему мнению, не должны иметь значения, вызывая множество Dispose вызовов.

6 голосов
/ 24 февраля 2012

Изучение исходного кода AutoFac выявляет причину такой двойной утилизации.

Метод расширения RegisterInstance упаковывает предоставленный экземпляр (в данном случае c) в ProvidedInstanceActivator и сохраняет его в коллекции активаторов. Когда компоновщик утилизируется, тогда любые сохраненные активаторы утилизируются, как и содержащиеся в них объекты (при условии, что они поддерживают IDisposable).

Разрешенные объекты (с помощью Resolve) также отслеживаются в контейнере LifetimeScope, который также удаляет свои объекты при утилизации строителя.

Поскольку AutoFac не определяет, был ли разрешенный объект изначально предоставленным экземпляром, такое двойное удаление происходит только для этого стиля регистрации.

Это может быть или не быть ошибкой, в зависимости от вашей точки зрения, но безвредно, если одноразовый предмет написан правильно, как упоминалось @ hvd.

2 голосов
/ 18 февраля 2012

Hvd был прав: вы должны подготовить свой одноразовый класс, чтобы разрешить несколько Dispose вызовов.Это правильный способ реализации одноразового шаблона, как описано в нескольких местах, таких как MSDN или CodeProject

Вернуться к исходному вопросу:

Autofacавтоматически вызывать Dispose для каждого компонента, который разрешается в течение срока действия жизненного цикла, если компонент IDisposable (в вашем примере область действия - это время существования контейнера, но это может быть любая другая область времени жизни).Так что это один «Утилизация C».

И если вы зарегистрировали компонент с помощью RegisterInstance, то при вызове контейнера он вызывает Dispose (даже если они никогда не были разрешены!).Это второй «Утилизация C».

Вы можете отключить эту дополнительную утилизацию, используя ExternallyOwned:

builder.RegisterInstance(new C()).ExternallyOwned();

Когда вы использовали cb.Register(c => new C());, тогда Autofac создает экземпляр Cдля вас, когда вы звоните Resolve, чтобы он мог отследить его (он не "принадлежит извне"), поэтому он вызывает только один раз Dispose, когда заканчивается область litetime.

Вы можете прочитать больше о от AutofacДетерминированное удаление .

0 голосов
/ 18 февраля 2012

В шаблоне Dispose очень легко ошибиться, вы должны думать о нем как о потенциально двухэтапной вещи.

  1. Очистка любых выделенных неуправляемых ресурсов. (например, освобождение памяти или вызов функций отключения)
  2. Очистка любых управляемых ресурсов.

Самый распространенный способ сделать это - то, что я люблю называть шаблоном двойного расположения .

public class MyClass : IDisposable {
    private bool _disposed = false;
    public void Dispose(){
        Dispose(true);
        GC.SuppressFinalize(this); // stop the GC clearing us up, 
    }
    protected virtual Dispose(bool disposing){
        if ( !_disposed ){
            if ( disposing ){
                // someone called Dispose()

                // dispose any other IDispose objects we have
            }
            _disposed = true;
        }
    }
}

Если это требуется вашему коду завершения работы, возможно, вам придется заблокировать содержимое метода Dispose (bool).

...