Почему Dispose () должен быть не виртуальным? - PullRequest
30 голосов
/ 01 сентября 2010

Я новичок в C #, поэтому извиняюсь, если это очевидный вопрос.

В примере MSDN Dispose определяемый ими метод Dispose не является виртуальным.Это почему?Мне это кажется странным - я ожидаю, что дочерний класс IDisposable, имеющий собственные неуправляемые ресурсы, просто переопределит Dispose и вызовет base.Dispose () в нижней части своего собственного метода.

Спасибо!

Ответы [ 8 ]

14 голосов
/ 01 сентября 2010

Типичное использование состоит в том, что Dispose () перегружен, с открытым, не виртуальным методом Dispose () и виртуальным защищенным Dispose (bool). Открытый метод Dispose () вызывает Dispose (true), и подклассы могут использовать этот защищенный виртуальный метод для освобождения своих собственных ресурсов и вызывать base.Dispose (true) для родительских классов.

Если класс, которому принадлежит открытый метод Dispose (), также реализует финализатор, то финализатор вызывает Dispose (false), указывая, что защищенный метод Dispose (bool) был вызван во время сборки мусора.

Если есть финализатор, то открытый метод Dispose () также отвечает за вызов GC.SuppressFinalize (), чтобы убедиться, что финализатор больше не активен и никогда не будет вызываться. Это позволяет сборщику мусора нормально обрабатывать класс. Классы с активными финализаторами обычно собираются только в крайнем случае, после очистки gen0, gen1 и gen2.

8 голосов
/ 01 сентября 2010

Это, конечно, не очевидный.Этот шаблон был особенно выбран, потому что он хорошо работает в следующих сценариях:

  • Классы, которые не имеют финализатор.
  • Классы, которые имеют финализатор.
  • Классы, которые могут быть унаследованы от.

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

5 голосов
/ 01 сентября 2010

Хотя методы в интерфейсе не являются «виртуальными» в обычном смысле, тем не менее они все же могут быть реализованы в классах, которые их наследуют.Очевидно, это удобство, встроенное в язык C #, позволяющее создавать методы интерфейса без использования ключевого слова virtual и реализовывать методы без использования ключевого слова override.

Следовательно, хотя интерфейс IDisposableсодержит метод Dispose(), перед ним нет ключевого слова virtual, и для его реализации не нужно использовать ключевое слово override в классе наследования.

Обычный шаблон Disposeреализовать Dispose в своем собственном классе, а затем вызвать Dispose в базовом классе, чтобы он мог высвободить ресурсы, которыми он владеет, и так далее.

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

http://msdn.microsoft.com/en-us/library/fs2xkftw.aspx

3 голосов
/ 01 сентября 2010

Метод Dispose не должен быть виртуальным, поскольку он не является точкой расширения для реализации одноразового шаблона.Это означает, что базовый доступный класс в иерархии создаст политику верхнего уровня (алгоритм) для удаления и передаст детали другому методу (Dispose(bool)). Эта политика верхнего уровня стабильна и не должна быть переопределена дочерними классами. Если вы разрешите дочерним классам переопределять ее, они могут не вызывать все необходимые части алгоритма, которые могут оставить объект внесовместимое состояние.

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

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

3 голосов
/ 01 сентября 2010

Вызовы через интерфейс всегда виртуальные, независимо от того, будет ли «обычный» вызов прямым или виртуальным.Если метод, который фактически выполняет работу по утилизации, не является виртуальным, за исключением случаев, когда он вызывается через интерфейс, тогда всякий раз, когда класс захочет избавиться от него, он должен будет убедиться, что он сам ссылается на iDisposable и вызывает его.*

В коде шаблона ожидается, что не виртуальная функция Dispose всегда будет одинаковой в родительском и дочернем элементах [просто вызывая Dispose (True)], поэтому никогда не нужно ее переопределять.Вся работа выполняется в виртуальном Dispose (Boolean).

Честно говоря, я думаю, что использование шаблона Dispose немного глупо в тех случаях, когда нет оснований ожидать, что классы-потомки будут напрямую хранить неуправляемые ресурсы.В первые дни .net классам часто требовалось напрямую хранить неуправляемые ресурсы, но сегодня в большинстве ситуаций я вижу нулевые потери от простой реализации Dispose () напрямую.Если будущему классу-потомку необходимо использовать неуправляемые ресурсы, он может и обычно должен заключать эти ресурсы в свои собственные объекты Finalizable.

С другой стороны, для некоторых видов методов могут быть преимущества наличия неВиртуальный метод базового класса, задача которого состоит в том, чтобы связать его с защищенным виртуальным методом и заставить виртуальный метод вызываться Dispose(bool) на самом деле не хуже, чем VirtDispose(), даже если предоставленный аргумент довольно бесполезен.Например, в некоторых ситуациях может быть необходимо, чтобы все операции над объектом были защищены блокировкой, которой владеет объект базового класса.Если не виртуальный базовый класс Dispose получит блокировку перед вызовом виртуального метода, это избавит все базовые классы от необходимости беспокоиться о самой блокировке.

2 голосов
/ 01 сентября 2010

Причина того, что пример метода Dispose () не является виртуальным, заключается в том, что он принимает весь процесс в этом примере и оставляет подклассы с виртуальным методом Dispose (bool dispose) для переопределения.Вы заметите, что в этом примере он хранит логическое поле, чтобы гарантировать, что логика Dispose не будет вызываться дважды (возможно, один раз из IDisposable и один раз из деструктора).Подклассы, которые переопределяют предоставленный виртуальный метод, не должны беспокоиться об этом нюансе.Вот почему основной метод Dispose в этом примере является не виртуальным.

1 голос
/ 01 сентября 2010

У меня есть довольно подробное объяснение схемы утилизации здесь . По сути, вы предоставляете метод protected для переопределения, более надежный для неуправляемых ресурсов.

0 голосов
/ 01 сентября 2010

Если базовый класс имеет ресурсы, которые необходимо очистить во время Dispose(), то наличие виртуального метода Dispose, который переопределяется наследующим классом, предотвращает освобождение этих ресурсов, если только наследующий класс специально не вызывает метод Dispose базы. Лучший способ реализовать это - каждый производный класс реализовать IDisposable.

...