Правильный способ реализации Finalize и Dispose (когда родительский класс реализует IDisposable) - PullRequest
4 голосов
/ 14 декабря 2010

Я реализовывал Finalize и Dispose в моих классах, я реализовал IDisposable в своем родительском классе и переопределил перегрузку Dispose (bool) в своих дочерних классах. Я не был уверен

  1. использовать ли дублированную переменную isDisposed (как она уже есть в базовом классе) или нет?
  2. Реализовать ли финализатор в дочернем классе или нет?

Обе эти вещи выполняются в приведенном здесь примере -

http://guides.brucejmack.biz/CodeRules/FxCop/Docs/Rules/Usage/DisposeMethodsShouldCallBaseClassDispose.html

В то время как пример в этой статье MSDN не имеет ни одного из этих двух - http://msdn.microsoft.com/en-us/library/b1yfkh5e.aspx

тогда как этот пример в MSDN не завершен - http://msdn.microsoft.com/en-us/library/ms182330.aspx

Ответы [ 5 ]

6 голосов
/ 14 декабря 2010

Финализатор очень редко бывает полезен.Документация, на которую вы ссылаетесь, не совсем полезна - она ​​предлагает следующие довольно круговые рекомендации:

Реализация Финализировать только на объектах, которые требуют финализации

Это отличный примерЗадавать вопрос, но это не очень полезно.

На практике в подавляющем большинстве случаев вам не нужен финализатор.(Одна из кривых обучения, которую должны пройти разработчики .NET, - это обнаружить, что в большинстве мест, где они думают, что им нужен финализатор, они этого не делают.) Вы пометили это как (среди прочего) вопрос WPF, иЯ бы сказал, что почти всегда было бы ошибкой помещать финализатор на объект пользовательского интерфейса.(Таким образом, даже если вы находитесь в одной из необычных ситуаций, в которых, как оказывается, требуется финализатор, эта работа не относится ни к какому коду, связанному с WPF.)

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

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

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

Основная ситуация, в которой финализаторы кажутся полезными, это взаимодействие: например, предположим, что вы используете P / Invoke для вызова некоторого неуправляемого API, и этот API возвращает вам дескриптор.Возможно, есть какой-то другой API, который вам нужно вызвать, чтобы закрыть этот дескриптор.Поскольку это все неуправляемые вещи, .NET GC не знает, что это за дескрипторы, и ваша задача - убедиться, что они вычищены, и в этот момент финализатор является разумным ... за исключением практики, это почти всегда лучшеиспользовать SafeHandle для этого сценария.

На практике единственными местами, где я использовал финализаторы, были а) эксперименты, предназначенные для исследования того, что делает ГХ, и б) диагностический код, предназначенный для обнаружениякое-что о том, как конкретные объекты используются в системе.Ни один из видов кода не должен заканчиваться вводом в производство.

Таким образом, ответ на вопрос, нужно ли вам "также использовать финализатор в дочернем классе": если вам нужно спросить, тогда ответ - нет.

Что касается дублирования флага ... другие ответы дают противоречивые советы здесь.Основные моменты: 1) вам нужно позвонить на базу Dispose и 2) ваш Dispose должен быть идемпотентом.(То есть, не имеет значения, вызывается ли он один, два, 5, 100 раз - он не должен жаловаться, если он вызывается более одного раза.) Вы можете реализовать это так, как вам нравится - логический флагодним способом, но я часто обнаруживал, что достаточно установить для некоторых полей значение null в моем методе Dispose, после чего устраняется необходимость в отдельном логическом флаге - вы можете сказать, что Dispose уже был вызванпотому что вы уже установили эти поля на null.

Многие рекомендации по IDisposable крайне бесполезны, потому что они касаются ситуации, когда вам нужен финализатор, но на самом деле это очень необычный случай.Это означает, что многие люди пишут IDisposable реализации, которые намного сложнее, чем необходимо.На практике большинство классов называют Стивеном Клири категорию "уровень 1" в статье, которую jpierson связал с .И для этого вам не нужны все вещи GC.KeepAlive, GC.SuppressFinalize и Dispose(bool), которые загромождают большинство примеров.Жизнь на самом деле намного проще в большинстве случаев, как показывает совет Клири для этих типов "уровня 1".

2 голосов
/ 14 декабря 2010

Необходим дубликат

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

Когда вы определяете класс как IDisposable, вы просто говорите GC, что я забочусь о его процедуре очистки, и вы должны SuppressFinilize для этого класса, поэтому GC удалит его из своей очереди.Если вы не позвоните GC.SupressFinalize(this), ничего не случится с классом IDisposable.Поэтому, если вы реализуете его, как я уже говорил, вам не понадобится Finilizer, поскольку вы только что сказали GC не завершать его.

1 голос
/ 14 декабря 2010

Правильный способ реализации IDisposable зависит от того, есть ли у вас неуправляемые ресурсы, принадлежащие вашему классу.Точный способ реализации IDisposable - это все еще то, с чем не все разработчики согласны, и у некоторых, таких как Стивен Клири , есть твердое мнение об одноразовой парадигме в целом.

см .: Реализация Finalize и Disposeдля очистки неуправляемых ресурсов

Документация для IDisposable интерфейса также объясняет это кратко, а в этой статье говорится о некоторых из тех же вещей, но также о MSDN.

Требуется ли в базовом классе дублированное логическое поле "isDisposed".Похоже, что это в основном просто полезное соглашение, которое можно использовать, когда сам подкласс может добавить дополнительные неуправляемые ресурсы, которые необходимо утилизировать.Поскольку Dispose объявлен виртуальным, вызов Dispose для экземпляра подкласса всегда вызывает первый вызов метода Dispose этого класса, который, в свою очередь, вызывает base.Dispose как последний шаг, дающий возможность очистить каждый уровень в иерархии наследования.Поэтому я бы, вероятно, суммировал это следующим образом: если у вашего подкласса есть дополнительные неуправляемые ресурсы сверх того, что принадлежит базе, то вам, вероятно, будет лучше иметь собственное логическое поле isDisposed для отслеживания его удаления в транзакционной природе внутри его метода Dispose, но как Ianупоминает в своем ответе, есть и другие способы представить уже настроенное состояние.

0 голосов
/ 14 декабря 2010

Реализуйте финализатор только в том случае, если объект содержит информацию о вещах, нуждающихся в очистке, и эта информация в некоторой форме отличается от ссылок на объекты на другие объекты, нуждающиеся в очистке (например, дескриптор файла, хранящийся как Int32).Если класс реализует финализатор, он не должен содержать сильные ссылки Object на любые другие объекты, которые не требуются для очистки.Если он будет содержать другие ссылки, часть, ответственная за очистку, должна быть разделена на собственный объект с помощью финализатора, а основной объект должен содержать ссылку на это.В этом случае главный объект не должен иметь финализатор.

Производные классы должны иметь финализаторы, только если цель базового класса должен был поддерживать один.Если цель класса не сосредоточена вокруг финализатора, нет особого смысла позволять производному классу добавлять его, поскольку производные классы почти наверняка не должны этого делать (даже если им нужно добавить неуправляемые ресурсы, они должны поместить ресурсы всвой класс и просто держи ссылку на него).

0 голосов
/ 14 декабря 2010

1) Не нужно дублировать

2) Внедрение финализатора поможет избавиться от предметов, которые не были удалены явным образом. Но не гарантируется . Это хорошая практика.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...