деструкторы c #: распоряжаться "шаблоном" и лучшими практиками - PullRequest
2 голосов
/ 15 ноября 2011

Я знаю разницу в значении и использовании деструкторов и финализаторов в c #.

Однако, как правило, ответ на вопрос "должен ли я ..." отвечает "не используйте деструкторы, скорее используйтешаблон утилизации, как показано в MSDN".Эрик Липперт пишет вполне решительно против ненужного использования деструкторов.

Но этот "шаблон" рекомендует писать деструктор как таковой ~T() { Dispose(false); }.Заявленная причина в том, что это «запасной вариант», который вызывается в случае, если программист забывает вызвать Dispose().Конечно, это игнорирует тот факт, что финализаторы неопределены в своей работе и могут даже никогда не запускаться.

Следовательно:

  1. Если я использую шаблон удаления, должен ли я такжепредоставить деструктор?Кстати, я распоряжаюсь только управляемыми ресурсами (например, Entity Framework DataContext).

  2. Если я предоставляю деструктор: если мой класс является производным от IDisposable, которыйможет уже предоставить деструктор, тогда я тоже должен его предоставить?Я думал, что в таком случае никто никогда не напишет деструктор, однако доктора говорят, что он все равно автоматически вызовет деструктор базового класса.

Ответы [ 5 ]

18 голосов
/ 15 ноября 2011

На самом деле я не буду отвечать на ваши два вопроса, но выскажу мнение по поводу:

Заявленная причина в том, что это «запасной вариант», который вызывается в случае, если программист забывает вызвать Dispose().

Если требование состоит в том, чтобы вызывающий метод передавал, скажем, ненулевую строку, то вы вполне можете создать исключение, если они передают ноль, верно? Абонент нарушил договор; это исключительное поведение, поэтому вы бросаете исключение. Вы не думаете, о, звонящий «забыл» передать действительный аргумент, я думаю, что я приму плохой ввод и солдат. Это эффективно меняет контракт метода с «null неприемлемо и приведет к исключению» к «null допустимо и рассматривается как пустая строка», например.

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

Либо явное удаление объекта необходимо , либо это не так. Если это необходимо, убедитесь, что пользователь делает это. В противном случае скрывает свою ошибку .

12 голосов
/ 15 ноября 2011

Если я использую шаблон удаления, должен ли я также предоставить деструктор?Между прочим, я распоряжаюсь только управляемыми ресурсами (например, Entity Framework DataContext).

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

Это часть сложности IDisposable - на самом деле должно быть больше, чем стандартная реализация, в зависимости отИспользование.В этом случае вы инкапсулируете ресурс, который реализует IDisposable.Таким образом, важно разрешить вашему пользователю (косвенно) распоряжаться этими ресурсами, но вам не нужно обрабатывать деструктор, поскольку не существует неуправляемого ресурса, который вы непосредственно «владеете».Я расскажу об этом в Части 3 моей серии о IDisposable , если вы хотите получить более подробную информацию.


, если я предоставлю деструктор: если мой класс получен из IDisposable, который уже может предоставить деструктор, тогда я должен предоставить один тоже?Я думал, что в таком случае никто никогда не напишет деструктор, однако доктора говорят, что он все равно автоматически вызовет деструктор базового класса.

В этом случае базовый класс должен предоставлять защищенный методформа protected virtual void Dispose(bool disposing).Вы бы поместили туда свою логику очистки ресурсов, так как деструктор базового класса обрабатывает вызов этого метода для вас.Подробнее см. Часть 2 моей серии по IDisposable .

2 голосов
/ 15 ноября 2011

Очень сложно добавить к этому вопросу что-то, что еще не затронуто отличными ответами здесь.

Тогда я попытаюсь дать альтернативу шаблону удаления, который пропагандируется в MSDN. Мне никогда не нравился этот метод Dispose(bool), поэтому я думаю, что этот шаблон лучше , если вам определенно нужен деструктор :

public class BetterDisposableClass : IDisposable {

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

  protected virtual void CleanUpManagedResources() { 
    // ...
  }
  protected virtual void CleanUpNativeResources() {
    // ...
  }

  ~BetterDisposableClass() {
    CleanUpNativeResources();
  }

}

Но, так как вы уже обнаружили, что он вам действительно не нужен, ваш паттерн намного проще :

public class ManagedDisposable : IDisposable {

  // ...

  public virtual void Dispose() {
    _otherDisposable.Dispose();
  }

  IDisposable _otherDisposable;

}
2 голосов
/ 15 ноября 2011

Если вы пишете класс, вы не можете заставить всех, кто использует этот класс, следовать ожидаемому шаблону IDisposable. Вот почему вам нужен запасной вариант деструктора.

Даже если «все» - это «только вы», вы человек и иногда допускаете ошибки.

0 голосов
/ 16 ноября 2011
 Or if you know that your object that you are trying to dispose of Implements IDisposable
why not do something like this

StreamWriter streamWrt = null
try
{
streamWrt = new StreamWrite();
... do some code here
}
catch (Exception ex)
{
 Console.WriteLine(ex.Message)
}
Finally
{
  if (streamWrt != null)
  {
    ((IDisposable)streamWrt).Dispose();
  }
}
...