Я понимаю, что делает System.WeakReference, но чего я не могу понять, так это практического примера того, для чего он может быть полезен. Сам класс мне кажется, ну, хаком. Мне кажется, что есть другие, более эффективные средства решения проблемы, когда WeakReference используется в примерах, которые я видел. Каков канонический пример того, где действительно нужно использовать WeakReference? Разве мы не пытаемся увести дальше от этого типа поведения и использования этого класса?





Я использую его для реализации кеша, в котором неиспользуемые записи автоматически собираются сборщиком мусора:
class Cache<TKey,TValue> : IEnumerable<KeyValuePair<TKey,TValue>>
{ Dictionary<TKey,WeakReference> dict = new Dictionary<TKey,WeakReference>();
public TValue this[TKey key]
{ get {lock(dict){ return getInternal(key);}}
set {lock(dict){ setInteral(key,value);}}
}
void setInteral(TKey key, TValue val)
{ if (dict.ContainsKey(key)) dict[key].Target = val;
else dict.Add(key,new WeakReference(val));
}
public void Clear() { dict.Clear(); }
/// <summary>Removes any dead weak references</summary>
/// <returns>The number of cleaned-up weak references</returns>
public int CleanUp()
{ List<TKey> toRemove = new List<TKey>(dict.Count);
foreach(KeyValuePair<TKey,WeakReference> kv in dict)
{ if (!kv.Value.IsAlive) toRemove.Add(kv.Key);
}
foreach (TKey k in toRemove) dict.Remove(k);
return toRemove.Count;
}
public bool Contains(string key)
{ lock (dict) { return containsInternal(key); }
}
bool containsInternal(TKey key)
{ return (dict.ContainsKey(key) && dict[key].IsAlive);
}
public bool Exists(Predicate<TValue> match)
{ if (match==null) throw new ArgumentNullException("match");
lock (dict)
{ foreach (WeakReference weakref in dict.Values)
{ if ( weakref.IsAlive
&& match((TValue) weakref.Target)) return true;
}
}
return false;
}
/* ... */
}
Один из полезных примеров - это разработчики объектно-ориентированной базы данных DB4O. Там WeakReferences используются как своего рода световой кеш: он будет хранить ваши объекты в памяти только до тех пор, пока это делает ваше приложение, позволяя вам разместить поверх них настоящий кеш.
Другое применение - реализация слабых обработчиков событий. В настоящее время одним из основных источников утечки памяти в приложениях .NET является забвение удаления обработчиков событий. Например.
public MyForm()
{
MyApplication.Foo += someHandler;
}
Видите проблему? В приведенном выше фрагменте MyForm будет храниться в памяти навсегда, пока MyApplication существует в памяти. Создайте 10 MyForms, закройте их все, ваши 10 MyForms все еще будут в памяти и будут поддерживаться обработчиком событий.
Введите WeakReference. Вы можете создать слабый обработчик событий с помощью WeakReferences, чтобы someHandler был слабым обработчиком событий для MyApplication.Foo, тем самым устраняя утечки памяти!
Это не просто теория. Дастин Кэмпбелл из блога DidItWith.NET разместил реализация слабых обработчиков событий, используя System.WeakReference.
Ссылка, предоставленная для реализации слабых обработчиков событий, кажется, теперь мертва. у кого-нибудь есть альтернативные ссылки порекомендовать?
Вот пример: github.com/adbancroft/WeakEvents.Fody/blob/master/…
Я использую слабую ссылку для сохранения состояния в миксинах. Помните, миксины статичны, поэтому, когда вы используете статический объект для присоединения состояния к нестатическому, вы никогда не знаете, сколько времени это потребуется. Поэтому вместо того, чтобы держать Dictionary<myobject, myvalue>, я держу Dictionary<WeakReference,myvalue>, чтобы миксин не перетаскивал элементы слишком долго.
Единственная проблема в том, что каждый раз, когда я выполняю доступ, я также проверяю мертвые ссылки и удаляю их. Не то чтобы они никого обидели, если, конечно, их не тысячи.
Начиная с .NET 4.0, ConditionalWeakTable, вероятно, более подходит для этого использования.
В частности, CWT автоматически закрывает последнее примечание «[не забудьте] проверить мертвые ссылки и удалить их», как Dictionary
Есть две причины, по которым вы должны использовать WeakReference.
Вместо глобальных объектов, объявленных статическими: глобальные объекты объявляются как статические поля, а статические поля не могут быть объединены в сборку мусора (сборщик мусора), пока AppDomain не будет объединен в сборку мусора. Таким образом, вы рискуете исключить нехватку памяти. Вместо этого мы можем обернуть глобальный объект в WeakReference. Несмотря на то, что сам WeakReference объявлен статическим, объект, на который он указывает, будет обработан сборщиком мусора при нехватке памяти.
Как правило, используйте wrStaticObject вместо staticObject.
class ThingsWrapper {
//private static object staticObject = new object();
private static WeakReference wrStaticObject
= new WeakReference(new object());
}
Простое приложение, чтобы доказать, что статический объект собирает мусор, когда AppDomain.
class StaticGarbageTest
{
public static void Main1()
{
var s = new ThingsWrapper();
s = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
class ThingsWrapper
{
private static Thing staticThing = new Thing("staticThing");
private Thing privateThing = new Thing("privateThing");
~ThingsWrapper()
{ Console.WriteLine("~ThingsWrapper"); }
}
class Thing
{
protected string name;
public Thing(string name) {
this.name = name;
Console.WriteLine("Thing() " + name);
}
public override string ToString() { return name; }
~Thing() { Console.WriteLine("~Thing() " + name); }
}
Примечание из вывода ниже staticThing GC'ed в самом конце даже после того, как ThingsWrapper - т.е. GC'ed, когда AppDomain GC'ed.
Thing() staticThing
Thing() privateThing
~Thing() privateThing
~ThingsWrapper
~Thing() staticThing
Вместо этого мы можем обернуть Thing в WeakReference. Поскольку wrStaticThing может быть GC'ed, нам понадобится метод с отложенной загрузкой, который я оставил для краткости.
class WeakReferenceTest
{
public static void Main1()
{
var s = new WeakReferenceThing();
s = null;
GC.Collect();
GC.WaitForPendingFinalizers();
if (WeakReferenceThing.wrStaticThing.IsAlive)
Console.WriteLine("WeakReference: {0}",
(Thing)WeakReferenceThing.wrStaticThing.Target);
else
Console.WriteLine("WeakReference is dead.");
}
}
class WeakReferenceThing
{
public static WeakReference wrStaticThing;
static WeakReferenceThing()
{ wrStaticThing = new WeakReference(new Thing("wrStaticThing")); }
~WeakReferenceThing()
{ Console.WriteLine("~WeakReferenceThing"); }
//lazy-loaded method to new Thing
}
Обратите внимание на вывод ниже, что wrStaticThing запускается GC при вызове потока GC.
Thing() wrStaticThing
~Thing() wrStaticThing
~WeakReferenceThing
WeakReference is dead.
Для объектов, инициализация которых требует много времени: вы не хотите, чтобы объекты, для инициализации которых требуется много времени, подвергались сборке мусора. Вы можете сохранить статическую ссылку, чтобы избежать этого (с минусами из вышеупомянутого пункта), или использовать WeakReference.
Да, это довольно здорово. Здесь мы приняли его код с некоторыми дополнениями для обработки других типов обработчиков событий (например, неуниверсальный обработчик событий, PropertyChangedEventHandler и т. д.). У нас это сработало очень хорошо.