Ответ 1
Я просто попытаюсь решить возникшие проблемы:
Для классов ref как финализатор, так и деструктор должны быть записаны так, чтобы их можно было выполнить несколько раз и на объектах, которые еще не были полностью построены.
Деструктор ~Foo()
просто автоматически генерирует два метода, реализацию метода IDisposable:: Dispose(), а также защищенный метод Foo:: Dispose (bool), который реализует одноразовый шаблон. Это простые методы, поэтому их можно вызвать несколько раз. В С++/CLI разрешено напрямую вызывать финализатор, this->!Foo()
и обычно делается так же, как и вы. Сборщик мусора только когда-либо называет финализатор один раз, он отслеживает внутренне независимо от того, было ли это сделано. Учитывая, что вызов финализатора напрямую разрешен и разрешен многократный вызов Dispose(), таким образом, можно запускать код финализатора более одного раза. Это специфично для С++/CLI, другие управляемые языки не позволяют этого. Вы можете легко предотвратить это, проверка nullptr обычно выполняет задание.
Это поведение undefined, или вполне приемлемо, для вызова delete r в строке # 2?
Это не UB и вполне приемлемо. Оператор delete
просто вызывает метод IDisposable:: Dispose() и тем самым запускает ваш деструктор. То, что вы делаете внутри него, очень типично называя деструктор неуправляемого класса, вполне может вызвать UB.
Если мы удалим строку # 2, имеет ли значение, что r по-прежнему является маркером отслеживания
Нет. Вызов деструктора полностью необязателен без хорошего способа его принудительного применения. Ничего не получается, финализатор в конечном счете всегда будет работать. В данном примере, когда CLR запускает поток финализатора в последний раз перед выключением. Единственным побочным эффектом является то, что программа работает "тяжелая", удерживая ресурсы дольше, чем необходимо.
В каких случаях другие обстоятельства могут вызвать деструктор управляемого объекта более одного раза?
Это довольно распространенный вопрос, чрезмерный программист на С# может еще раз вызвать ваш метод Dispose(). Классы, которые предоставляют метод Close и Dispose, довольно распространены в структуре. Есть некоторые шаблоны, где это почти неизбежно, случай, когда другой класс предполагает принадлежность к объекту. Стандартный пример - это бит кода С#:
using (var fs = new FileStream(...))
using (var sw = new StreamWriter(fs)) {
// Write file...
}
Объект StreamWriter будет владеть базовым потоком и вызвать его метод Dispose() в последней фигурной скобке. Оператор using на объекте FileStream вызывает Dispose() второй раз. Написание этого кода, чтобы этого не произошло и по-прежнему предоставлять исключения, слишком сложно. Указание того, что Dispose() может быть вызвано более одного раза, решает проблему.
Можно ли вставить x. ~ Foo(); непосредственно перед или после r =% x;?
Все в порядке. Результат вряд ли будет приятным, исключение NullReferenceException будет наиболее вероятным результатом. Это то, что вы должны проверить, поднять ObjectDisposedException, чтобы дать программисту лучшую диагностику. Все стандартные классы .NET Framework делают это.
Другими словами, управляемые объекты "живут вечно"
Нет, сборщик мусора объявляет объект мертвым и собирает его, когда он больше не может найти ссылки на объект. Это безопасный способ управления памятью, нет возможности случайно ссылаться на удаленный объект. Потому что для этого требуется ссылка, которую GC всегда будет видеть. Общие проблемы управления памятью, такие как круговые ссылки, также не являются проблемой.
Фрагмент кода
Удаление объекта a
не требуется и не имеет никакого эффекта. Вы удаляете только объекты, реализующие IDisposable, массив не делает этого. Общим правилом является то, что класс .NET реализует только IDisposable, когда он управляет ресурсами, отличными от памяти. Или если у него есть поле типа класса, которое само реализует IDisposable.
Кроме того, сомнительно, следует ли вам реализовать деструктор в этом случае. Ваш примерный класс держится за довольно скромный неуправляемый ресурс. Внедряя деструктор, вы накладываете нагрузку на код клиента, чтобы использовать его. Это сильно зависит от использования класса, насколько это просто для клиентского программиста, это определенно не означает, что объект, как ожидается, будет жить долгое время, за пределами тела метода, чтобы оператор using не использовался, Вы можете позволить сборщику мусора узнать о потреблении памяти, который он не может отслеживать, вызвать GC:: AddMemoryPressure(). Который также заботится о случае, когда клиент-программист просто не использует Dispose(), потому что это слишком сложно.