Мне хорошо известно, что финализаторы обычно используются для управления неуправляемыми ресурсами. При каких обстоятельствах финализатор может иметь дело с управляемыми?
Насколько я понимаю, присутствие в очереди финализатора предотвратит сбор любого объекта или объектов, на которые он сильно ссылается, но не будет (конечно) защищать их от завершения. В обычном ходе событий, после того, как объект завершен, он будет удален из очереди, и любые объекты, на которые он ссылается, больше не будут защищены от сбора при следующем проходе GC. К тому времени, когда вызывается финализатор, финализаторы могут быть вызваны для любой комбинации объектов, на которые ссылается объект; нельзя полагаться на то, что финализаторы вызываются в какой-либо конкретной последовательности, но ссылки на объекты, которые он хранит, должны быть действительными.
Совершенно очевидно, что финализатор никогда не должен ни получать блокировки, ни пытаться создать новый объект. Предположим, однако, что у меня есть объект, который подписывается на некоторые события, и другой объект, который фактически использует события. Если последний объект получает право на сборку мусора, я хочу, чтобы первый объект отписался от событий как можно скорее. Обратите внимание, что прежний объект никогда не будет допущен к финализации до тех пор, пока никакие подписки на него не будут удерживаться никаким живым объектом.
Было бы целесообразно иметь свободный от блокировок стек связанных списков или очередь объектов, для которых необходимо было отписаться, и чтобы финализатор основного объекта поместил ссылку на другой объект в стеке / очереди? Объект элемента связанного списка должен быть размещен при создании основного объекта (поскольку размещение в финализаторе будет запрещено), и, вероятно, потребуется использовать что-то вроде события таймера для опроса очереди (так как событие отменено пришлось бы запускать вне потока финализатора, и, вероятно, было бы глупо иметь поток, единственная цель которого состояла в том, чтобы ждать, пока что-то появится в очереди финализатора), но если финализатор мог бы безопасно ссылаться на свой предварительно выделенный объект связанного списка и основной объект очереди, связанный с его классом, может позволить отписаться от событий в течение 15 секунд или около того после завершения.
Это было бы хорошей идеей? (Примечания: я использую .net 2.0; также попытка добавления в стек или очередь может несколько раз обернуться в Threading.Interlocked.CompareExchange, но я не ожидаю, что он когда-либо застрянет очень долго).
EDIT
Конечно, любой код, который подписывается на события, должен реализовывать iDisposable, но одноразовые вещи не всегда располагаются должным образом. Если бы они были, в финализаторах не было бы необходимости.
Мой интересный сценарий может выглядеть примерно так: класс, реализующий iEnumerator (из T), перехватывает событие changeNotify своего связанного класса, так что перечисление может быть разумно обработано при изменении базового класса (да, я знаю Microsoft считает, что все перечислители должны просто сдаться, но иногда полезнее будет перечислитель, который может продолжать работать). Вполне возможно, что экземпляр класса мог бы перечисляться много тысяч или даже миллионов раз в течение дней или недель, но не обновляться вообще за это время.
В идеале, перечислитель никогда не будет забыт без утилизации, но перечислители иногда используются в тех случаях, когда «foreach» и «использование» не применимы (например, некоторые перечислители поддерживают вложенное перечисление). Тщательно продуманный финализатор может дать возможность справиться с этим сценарием.
Кстати, я бы потребовал, чтобы любое перечисление, которое должно продолжаться через обновления, использовало универсальный IEnumerable (из T); неуниверсальная форма, которая не обрабатывает iDisposable, должна будет вызвать исключение, если коллекция будет изменена.