Анализатор обнаружил подозрительный деструктор, содержащий потенциально некорректное "воскрешение" объекта.
Деструктор объекта вызывается сборщиком мусора .NET непосредственно перед тем, как объект будет удалён. Объявление деструктора объекта не является обязательным в языках .NET Framework - объект будет удалён сборщиком мусора и без явного определения деструктора. Обычно деструктор используется для освобождения одновременно с удалением самого .NET объекта неуправляемых ресурсов, которые этот объект использует. Например, это могут быть дескрипторы файловой системы. Такие неуправляемые ресурсы не будут освобождены сборщиком мусора автоматически.
Однако, в момент непосредственно перед удалением объекта, пользователь может (сознательно или несознательно) "воскресить" такой объект перед тем, как тот будут очищен сборщиком мусора. Напомним, что сборщик мусора очищает объекты, ставшие недоступными. Т.е. на такие объекты нигде не осталось ссылок. Однако, если присвоить ссылку на такой объект из его деструктора, например, в глобальную статическую переменную, то объект снова станет видим и из других частей программы, и, соответственно, "воскреснет". Заметим, что такую операцию можно производить неограниченное количество раз.
Далее приведём пример такого "воскрешения":
class HeavyObject
{
private HeavyObject()
{
HeavyObject.Bag.Add(this);
}
...
public static ConcurrentBag<HeavyObject> Bag;
~HeavyObject()
{
if (HeavyObject.Bag != null)
HeavyObject.Bag.Add(this);
}
}
Предположим, что у нас есть объект HeavyObject, создание которого является очень ресурсозатратным дейстивем. При этом такой объект нельзя использовать из нескольких мест параллельно. Предположим также, что мы можем создать всего несколько экземпляров таких объектов. В нашем примере тип HeavyObject имеет открытое статическое поле Bag - коллекцию, в которую будут добавлены (в конструкторе) все созданные нами экземпляры объектов HeavyObject. Это позволит получить из любого места в программе экземпляр типа HeavyObject:
HeavyObject heavy;
HeavyObject.Bag.TryTake(out heavy);
Метод TryTake удалит экземпляр heavy из коллекции Bag. Таким образом, в программе возможно будет использовать только ограниченное число заранее созданных экземпляров типа HeavyObject (его конструктор является закрытым). Далее, представим, что экземпляр heavy, полученный с помощью метода TryTake, стал более не нужен, и все ссылки на этот объект оказались удалены. Тогда для этого объекта, через какое-то время, сборщиком мусора будет вызван его деструктор, где этот объект снова будет добавлен в коллекцию Bag, т.е. "воскрешён", и снова станет доступен для пользователей программы, без необходимости его пересоздавать.
Однако, приведённый пример содержит ошибку, из-за которой он не будет работать так, как было описано выше. Ошибка эта - в предположении, что у "воскрешённого" объекта будет вызываться деструктор каждый раз, когда он перестаёт быть виден в программе (на него не остаётся ссылок). На самом деле, в приведённом примере деструктор будет вызван лишь один раз, т.е. мы "потеряем" объект при его повторной (второй) уборке сборщиком.
Для обеспечения корректной работы деструктора при "воскрешении" объекта, такой объект необходимо перерегистрировать с помощью метода GC.ReRegisterForFinalize:
~HeavyObject()
{
if (HeavyObject.Bag != null)
{
GC.ReRegisterForFinalize(this);
HeavyObject.Bag.Add(this);
}
}
Это обеспечит вызов деструктора каждый раз перед очисткой объекта сборщиком мусора.