Вопрос

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

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

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

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

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

Это было полезно?

Решение

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

Другие советы

Предупреждение в основном подсказывает вам реализовать Шаблон утилизации в твоем классе.

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

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?) и эти ресурсы должны быть освобождены, вам это нужно.Производный класс всегда может добавить финализатор, если он действительно в этом нуждается, но принуждение всех производных классов иметь финализатор путем помещения его в базовый класс приводит к тому, что на все производные классы влияет снижение производительности завершаемых объектов, когда эти накладные расходы могут быть необязательными.

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

Я использую шаблон 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 #, написал в блоге о том, почему сообщения об ошибках должны быть "диагностическими, но не предписывающими:опишите проблему, а не решение".Читайте здесь.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top