2009-11-09 6 views
7

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

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

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

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

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

+0

Зачем вам нужно добавить Dispose() в свой интерфейс? Если он унаследован от IDisposable, метод Dispose() уже является частью вашего интерфейса. –

+0

Сет, вам нужно реализовать все интерфейсные элементы. Оставлять его не собиралось. –

ответ

12

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

+0

Я бы предпочел, чтобы людям это посоветовали: http://nitoprograms.blogspot.com/2009/08/how-to-implement-idisposable-and.html - образец был бы намного проще, если бы у него не было встроенной поддержки классов, в которых используются управляемые и неуправляемые ресурсы. –

+0

Если какой-либо класс, полученный из определенного класса, должен будет реализовать IDisposable, и если объект, который ожидает получить тип базового класса, может в конечном итоге сохранить последнюю полезную ссылку, базовый класс должен реализовать IDisposable, даже если подавляющее большинство производных классов им не понадобится. – supercat

+0

Разве это не устаревший шаблон с .net 2? Я понимаю, что неуправляемые ресурсы должны храниться с помощью какой-либо формы safehandle/критического дескриптора, а обычные классы, реализованные пользователем, больше не нуждаются в финализаторе. И их 'Dispose' просто вызывает' Dispose' на дескрипторе. – CodesInChaos

10

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

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

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

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

    protected virtual void Dispose(bool disposing) 
    { 
    } 
} 
+3

+1 для обозначения шаблона и указав, что Dispose (bool) должен быть виртуальным/абстрактным методом, который производные классы должны реализовывать или переопределять, а не Dispose(). – Yoopergeek

-1

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

+1

Это не сообщение об ошибке компилятора. Это результат запуска инструмента, который конкретно указывает на проблемные вещи. В этом нет ничего плохого, указывающего на решение. –

+0

Несомненно. Просто хотел поделиться ссылкой. Он пришел ко мне, потому что сообщение не указывает проблему, но дает решение только. Надеюсь, это было предыдущее сообщение. –

+0

Это своего рода тангенциальный исходный вопрос. –

1

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

Я использую шаблон Dispose упоминается в книге Брюса Wagners Effective 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; 
    } 
3

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

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

+0

Моя любимая ручка. Большинство людей просто не утруждают себя попытками понять это, и лучшим советом, который они обычно получают, является просто внедрение шаблона Microsoft. Вот что я думаю, это гораздо лучший совет, чем «официальный» шаблон: http://nitoprograms.blogspot.com/2009/08/how-to-implement-idisposable-and.html –

+0

Это действительно хороший пост в блоге на тема - простая и тоже точка. –