Сборщик мусора опирается на информацию , скомпилированную в вашу сборку , предоставляемую 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
-блок или вызывать Dispose
вручную.
Правильный способ написания вышеупомянутого метода - принудительно установить, чтоGC не будет забирать наш объект, пока он нам нужен:
public void Test()
{
IntPtr local = _HandleToSomethingUnmanaged;
... access resource through local here
GC.KeepAlive(this); // won't be collected before this has executed
}