Когда CLR сообщает, что у объекта есть финализатор?

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

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

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

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

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

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

Спасибо.


person mherle    schedule 11.12.2008    source источник


Ответы (4)


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

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

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

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

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

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

Начиная с .NET 2.0, с SafeHandle class, вам невероятно понадобится собственный финализатор.

person Jon Skeet    schedule 11.12.2008
comment
Правильно ли я предполагаю, что нет необходимости вызывать GC.SuppressFinalize(this) в методе IDisposable.Dispose() в классе ManagedResourceHolder (поскольку у него действительно нет финализатора для подавления)? - person mherle; 11.12.2008
comment
Ага. Не нужно подавлять то, чего не существует. - person Jon Skeet; 11.12.2008
comment
Использование финализатора где-либо, кроме класса базового уровня, который либо запечатан, либо предназначен для финализации, кажется мне изворотливым, если только единственная цель финализатора не состоит в том, чтобы предупредить о невозможности правильного удаления объекта. Если объекту необходимо гарантировать, что что-то будет очищено, когда оно выйдет за пределы области видимости, он должен инкапсулировать информацию и импульс, необходимые для проведения такой очистки, в небольшой выделенный класс. Только этот небольшой класс нужно будет сохранить во время финализации. - person supercat; 19.02.2011

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

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

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

person Marc Gravell    schedule 11.12.2008

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

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

person mmx    schedule 11.12.2008
comment
Это нехороший шаблон для добавления финализатора, ПОТОМУ ЧТО вы IDisposable. Это дорогая ошибка. Существует множество причин для IDisposable, которые не указывают на использование финализатора. - person Will Dean; 11.12.2008
comment
Кажется, я упомянул, есть ли у вас неуправляемые ресурсы в ответе. - person mmx; 11.12.2008
comment
Да, но вы должны четко указать, что это только в том случае, если у вас напрямую есть неуправляемый ресурс. Например, если вы только что получили FileStream, позвольте ему завершиться — вам это не нужно. В наши дни вам следует очень редко, если вообще когда-либо, иметь собственных прямых дескрипторов. Используйте SafeHandle. - person Jon Skeet; 11.12.2008
comment
Я не думаю, что управляемый класс является неуправляемым ресурсом, не так ли? Я считаю FileStream управляемым ресурсом, и мне все равно, что он содержит. Но когда я сам создаю что-то в неуправляемом мире, это однозначно неуправляемый ресурс. - person mmx; 11.12.2008
comment
@Mehrdad: Тогда, я думаю, вам следует уточнить свой ответ. Речь идет о прямом владении неуправляемым ресурсом, а не о его косвенном владении. Если у меня есть ссылка на FileStream, у меня косвенно есть неуправляемый ресурс, поэтому я должен вызывать для него Dispose. - person Jon Skeet; 11.12.2008
comment
Джон, конечно, твое мнение слишком ценно, чтобы его игнорировать! Я буду, немедленно! - person mmx; 11.12.2008
comment
@Mehrdad: Никогда не делайте что-то только из-за того, кто это предлагает, если только это не ваш босс или ваша вторая половинка :) Это не значит, что мое предложение не имеет достоинств - только то, что его следует оценивать по этим достоинствам, а не по моему представителю. :) - person Jon Skeet; 11.12.2008
comment
Конечно, я этого не сделал. Если бы я знал, я бы не стал с тобой спорить. Я уважаю вас, потому что вы знаете, о чем говорите, и да, конечно, великие люди тоже ошибаются. Мы все здесь, чтобы учиться! - person mmx; 11.12.2008

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

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

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

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

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

person shahkalpeshp    schedule 11.12.2008
comment
Вы ошибаетесь в третьем пункте. Если вы непосредственно держите неуправляемые ресурсы, вам нужен финализатор, но если вы только что получили ссылку на что-то еще и у которого есть финализатор, вы ничего не получите от того, чтобы иметь один себе. StreamWriter — хороший тому пример. - person Jon Skeet; 11.12.2008
comment
Джон, не могли бы вы объяснить вышесказанное на примере? что означает прямое владение неуправляемыми ресурсами (в качестве примера вы говорите о классе соединения)? Кроме того, что это значит, если у вас есть ссылка на что-то.....? - person shahkalpeshp; 11.12.2008
comment
Джон, я совсем не в своем понимании по всем пунктам? - person shahkalpeshp; 11.12.2008
comment
Посмотрите внизу моего ответа. По сути, если вы только что получили ссылку на другой объект (например, поток), вам не нужен финализатор. Если у вас есть IntPtr, который нужно очистить, у вас должен быть финализатор. - person Jon Skeet; 11.12.2008
comment
И на самом деле, для 1 и 2 вы тоже немного ошибаетесь. У всего есть метод Finalize, но не у всего на самом деле есть финализатор, насколько это касается CLR. Только объекты, которые переопределяют Finalize, ставятся в очередь на финализацию. - person Jon Skeet; 11.12.2008