Where can I find good implementation of IDictionary
which uses weak references inside?
Dictionary should be holding only weak references to values and eventually clean up itself of dead references.
Or should I just write it myself?
Where can I find good implementation of IDictionary
which uses weak references inside?
Dictionary should be holding only weak references to values and eventually clean up itself of dead references.
Or should I just write it myself?
ConditionalWeakTable Class uses weak keys and automatically removes the key/value entry as soon as no other references to a key exist outside the table.
You'll need to write it yourself. It should be relatively straight forward, implementing the IDictionary interface and then storing the actual values as WeakReferences. You can then check the values on add/select to see if they're still alive.
Pseudo code - won't really compile:
public class WeakDictionary <TKey,TValue> : IDictionary<TKey,TValue>
{
private IDictionary<TKey,WeakReference> _innerDictionary = new Dictionary<TKey,WeakReference>();
public TValue Index[ TKey key ]
{
get{
var reference = _innerDictionary[ key ];
if( reference.IsAlive )
return (TValue)reference.Target;
throw new InvalidOperation( "Key not found." );
}
}
private void Cull()
{
var deadKeys = new List<TKey>();
foreach( var pair in _innerDictionary )
{
if( ! pair.Value.IsAlive )
deadKeys.Add( pair.Key );
}
foreach( var key in deadKeys )
_innerDictionary.Remove( key );
}
}
It is one thing to have WeakReferences to values, but I found that Dictionary keys can also be a source of memory leaks. Here is a bare bones implementation with WeakReference to keys:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Common.library.collections {
/// <summary>
/// THIS DICTIONARY WILL NOT "HANG ON" TO THE KEYS IT USES
/// IF THE KEY IS GARBAGE COLLECTED, THE VALUE WILL BE RELEASED TOO
/// </summary>
public class Dictionary_usingWeakKey<K, V> {
//MAP FROM HASH CODE TO LIST OF KEY/VALUE PAIRS
private Dictionary<int, List<Pair>> dic = new Dictionary<int, List<Pair>>();
public void Add(K key, V value) {
if (value==null){
this.Remove(key);
return;
}//endif
List<Pair> list = null;
dic.TryGetValue(key.GetHashCode(), out list);
if (list == null) {
list = new List<Pair>();
dic.Add(key.GetHashCode(), list);
}//endif
Boolean isDirty = false;
foreach(Pair p in list){
if (p.Key.Target == null) {
isDirty = true;
continue;
}//endif
if (p.Key.Target == (Object)key) {
p.Value = (Object)value;
if (isDirty) cleanList(list);
return;
}//endif
}//for
if (isDirty) cleanList(list);
Pair newP=new Pair();
newP.Key = new WeakReference(key);
newP.Value = value;
list.Add(newP);
}//method
public bool ContainsKey(K key) {
List<Pair> list = null;
dic.TryGetValue(key.GetHashCode(), out list);
if (list == null) return false;
Boolean isDirty = false;
foreach (Pair p in list) {
if (p.Key.Target == null) {
isDirty = true;
continue;
}//endif
if (p.Key.Target == (Object)key) {
if (isDirty) cleanList(list);
return true;
}//endif
}//for
if (isDirty) cleanList(list);
return false;
}//method
private void cleanList(List<Pair> list) {
var temp = (from Pair p in list where p.Key.Target != null select p);
list.Clear();
list.AddRange(temp);
}//method
public bool Remove(K key) {
List<Pair> list = null;
dic.TryGetValue(key.GetHashCode(), out list);
if (list == null) return true;
foreach (Pair p in list) {
if (p.Key.Target == (Object)key) {
p.Value = null;
break;
}//endif
}//for
cleanList(list);
return true;
}//method
public V this[K key] {
get {
List<Pair> list = null;
dic.TryGetValue(key.GetHashCode(), out list);
if (list == null) return default(V);
Boolean isDirty = false;
foreach (Pair p in list) {
if (p.Key.Target == null) {
isDirty = true;
continue;
}//endif
if (p.Key.Target == (Object)key) {
if (isDirty) cleanList(list);
return (V)p.Value;
}//endif
}//for
if (isDirty) cleanList(list);
return default(V);
}
set {
this.Add(key, value);
}
}
public void Add(KeyValuePair<K, V> item) {
throw new NotImplementedException();
}
public void Clear() {
dic.Clear();
}
public bool Contains(KeyValuePair<K, V> item) {
throw new NotImplementedException();
}
public void CopyTo(KeyValuePair<K, V>[] array, int arrayIndex) {
throw new NotImplementedException();
}
public int Count {
get {
throw new NotImplementedException();
//return dic.Count();
}
}
public bool IsReadOnly {
get { return false; }
}
public bool Remove(KeyValuePair<K, V> item) {
throw new NotImplementedException();
}
public IEnumerator<KeyValuePair<K, V>> GetEnumerator() {
throw new NotImplementedException();
//return dic.GetEnumerator();
}
//System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
// return ((System.Collections.IEnumerable)dic).GetEnumerator();
//}
}//class
public class Pair{
public WeakReference Key;
public Object Value;
}//method
}
One problem with simply holding a dictionary of WeakReference objects is that there's no way, short of enumerating the entire dictionary, of removing from the Dictionary any WeakReference objects whose targets go out of scope.
It would be helpful if a WeakReference could include a delegate which would be invoked when the primary target went out of scope. As far as I know, there's no way to do that. If you don't mind adding another field and a little code to the objects you're storing within your "weak dictionary", I'd suggest creating what I call a "Finasposer" object, whose sole field is a MethodInvoker; when disposed, the MethodInvoker should be nulled out; the finalizer should Interlocked.Exchange() the MethodInvoker to null and--if its old value was non-null--invoke it. The object to be written in the dictionary should create a new Finasposer object, with a delegate that will cause the key to be removed from the dictionary when convenient.
Note that the neither the finalizer nor any delegate invoked thereby should never directly manipulate the dictionary, nor do anything that would require acquiring a lock. If the Finasposer holds a delegate, that delegate itself is guaranteed to be valid when Finalize executes, but the object attached to the delegate, and any objects referenced thereby, may be in unexpected states. It should be safe, however, for the Finasposer-called method to add to a linked list a reference to the object that went out of scope. The Dictionary's Add, Remove, and other methods could poll the linked list to see if any of the WeakReferences therein had died and needed to be cleaned out.
If identity comparison cannot be used then ConditionalWeakTable is not an option.
In this case I dare to suggest our implementation WeakTable.cs, and our description in the blog WeakTable.
This is my version of a concurrent weak (value) dictionary:
public class WeakConcurrentDictionary<TKey, TValue> : IDictionary<TKey, TValue> where TValue : class
{
private readonly ConcurrentDictionary<TKey, WeakReference<TValue>> _internalDictionary =
new ConcurrentDictionary<TKey, WeakReference<TValue>>();
public TValue this[TKey key]
{
get
{
if (_internalDictionary.TryGetValue(key, out var weakReference) &&
weakReference.TryGetTarget(out var value))
return value;
return null;
}
set
{
_internalDictionary.TryAdd(key, new WeakReference<TValue>(value));
}
}
public ICollection<TKey> Keys => _internalDictionary.Keys;
public ICollection<TValue> Values => _internalDictionary.Values
.Select(_ => _.GetTarget())
.Where(_ => _ != null)
.ToList();
public int Count => _internalDictionary.Count;
public bool IsReadOnly => false;
public void Add(TKey key, TValue value)
{
Purge();
if (!_internalDictionary.TryAdd(key, new WeakReference<TValue>(value)))
{
throw new InvalidOperationException("Key already existing");
}
}
public void Add(KeyValuePair<TKey, TValue> item)
{
throw new NotSupportedException();
}
public void Clear()
{
_internalDictionary.Clear();
}
public bool Contains(KeyValuePair<TKey, TValue> item) => _internalDictionary.TryGetValue(item.Key, out var weakReference) &&
weakReference.GetTarget() == item.Value;
public bool ContainsKey(TKey key) => _internalDictionary.TryGetValue(key, out var weakReference) &&
weakReference.IsAlive();
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
Purge();
_internalDictionary
.Select(_ => new KeyValuePair<TKey, TValue>(_.Key, _.Value.GetTarget()))
.Where(_ => _.Value != null)
.ToList()
.CopyTo(array, arrayIndex);
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
Purge();
return _internalDictionary
.Select(_ => new KeyValuePair<TKey, TValue>(_.Key, _.Value.GetTarget()))
.Where(_ => _.Value != null)
.GetEnumerator();
}
public bool Remove(TKey key)
{
return _internalDictionary.TryRemove(key, out var weakReference);
}
public bool Remove(KeyValuePair<TKey, TValue> item)
{
throw new NotSupportedException();
}
public bool TryGetValue(TKey key, out TValue value)
{
value = null;
if (_internalDictionary.TryGetValue(key, out var weakReference))
{
value = weakReference.GetTarget();
}
return value != null;
}
IEnumerator IEnumerable.GetEnumerator()
{
Purge();
return GetEnumerator();
}
public void Purge()
{
foreach (var itemToRemove in _internalDictionary
.Select(_ => new KeyValuePair<TKey, TValue>(_.Key, _.Value.GetTarget()))
.Where(_ => _.Value == null))
{
_internalDictionary.TryRemove(itemToRemove.Key, out var weakReference);
}
}
}
public static class WeakReferenceExtensions
{
public static bool IsAlive<T>([NotNull] this WeakReference<T> weakReference) where T : class =>
weakReference.TryGetTarget(out var target);
public static T GetTarget<T>([NotNull] this WeakReference<T> weakReference, T defaultValue = default(T)) where T : class
{
if (!weakReference.TryGetTarget(out T target))
return defaultValue;
return target;
}
}
and a test proving that reference to value is actually discarded:
[TestMethod]
public void TestWeakDictionary()
{
var weakDict = new WeakConcurrentDictionary<string, TestItem>();
{
var testItem = new TestItem();
weakDict.Add("testitem", testItem);
Assert.AreEqual(1, weakDict.Count);
Assert.AreSame(testItem, weakDict["testitem"]);
}
GC.Collect();
Assert.IsNull(weakDict["testitem"]);
weakDict.Purge();
Assert.AreEqual(0, weakDict.Count);
}
Some notes: