Слабые ссылки
142C# и .NET Framework --- Оптимизация приложений .NET Framework --- Слабые ссылки
Слабые ссылки (weak references) - это вспомогательный механизм обслуживания ссылок на управляемые объекты. Типичная ссылка на объект (также называется сильной ссылкой (strong reference)) является очень детерминированной: пока имеется ссылка на объект, он будет продолжать существовать. И это правильно, с точки зрения сборщика мусора.
Однако иногда бывает желательно сохранить невидимой строку, связанную с объектом, не мешая при этом сборщику мусора удалить объект из памяти. Когда сборщик мусора удалит объект, строка окажется не связанной ни с каким объектом, и мы сможем определить это. Если сборщик мусора еще не прикасался к объекту, с помощью этой строки мы сможем восстановить сильную ссылку на объект и использовать его снова.
Это может пригодиться в разных ситуациях, наиболее типичные из которых перечислены ниже:
Пользование внешними услугами без сохранения объекта. Такие услуга, как таймеры и события, могут предоставляться объектам без сохранения ссылок на них, что может способствовать устранению типичных причин утечки памяти.
Автоматическое управление стратегией кеширования или поддержания пула объектов. Кеш может хранить слабые ссылки на недавно использовавшиеся объекты, не мешая их утилизации; пул может быть разделен на две части - основную, минимального размера, хранящую сильные ссылки, и дополнительную, содержащую слабые ссылки.
Удержание больших объектов в надежде, что они не будут утилизированы. Приложение может хранить слабую ссылку на большой объект, для создания и инициализации которого требуется много времени. Если объект окажется уничтожен сборщиком мусора, приложение сможет повторно создать его; в противном случае его можно использовать повторно.
Механизм слабых ссылок доступен приложению в виде класса System.WeakReference, являющегося особым случаем типа System.Runtime.InteropServices.GCHandle. Слабая ссылка имеет логическое свойство IsAlive, сообщающее о существовании объекта ссылки, и свойство Target, которое можно использовать для получения ссылки на целевой объект (если он еще не был утилизирован, в противном случае возвращается значение null).
Имейте в виду, что единственным безопасным способом получения строгой ссылки на объект слабой ссылки является свойство Target. Даже если свойство IsAlive вернет true, есть вероятность, что сразу после этого объект будет утилизирован. Чтобы избежать состояния гонки, сначала следует использовать свойство Target, присвоить возвращаемое им значение сильной ссылке (локальной переменной, полю и так далее) и затем сравнить полученное значение на равенство null. Свойство IsAlive следует использовать, только когда необходимо определить факт уничтожения объекта; например, чтобы удалить слабую ссылку из кеша.
Ниже демонстрируется черновая версия реализации событий на основе слабых ссылок (как показано на рисунке). Само событие не может использовать механизм делегатов .NET непосредственно, потому что делегат хранит сильную ссылку на свою цель и это является мешающим обстоятельством. Однако, можно хранить цель делегата (в виде слабой ссылки). Такой подход позволяет избавиться от одной из наиболее типичных причин утечек памяти в .NET, когда по забывчивости события продолжают оставаться зарегистрированными!

Генератор событий хранит слабые ссылки на всех подписчиков. Если подписчик окажется недостижим для приложения, генератор событий сможет определить это по появлению значения null в слабой ссылке.
public class Button
{
private class WeakDelegate
{
public WeakReference Target;
public MethodInfo Method;
}
private List<WeakDelegate> clickSubscribers = new List<WeakDelegate>();
public event EventHandler Click
{
add
{
clickSubscribers.Add(new WeakDelegate
{
Target = new WeakReference(value.Target),
Method = value.Method
});
}
remove
{
// ... Реализация опущена ради экономии места
}
}
public void FireClick()
{
List<WeakDelegate> toRemove = new List<WeakDelegate>();
foreach (WeakDelegate subscriber in clickSubscribers)
{
object target = subscriber.Target.Target;
if (target == null)
{
toRemove.Add(subscriber);
}
else
{
subscriber.Method.Invoke(target, new object[] { this, EventArgs.Empty });
}
}
clickSubscribers.RemoveAll((weak) => weak != null);
}
}
По умолчанию слабые ссылки не отслеживают воскрешение объектов. Чтобы включить отслеживание, используйте перегруженный конструктор, принимающий логический параметр, и передавайте в нем истинное значение, чтобы указать на необходимость слежения за воскрешением. Слабые ссылки, отслеживающие воскрешение, называют длинными слабыми ссылками (long weak references); слабые ссылки, не отслеживающие воскрешение, называются короткими слабыми ссылками (short weak references).
Дескрипторы сборщика мусора
Слабые ссылки - это особый случай дескрипторов сборщика мусора. Дескриптор сборщика мусора - это значение особого низкоуровневого типа, придающее ссылкам на объекты дополнительные возможности, позволяя:
сохранять обычную (сильную) ссылку на объект, препятствуя его утилизации; представлено значением GCHandleType.Normal;
сохранять короткую слабую ссылку на объект; представлено значением GCHandleType.Weak;
сохранять длинную слабую ссылку на объект; представлено значением GCHandleType.WeakTrackResurrection;
сохранять ссылку на объект, закрепляя его так, что он не может перемещаться в памяти, и дает возможность получать адрес объекта; представлено значением GCHandleType.Pinned;
На практике дескрипторы сборщика GC редко используются непосредственно, но они часто участвуют в результатах профилирования, как еще один тип корней, позволяющих удерживать управляемые объекты.