В шаблоне IDisposable должен ли базовый класс разрешать производным классам совместно использовать свой удаленный флаг?

В настоящее время я работаю над исправлением кодовой базы С#, которая не имеет хорошего шаблона использования Dispose.

Это большая кодовая база, ресурсоемкая кодовая база, использующая множество пользовательских неуправляемых библиотек C++ на низком уровне.

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

Пытаясь свести к минимуму дублирование кода, мы рассмотрели некоторые вспомогательные классы удаления, и поэтому мой вопрос:

Если базовый класс реализует стандартный шаблон Dispose, он должен разрешить совместное использование флага disposed, т.е. отмечен как защищенный?

Чтобы уточнить, я имею в виду, должно ли быть только одно логическое состояние в иерархии наследования, которое определяет, был ли экземпляр объекта удален, или должно быть частное логическое значение на каждом шаге лестницы наследования?

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


person morechilli    schedule 03.07.2009    source источник


Ответы (4)


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

Например, рассмотрим сценарий, в котором у вас есть свойство Disposed только для чтения в самом базовом классе. Поле резервного копирования имеет значение true только в методе Dispose (удаление) базового класса. Это означает, что свойство Disposed может когда-либо возвращать true только в том случае, если вызывается базовый класс Dispose (конечно, за исключением злого отражения). Это позволяет базовому классу предоставлять исполняемый контракт.

Теперь рассмотрим обратное, где есть защищенный сеттер. Теперь любой класс может произвольно установить для свойства Disposed значение true, ничего не удаляя. Это создает возможность для Dispose вернуть true, когда ничего не удаляется.

Я бы выбрал первый вариант, потому что он обеспечивает наиболее юридическую силу контракта.

person JaredPar    schedule 03.07.2009

Я бы рекомендовал иметь свойство Disposed в базовом классе с общедоступным геттером и защищенным сеттером.

person SLaks    schedule 03.07.2009
comment
Спасибо, но поясните, почему вы предпочитаете единую защищенную собственность частной собственности на каждом уровне? - person morechilli; 03.07.2009
comment
Меньше дублирования кода. Поскольку вы не можете распоряжаться каждым слоем отдельно, нет смысла иметь отдельные свойства. Кроме того, если у вас есть отдельные свойства, какие из них следует предоставлять клиентам классов? - person SLaks; 03.07.2009

В случае, если у вас есть методы, которые должны делать что-то другое, когда класс был удален, например, генерировать исключение, я бы создал только защищенное свойство получателя для базового класса, которое считывает частное поле в базовом классе. Таким образом, вы позволяете любому наследнику узнать, может ли он выполнить операцию.

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

person jmservera    schedule 03.07.2009

В дополнение к ответу JaredPar я бы добавил, что не всегда необходимо наличие флага _disposed. Очень часто другие «связанные с ресурсами» поля объекта, естественно, предоставляют способ представления удаленного состояния.

Например, внешний COM-объект, который должен быть Shutdown, прежде чем он будет отброшен, будет представлен ссылкой на COM-объект, поэтому соответствующая часть Dispose будет следующей:

if (_comObject != null)
{
    _comObject.Shutdown();
    _comObject = null;
}

Это можно безопасно запускать несколько раз, не вызывая Shutdown более одного раза, как это требуется. Другие методы, пытающиеся использовать _comObject после Dispose, получат NullReferenceException, или, в идеале, эти методы будут проверять поле _comObject и выдавать ObjectDisposedException.

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

Так что это вряд ли будет полезно для производных классов, даже если это была безопасная идея (а это не так, как объясняет JaredPar).

person Daniel Earwicker    schedule 03.07.2009