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

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

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

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

Microsoft.Design: измените ConnectionAccessor.Dispose () так, чтобы он вызывал Dispose (true), затем вызывал GC.SuppressFinalize для текущего экземпляра объекта (this или Me в Visual Basic), а затем возвращался.

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


person Sarah Vessels    schedule 09.11.2009    source источник
comment
Зачем вам нужно добавлять Dispose () в свой интерфейс? Если он унаследован от IDisposable, метод Dispose () уже является частью вашего интерфейса.   -  person Daniel Rodriguez    schedule 09.11.2009
comment
Сет, тебе нужно реализовать все элементы интерфейса. Если его не использовать, компиляция не будет.   -  person Henk Holterman    schedule 10.11.2009


Ответы (5)


Вы должны следовать общепринятой схеме реализации Dispose. Создание Dispose() виртуальным считается плохой практикой, потому что традиционный шаблон подчеркивает повторное использование кода в «управляемой очистке» (клиент 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 (сам по себе абстрактный), поэтому есть прецедент.

person Pavel Minaev    schedule 09.11.2009
comment
Я бы предпочел, чтобы людям посоветовали это сделать: nitoprograms.blogspot.com/2009/08/ - шаблон был бы намного проще, если бы в нем не было встроенной поддержки классов, которые смешивают управляемые и неуправляемые ресурсы. - person Roman Starkov; 04.05.2010
comment
Если какой-либо класс, производный от определенного класса, должен будет реализовать IDisposable, и если объект, который ожидает получить тип базового класса, может в конечном итоге содержать последнюю полезную ссылку на него, базовый класс должен реализовать IDisposable, даже если обширный большинству производных классов это не понадобится. - person supercat; 03.01.2011
comment
Разве этот шаблон не устарел со времен .net 2? Насколько я понимаю, неуправляемые ресурсы должны удерживаться какой-либо формой безопасного / критического дескриптора, а обычные классы, реализованные пользователем, больше не нуждаются в финализаторе. И их Dispose просто вызывает Dispose на ручке. - person CodesInChaos; 20.09.2011


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

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

person Doug Rohrer    schedule 09.11.2009
comment
Моя любимая жалоба. Большинство людей просто не пытаются понять это, и лучший совет, который они обычно получают, - просто реализовать шаблон Microsoft. Вот, на мой взгляд, лучший совет, чем официальный шаблон: nitoprograms.blogspot.com/2009/08/ - person Roman Starkov; 04.05.2010
comment
Это действительно хороший пост в блоге на эту тему - простой и слишком важный. - person Doug Rohrer; 03.06.2010

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

Я использую шаблон удаления, упомянутый в книге Брюса Вагнерса Эффективный 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;
    }
person akmad    schedule 09.11.2009

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

person Igor Lankin    schedule 09.11.2009
comment
Это не сообщение об ошибке компилятора. Это результат использования инструмента, который специально указывает на проблемные вещи. Нет ничего плохого в том, что это тоже указывает на решение. - person Pavel Minaev; 10.11.2009
comment
Конечно. Просто хотел поделиться ссылкой. Это пришло ко мне, потому что в сообщении не говорится о проблеме, а только о ее решении. Надеюсь, это было в предыдущем сообщении. - person Igor Lankin; 10.11.2009
comment
Это как бы касательно исходного вопроса. - person Michael Donohue; 10.11.2009