Когда CLR говорит, что у объекта есть финализатор? - PullRequest
9 голосов
/ 11 декабря 2008

Я знаю, что в C #, если вы напишите ~MyClass(), это в основном означает override System.Object.Finalize(). Таким образом, независимо от того, пишете ли вы деструктор или нет, каждый тип в CLR будет иметь метод Finalize() (минимум System.Object).

1] Значит ли это, что каждый объект по умолчанию имеет финализатор?

2] На каком основании CLR принимает решение о том, что объект должен быть помещен в очередь завершения?

Я спрашиваю об этом, потому что у меня был класс, скажем ManagedResourceHolder, который реализовал IDisposable, но не вызывал GC.SuppressFinalize(this) в его методе IDisposable.Dispose(). Класс не содержал никаких неуправляемых ресурсов, и в методе ~ManagedResourceHolder() не было необходимости, что, в свою очередь, означало отсутствие необходимости в вызове GC.SuppressFinalize(this), так как не было finalizer .

3] В контексте приведенного выше сценария, является ли всегда необходимым для предоставления финализатора при реализации IDisposable? (даже в классе, который не содержит неуправляемых ресурсов)

Правило FxCop CA1816 давало мне нарушение по этому поводу, и ответ, который я получил здесь , когда я спросил на форуме CA на MSDN, смутил меня.

Спасибо.

Ответы [ 4 ]

16 голосов
/ 11 декабря 2008

Вопросы 1 и 2 : CLR в основном проверяет, переопределен ли финализатор. Если это не так, он рассматривает это как отсутствие финализатора.

Преимущество наличия финализатора в System.Object заключается в том, что компиляторы знают, что они могут всегда вызывать base.Finalize() in. Это позволяет избежать проблем с версиями. Рассмотрим мир без System.Object.Finalize():

  • System.Object (без финализации)
  • Acme.BaseClass (без финализации)
  • MyCompany.DerivedClass (Завершить)

Без метода Finalize в объекте финализатор в MyCompany.DerivedClass не может ничего вызывать. Что приводит к проблеме, когда версия 2 Acme.BaseClass выходит с финализатором. Если вы не перекомпилируете MyCompany.DerivedClass, экземпляр DerivedClass будет завершен без вызова BaseClass.Finalize, что явно является плохим.

Теперь рассмотрим ту же ситуацию с System.Object.Finalize - компилятор вставляет вызов base.Finalize автоматически в DerivedClass.Finalize, который в версии 1 просто вызывает реализацию no-op в System. Объект. Когда выйдет версия 2 Acme.BaseClass, вызов base.Finalize вызовет (без перекомпиляции DerivedClass) вызов BaseClass.Finalize.

Вопрос 3 : Нет, вам не нужно иметь финализатор только потому, что вы реализуете IDisposable. Финализаторы должны использоваться только для неуправляемых ресурсов, которые больше не будут очищать - то есть те, к которым у вас есть прямая ссылка . Например, предположим, что у вас есть класс, который имеет FileStream переменную-член. Вы хотите реализовать IDisposable, чтобы вы могли закрыть поток как можно скорее, если вызывающий абонент помнит - но если они не не забудут вызвать Dispose(), поток станет пригодным для сбора мусора в то же время, как ваш объект. Поверьте, что FileStream имеет соответствующий финализатор (или ссылку на что-то еще с финализатором и т. Д.) Вместо того, чтобы пытаться очистить его в вашем собственном финализаторе.

Начиная с .NET 2.0, с классом SafeHandle , невероятно редко требуется для вашего собственного финализатора.

3 голосов
/ 11 декабря 2008

1: Он действительно считается (в полезном смысле) только если он был переопределен

2: как определено 1, и GC.SuppressFinalize не был вызван (плюс перерегистрация и т. Д.)

3: конечно, нет; на самом деле, если вы не работаете непосредственно с неуправляемым ресурсом, у вас не должно быть финализатора. Вы не должны добавлять финализатор только потому, что он IDisposable - но вещи, которые имеют финализаторы, также обычно должны быть IDisposable.

0 голосов
/ 11 декабря 2008

1) Да (в силу наследования)

2) Ничто не содержит ссылку на экземпляр класса (что делает его пригодным для финализации)

3) Да (почему следует реализовывать IDisposable, если он не требует от пользователя явного вызова dispose? Классы подключения в .net используют неуправляемый ресурс под капотом, и если вы не вызываете dispose на нем, он будет зависать к нему. Поскольку время GC неизвестно, соединение будет оставаться открытым до этого времени)

Это мое понимание.

Я могу ошибаться. В этом случае эксперты исправят ситуацию для меня.

0 голосов
/ 11 декабря 2008
  1. Нет, это не так. Только переопределенный Finalize() будет подсчитываться CLR.
  2. Наличие финализатора, как определено выше.
  3. Нет, это не всегда необходимо. Это просто хороший шаблон. Я имею в виду, никто не заставляет вас делать это. Но это хорошо, если у вас есть неуправляемые ресурсы, поскольку, если кто-то забудет их утилизировать, неуправляемый ресурс когда-нибудь будет освобожден. FxCop не применяет строгие правила. Он навязывает хорошие шаблоны, которые могут привести к неудаче в будущем, если вы не позаботитесь о них.

ОБНОВЛЕНИЕ: Каждый класс отвечает за управление своими собственными ресурсами. Из-за особенностей абстракции и инкапсуляции объектно-ориентированных парадигм, потребитель класса не должен заботиться о том, какие ресурсы он имеет, косвенно. Следовательно, вы должны либо вручную высвободить ресурсы, которыми вы владеете (то, чем вы владеете - это то, чем вы непосредственно владеете , когда вы смотрите на другие вещи как черный ящик ), или оставить это ГХ для отпустите их. Для неуправляемых ресурсов у вас нет возможности оставить его в GC, поэтому вы должны разблокировать его вручную. В этом смысле SafeHandle, о котором упоминал Джон, - это управляемая абстракция неуправляемого ресурса , поэтому его следует рассматривать как ценный управляемый ресурс (который является черным ящиком, управляющим завершением работы этого неуправляемого ресурса). .

...