Я понимаю, что делает System.WeakReference , но то, что я не могу понять, является практическим примером того, для чего это может быть полезно. Сам класс кажется мне, ну, взломать. Мне кажется, что есть другие, более эффективные способы решения проблемы, в которых WeakReference используется в примерах, которые я видел. Какой канонический пример того, где вы действительно должны использовать WeakReference? Разве мы не пытаемся получить дальше от этого типа поведения и использования этого класса?
Один полезный пример - ребята, которые управляют объектно-ориентированной базой данных DB4O. Там WeakReferences используются в качестве своего рода легкого кэша: он будет хранить ваши объекты в памяти только до тех пор, пока это делает ваше приложение, позволяя вам поместить реальный кэш поверх.
Другое использование будет в реализации слабых обработчиков событий. В настоящее время один большой источник утечек памяти в приложениях .NET забывает удалить обработчики событий. Например
public MyForm()
{
MyApplication.Foo += someHandler;
}
Видишь проблему? В приведенном выше фрагменте MyForm будет оставаться в памяти в памяти навсегда, пока MyApplication остается в памяти. Создайте 10 MyForms, закройте их все, ваши 10 MyForms останутся в памяти и будут поддерживаться обработчиком событий.
Введите WeakReference. Вы можете создать слабый обработчик событий, используя WeakReferences, чтобы someHandler был слабым обработчиком событий для MyApplication.Foo, тем самым исправляя утечки памяти!
Это не просто теория. Дастин Кэмпбелл из блога DidItWith.NET опубликовал реализацию слабых обработчиков событий с использованием System.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;
}
/* ... */
}
Я использую слабую ссылку для сохранения состояния в миксинах. Помните, что миксины являются статическими, поэтому, когда вы используете статический объект для присоединения состояния к нестатическому, вы никогда не знаете, как долго это потребуется. Поэтому вместо того, чтобы держать a, Dictionary<myobject, myvalue>
я сохраняю a, Dictionary<WeakReference,myvalue>
чтобы миксин не тянул вещи слишком долго.
Единственная проблема заключается в том, что каждый раз, когда я делаю доступ, я также проверяю наличие мертвых ссылок и удаляю их. Не то чтобы они причиняли кому-либо боль, если не тысячи, конечно.
Есть две причины, по которым вы бы использовали WeakReference
.
Вместо глобальных объектов, объявленных как статические : глобальные объекты объявляются как статические поля, и статические поля не могут быть GC-обработаны (сборка мусора) до тех пор, пока они
AppDomain
не GC-обработаны. Таким образом, вы рискуете исключения из памяти. Вместо этого мы можем обернуть глобальный объект вWeakReference
. Даже еслиWeakReference
сам объект объявлен статическим, объект, на который он указывает, будет GC'ed, когда памяти мало.В основном, используйте
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, нам понадобится метод с отложенной загрузкой, который я оставил для краткости.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'ed, когда GC-поток вызывается.Thing() wrStaticThing ~Thing() wrStaticThing ~WeakReferenceThing WeakReference is dead.
Для объектов, которые отнимают много времени для инициализации : Вы не хотите, чтобы объекты, отнимающие много времени для инициализации, были GC-объектами. Вы можете либо сохранить статическую ссылку, чтобы избежать этого (с минусами вышеупомянутой точки), либо использовать
WeakReference
.