Ответ 1
Сборщик мусора опирается на информацию , скомпилированную в вашу сборку, предоставленную компилятором JIT, которая сообщает ему, что код кода задает различные переменные, а "вещи" все еще используются.
Таким образом, в вашем коде, поскольку вы больше не используете переменные объекта GC, их можно бесплатно собрать. WeakReference не помешает этому, на самом деле, это весь смысл WR, чтобы вы могли ссылаться на объект, не мешая ему собираться.
Случай с WeakReference прекрасно скомпонован в однострочном описании в MSDN:
Представляет собой слабую ссылку, которая ссылается на объект, все еще позволяя этому объекту быть восстановленным путем сбора мусора.
Объекты WeakReference не собираются в мусор, поэтому вы можете безопасно их использовать, но объекты, на которые они ссылаются, оставили только ссылку WR, и, следовательно, их можно было бесплатно собрать.
При выполнении кода через отладчик переменные искусственно расширяются в области действия до тех пор, пока их область действия не закончится, как правило, конец блока, в котором они объявлены (например, методы), чтобы вы могли проверить их в точке останова.
Там есть некоторые тонкие вещи, чтобы обнаружить это. Рассмотрим следующий код:
using System;
namespace ConsoleApplication20
{
public class Test
{
public int Value;
~Test()
{
Console.Out.WriteLine("Test collected");
}
public void Execute()
{
Console.Out.WriteLine("The value of Value: " + Value);
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Console.Out.WriteLine("Leaving Test.Execute");
}
}
class Program
{
static void Main(string[] args)
{
Test t = new Test { Value = 15 };
t.Execute();
}
}
}
В режиме Release, выполненном без прикрепленного отладчика, здесь вывод:
The value of Value: 15 Test collected Leaving Test.Execute
Причиной этого является то, что, хотя вы все еще выполняете внутри метода, связанного с объектом Test, в момент запроса GC сделать это, нет необходимости в каких-либо экземплярах ссылок на Test (ссылка на this
или Value
) и никаких вызовов какого-либо метода-экземпляра, оставшегося для выполнения, поэтому объект можно безопасно собирать.
Это может иметь некоторые неприятные побочные эффекты, если вы не знаете об этом.
Рассмотрим следующий класс:
public class ClassThatHoldsUnmanagedResource : IDisposable
{
private IntPtr _HandleToSomethingUnmanaged;
public ClassThatHoldsUnmanagedResource()
{
_HandleToSomethingUnmanaged = (... open file, whatever);
}
~ClassThatHoldsUnmanagedResource()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
(release unmanaged resource here);
... rest of dispose
}
public void Test()
{
IntPtr local = _HandleToSomethingUnmanaged;
// DANGER!
... access resource through local here
}
На этом этапе, если Test не использует данные экземпляра после захвата копии неуправляемого дескриптора? Что делать, если GC теперь работает в точке, где я написал "ОПАСНОСТЬ"? Вы видите, где это происходит? Когда GC запускается, он выполнит финализатор, который выдержит доступ к неуправляемому ресурсу из теста Test, который все еще выполняется.
Неуправляемые ресурсы, обычно доступные через IntPtr
или подобные, непрозрачны сборщику мусора и не учитывают их при оценке срока жизни объекта.
Другими словами, мы сохраняем ссылку на дескриптор в локальной переменной, не имеет смысла для GC, он только замечает, что ссылок на экземпляры не осталось, и поэтому считает, что объект безопасен для сбора.
Это, если курс предполагает, что нет внешней ссылки на объект, который все еще считается "живым". Например, если вышеупомянутый класс использовался из метода, подобного этому:
public void DoSomething()
{
ClassThatHoldsUnmanagedResource = new ClassThatHoldsUnmanagedResource();
ClassThatHoldsUnmanagedResource.Test();
}
Тогда у вас есть та же самая проблема.
(конечно, вы, вероятно, не должны использовать его так, поскольку он реализует IDisposable, вы должны использовать using
-block или call Dispose
вручную.)
Правильный способ написания вышеуказанного метода состоит в том, чтобы обеспечить, чтобы GC не собирал наш объект, пока он нам еще нужен:
public void Test()
{
IntPtr local = _HandleToSomethingUnmanaged;
... access resource through local here
GC.KeepAlive(this); // won't be collected before this has executed
}