Как мне наследовать IDisposable? - PullRequest
22 голосов
/ 02 декабря 2009

Имена классов были изменены, чтобы защитить невинных .

Если у меня есть интерфейс с именем ISomeInterface. У меня также есть классы, которые наследуют интерфейс, FirstClass и SecondClass. FirstClass использует ресурсы, которые должны быть утилизированы. Второй класс не.

Итак, вопрос в том, где мне наследовать от IDisposable? Обе следующие опции кажутся не идеальными:

1) Сделать FirstClass наследующим IDisposable . Затем любой код, который имеет дело с ISomeInterfaces, должен знать, следует ли им распоряжаться. Это пахнет как тесная связь со мной.

2) Заставить ISomeInterface наследовать IDisposable . Затем любой класс, который наследуется от него, должен реализовать IDisposable, даже если нечего располагать. Метод Dispose по существу будет пустым, за исключением комментариев.

# 2 мне кажется правильным выбором, но мне интересно, есть ли альтернативы.

Ответы [ 6 ]

20 голосов
/ 02 декабря 2009

Если есть реальная вероятность того, что абстрактный объект (интерфейс или абстрактный класс) может быть одноразовым, он должен реализовать его. Stream, например, само по себе не нужно IDisposable и IEnumerator<T> ...

Абстрактный базовый класс может быть проще, поскольку вы можете иметь реализацию по умолчанию (пустую) Dispose() тогда и, возможно, шаблон финализатора / Dispose (bool), т.е.

void IDisposable.Dispose() { Dispose(true); GC.SuppressFinalize(this); }
protected virtual void Dispose(bool disposing) {}
~BaseType() {Dispose(false);}
3 голосов
/ 02 декабря 2009

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

Например, в BCL IDataReader реализует IDisposable, даже если можно было бы представить реализации считывателя данных, которые не имеют внешних ресурсов, которые необходимо утилизировать.

2 голосов
/ 02 декабря 2009

Если вы хотите, чтобы весь ваш код имел дело с ISomeInterfaces, то да, все они должны быть одноразовыми.

Если нет, то код, который создает FirstClass, должен его использовать:

using (FirstClass foo = new FirstClass()) {
    someObjectThatWantsISomeInterface.Act(foo);
}

в противном случае вы всегда можете использовать что-то вроде этого метода расширения:

public static void DisposeIfPossible(this object o) {
    IDisposable disp = o as IDisposable;
    if (disp != null)
        disp.Dispose();
}

// ...
someObject.DisposeIfPossible(); // extension method on object

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

2 голосов
/ 02 декабря 2009

Это зависит от вашего интерфейса, но я бы склонился к # 2. Если у вас есть две реализации ISomeInterface, и вам нужно утилизировать только одну, то есть вероятность, что вам потребуется рефакторинг.

Обычно, когда вы привязываетесь к интерфейсу, лучше, чтобы этот интерфейс наследовал IDisposable, а не базовый класс; если ваш интерфейс не наследует IDisposable, вы должны привести к IDisposable, чтобы избавиться от объекта, и это создает риск InvalidCast ...

1 голос
/ 28 сентября 2012

Все ответы, написанные до сих пор, упускают ключевой момент: необходимо, чтобы базовый тип или базовый интерфейс реализовали IDisposable, если вполне вероятно, что код, который ожидает экземпляр базового класса, мог бы иначе приобрести владение экземпляра, который требует избавления, не осознавая этого. Наиболее распространенный сценарий, с помощью которого это может происходить, - это метод фабрики; ярким примером является IEnumerable<T> / IEnumerator<T>. Большинство перечислителей не требуют очистки, но код, вызывающий IEnumerable<T>.GetEnumerator, как правило, не имеет конкретной причины полагать, что возвращенный перечислитель действительно потребует очистки, или полагать, что это не так. Как правило, все реализации IEnumerator<T> реализуют IDisposable и все потребители вызывают Dispose в возвращенном перечислителе, чем когда потребители проверяют, реализует ли возвращаемый тип IDisposable, и вызывают его, если так.

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

1 голос
/ 02 декабря 2009

Мой совет - идти к корню, а не напрямую к конкретному классу. Пункт 2 лежит в основе, и вы руководствуетесь каким-то контрактом от FirstClass. Если вы знаете, что классы должны реализовывать некоторый интерфейс, вы должны убедиться, что интерфейс, с которым они подписывают контракт, наследует IDisposable

...