- PVSM.RU - https://www.pvsm.ru -
Не так давно мы работали над диагностикой, связанной с проверкой финализатора, и у нас с коллегой возник спор по поводу деталей работы сборщика мусора и финализации объектов. И хотя я и он занимаемся разработкой на C# более 5 лет, к общему мнению мы не пришли, и я решил изучить этот вопрос подробнее.
Обычно первое знакомство с финализаторами у .NET разработчиков происходит, когда им нужно освободить неуправляемый ресурс. Возникает вопрос что же нужно использовать: реализовать в своём классе IDisposable или добавить финализатор? Тогда они идут, например, на StackOverflow и читают ответы на вопросы типа этого Finalize/Dispose pattern in C# [1] где рассказывается про классический паттерн реализации IDisposable в сочетании с определением финализатора. Тот же самый паттерн можно найти и в MSDN в описании интерфейса IDisposable [2]. Некоторые считают его довольно сложным для понимания и предлагают свои варианты вроде реализации очистки управляемых и неуправляемых ресурсов в отдельных методах или создания класса-обёртки специально для освобождения неуправляемого ресурса. Их можно найти на той же страничке на StackOverflow.
Большинство этих способов предполагают реализацию финализатора. Посмотрим какие плюсы и потенциальные проблемы это может принести.
Плюсы.
Пожалуй, всё. Это единственный плюс, да и то спорный, о чём ниже.
Минусы.
class Root
{
public volatile static Root StaticRoot = null;
public Nested Nested = null;
~Root()
{
Console.WriteLine("Finalization of Root");
StaticRoot = this;
}
}
class Nested
{
public void DoSomeWork()
{
Console.WriteLine(String.Format(
"Thread {0} enters DoSomeWork",
Thread.CurrentThread.ManagedThreadId));
Thread.Sleep(2000);
Console.WriteLine(String.Format(
"Thread {0} leaves DoSomeWork",
Thread.CurrentThread.ManagedThreadId));
}
~Nested()
{
Console.WriteLine("Finalization of Nested");
DoSomeWork();
}
}
class Program
{
static void CreateObjects()
{
Nested nested = new Nested();
Root root = new Root();
root.Nested = nested;
}
static void Main(string[] args)
{
CreateObjects();
GC.Collect();
while (Root.StaticRoot == null) { }
Root.StaticRoot.Nested.DoSomeWork();
Console.ReadLine();
}
}
Вот что будет выведено на экран на моей машине:
Finalization of Root
Finalization of Nested
Thread 10 enters DoSomeWork
Thread 2 enters DoSomeWork
Thread 10 leaves DoSomeWork
Thread 2 leaves DoSomeWork
Если у вас финализаторы вызываются в другом порядке, попробуйте поменять местами создание nested и root.
Финализаторы в .NET — это то место, где проще всего выстрелить себе в ногу. Прежде чем бросаться добавлять финализаторы для всех классов, реализующих IDisposable, стоит подумать, а действительно ли они так нужны. Надо отметить, что и сами разработчики CLR предостерегают от их использования на странице Dispose Pattern [3]: «Avoid making types finalizable. Carefully consider any case in which you think a finalizer is needed. There is a real cost associated with instances with finalizers, from both a performance and code complexity standpoint.»
Но если вы всё-таки решили использовать финализаторы, то PVS-Studio может помочь вам найти потенциальные ошибки. У нас есть диагностика V3100 [4], которая покажет все места в финализаторе, где может возникнуть NullReferenceException.
Автор: PVS-Studio
Источник [5]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/c-2/196676
Ссылки в тексте:
[1] Finalize/Dispose pattern in C#: http://stackoverflow.com/questions/898828/finalize-dispose-pattern-in-c-sharp
[2] IDisposable: https://msdn.microsoft.com/en-us/library/system.idisposable(v=vs.110).aspx
[3] Dispose Pattern: https://msdn.microsoft.com/en-us/library/b1yfkh5e(v=vs.110).aspx
[4] V3100: http://www.viva64.com/ru/w/V3100/
[5] Источник: https://habrahabr.ru/post/311998/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.