C# абстрактный метод Dispose

у меня есть абстрактный класс, который реализует IDisposable, вот так:

public abstract class ConnectionAccessor : IDisposable
{
    public abstract void Dispose();
}

в Visual Studio 2008 Team System я запустил анализ кода в своем проекте, и одно из появившихся предупреждений было следующим:

Microsoft.Дизайн: Изменить ' ConnectionAccessor.Dispose () ' так что он вызывает Dispose (true), а затем вызывает GC.SuppressFinalize на текущем экземпляре объекта ("это" или "меня" в Visual Basic), а затем возвращает.

это просто глупо говорить мне изменить тело абстрактного метода или я должен сделать что-то еще в любом производном экземпляре Dispose?

5 ответов


вы должны следовать обычной схеме для реализации Dispose. Делая Dispose() virtual считается плохой практикой, потому что обычный шаблон подчеркивает повторное использование кода в " управляемой очистке "(вызов клиента API Dispose() непосредственно или через using) и" неуправляемая очистка " (финализатор вызова GC). Чтобы напомнить, шаблон таков:

public class Base
{
    ~Base()
    {
        Dispose(false);
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this); // so that Dispose(false) isn't called later
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
             // Dispose all owned managed objects
        }

        // Release unmanaged resources
    }
}

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

для вашего случая, что вы должны сделать, это:

protected abstract void Dispose(bool disposing)

и оставить все как есть. Даже это имеет сомнительное значение, так как вы применяете свои производные классы для реализации Dispose теперь - и как вы знаете, что все они нуждаются в этом? Если вашему базовому классу нечего распоряжаться, но большинство производных классов, скорее всего, делают (за некоторыми исключениями, возможно), тогда просто предоставьте пустую реализацию. Это то, что System.IO.Stream (сам абстрактное), так что есть прецедент.


предупреждение в основном говорит вам реализовать Dispose pattern в своем классе.

результирующий код должен выглядеть так:

public abstract class ConnectionAccessor : IDisposable
{
    ~ConnectionAccessor()
    {
        Dispose(false);
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
    }
}

единственное, что у меня было бы с ответами, предоставленными до сих пор, это то, что все они предполагают, что вы нужно чтобы иметь финализатор,что не обязательно так. Существует довольно значительные издержки производительности, связанные с завершением, которые я не хотел бы накладывать на все мои производные классы, если бы это не было необходимо.

посмотреть этот блог Джо Даффи, который объясняет, когда вам может понадобиться или не понадобиться финализатор, и как правильно реализовать шаблон Dispose в любом случае.
Подводя итог сообщению Джо в блоге, Если вы не делаете что-то довольно низкоуровневое с неуправляемой памятью, вы не должны реализовывать финализатор. Как правило, если ваш класс содержит только ссылки на управляемые типы, которые реализуют IDisposable сами, вам не нужен финализатор (но должен реализовать IDisposable и утилизировать эти ресурсы). Если вы выделяете неуправляемые ресурсы непосредственно из своего кода (PInvoke?) и те ресурсы должны быть освобождены, вам нужен. Производный класс всегда может добавить финализатор, если он действительно в нем нуждается, но принуждение всех производных классов иметь финализатор, помещая его в базовый класс, заставляет все производные классы влиять на производительность завершаемых объектов, когда эти накладные расходы могут не потребоваться.


хотя это кажется немного похожим на NIT-picking, совет действителен. Вы уже указываете, что ожидаете, что любые подтипы ConnectionAccessor будут иметь что-то что они должны распоряжаться. Поэтому, кажется, лучше обеспечить правильную очистку (с точки зрения GC.SuppressFinalize call) базовым классом, а не полагаться на каждый подтип для этого.

Я использую шаблон dispose, упомянутый в книге Брюса Вагнера эффективное В C# который является в основном:

public class BaseClass : IDisposable
{
    private bool _disposed = false;
    ~BaseClass()
    {
        Dispose(false);
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(true);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (_disposed)
            return;

        if (disposing)
        {
            //release managed resources
        }

        //release unmanaged resources

        _disposed = true;
    }
}

public void Derived : BaseClass
{
    private bool _disposed = false;

    protected override void Dispose(bool disposing)
    {
        if (_disposed) 
            return;

        if (disposing)
        {
            //release managed resources
        }

        //release unmanaged resources

        base.Dispose(disposing);
        _disposed = true;
    }

предупреждение интересно, хотя. Эрик Липперт, один из дизайнеров C#, написал в блоге о том, почему сообщения об ошибках должны быть "диагностическими, но не предписывающими: описывайте проблему, а не решение". читать здесь.