namespace NesterovskyBros.Utils { using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using System.Threading; /// /// A weak table supporting non-identity equality. /// WeakTable is modeled after ConditionalWeakTable<K, V> class. /// /// A key type. /// A value type. public class WeakTable: IEnumerable> where K: class { /// /// Creates a WeakTable instance. /// /// /// Optional comparer instance. If no or null value is passed then /// EqualityComparer<K>.Default is used to match keys. /// public WeakTable(IEqualityComparer comparer = null) { self = new WeakReference>(this, true); this.comparer = comparer ?? (comparer = EqualityComparer.Default); values = new ConcurrentDictionary>( new EqualityComparer { comparer = comparer }); } /// /// A key enumerator. /// public IEnumerable Keys { get { return States.Select(s => s.key); } } /// /// Value enumerator. /// public IEnumerable Values { get { return States.Select(s => s.value); } } /// /// Gets a enumerator of entities contained in this weak table. /// /// /// A enumerator of entities contained in this weak table. /// public IEnumerator> GetEnumerator() { return States.Select(s => new KeyValuePair(s.key, s.value)). GetEnumerator(); } /// /// Gets a enumerator of entities contained in this weak table. /// /// /// A enumerator of entities contained in this weak table. /// IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } /// /// Adds an item to the weak table. /// /// A key. /// A value. /// /// If item is already exists in the weak table. /// public void Add(K key, V value) { if (!TryAdd(key, value)) { throw new ArgumentException("Duplicate argument."); } } /// /// Clears content of the weak table. /// public void Clear() { foreach(var state in States) { Remove(state.key); } } /// /// Gets or creates a value for a key. /// If value does not exist for a key then it's created using /// Activator.CreateInstance(). /// /// A key to get or create value for. /// A value for the key. public V GetOrCreateValue(K key) { return GetValue(key, k => Activator.CreateInstance()); } /// /// Gets or creates a value for a key. /// /// A key to get or create value for. /// A factory to create a value. /// A value for the key. public V GetValue(K key, Func createValue) { if (key == null) { throw new ArgumentException("key"); } State state; GetOrAdd(key, createValue, out state); return state.value; } /// /// Removes a key from a weak table. /// /// A key to remove. /// /// true if key is removed form the table, and false if no key was present. /// public bool Remove(K key) { if (key == null) { throw new ArgumentException("key"); } WeakReference stateRef; if (!values.TryGetValue(key, out stateRef)) { return false; } var state = Get(stateRef); if (state != null) { Volatile.Write(ref state.initialized, 2); states.Remove(state.key); GC.SuppressFinalize(state); } var result = values.TryRemove(stateRef, out stateRef); GC.KeepAlive(state); return result; } /// /// Tries to add a key and value to the weak table. /// /// A key. /// A value. /// true if value is successfully added, and false otherwise. public bool TryAdd(K key, V value) { if (key == null) { throw new ArgumentException("key"); } State state; return GetOrAdd(key, k => value, out state); } /// /// Tries to get a value for the key. /// /// A key. /// An output value. /// /// true if key is stored in the weak table, and false otherwise. /// public bool TryGetValue(K key, out V value) { WeakReference stateRef; if (values.TryGetValue(key, out stateRef)) { var state = Get(stateRef); if (state != null) { value = state.value; return true; } } value = default(V); return false; } /// /// Gets or adds a value. /// /// A key for the value. /// A value factory. /// Output value. /// true if this is a new value, and false otherwise. private bool GetOrAdd(K key, Func createValue, out State value) { var state = new State(self, key, comparer.GetHashCode(key)); do { value = Get(values.GetOrAdd( state.self, k => { state.value = createValue(key); return state.self; })); } while(value == null); if (state != value) { GC.SuppressFinalize(state); return false; } if (Interlocked.CompareExchange(ref state.initialized, 1, 0) == 0) { states.Add(key, state); if (Volatile.Read(ref state.initialized) == 2) { states.Remove(key); GC.SuppressFinalize(state); } } return true; } /// /// States enumerator. /// private IEnumerable States { get { return values.Values.Select(v => Get(v)).Where(s => s != null); } } /// /// Gets a target of a weak reference. /// /// A target type. /// A weak reference. /// A target instance, or null. private static R Get(WeakReference value) where R: class { R target; value.TryGetTarget(out target); return target; } /// /// A state. /// private class State { /// /// Creates a state instance. /// /// A WeakTable live weak reference. /// A key instance. /// A hash code. public State(WeakReference> weakTableRef, K key, int hashCode) { this.weakTableRef = weakTableRef; this.self = new WeakReference(this, true); this.key = key; this.hashCode = hashCode; } /// /// A finalizer. /// ~State() { var weakTable = Get(this.weakTableRef); if (weakTable != null) { WeakReference state; weakTable.values.TryRemove(self, out state); } } /// /// A weak reference to the self. /// public readonly WeakReference self; /// /// A weak table weak reference. /// public readonly WeakReference> weakTableRef; /// /// A key hashcode. /// public readonly int hashCode; /// /// A key. /// public readonly K key; /// /// A value for the key. /// public V value; /// /// Indicator of initialized state. /// public int initialized; } /// /// Internal comparer. /// private class EqualityComparer: IEqualityComparer { /// /// A comparer instance used to match keys. /// public IEqualityComparer comparer; /// /// Compares two weak references. /// /// first reference. /// second reference. /// true if references are equal, and false other wise. public new bool Equals(object x, object y) { if (x == y) { return true; } var xinitialized = 0; K xkey; var xref = x as WeakReference; if (xref != null) { var xstate = Get(xref); if (xstate == null) { return false; } xinitialized = xstate.initialized; xkey = xstate.key; } else { xkey = x as K; } if (xkey == null) { return false; } K ykey; var yref = y as WeakReference; if (yref != null) { var ystate = Get(yref); if (ystate == null) { return false; } if ((xinitialized != 0) && (ystate.initialized != 0)) { return false; } ykey = ystate.key; } else { ykey = y as K; } if (ykey == null) { return false; } return comparer.Equals(xkey, ykey); } /// /// Gets a hash code for the weak reference. /// /// A key. /// A hash code value. public int GetHashCode(object obj) { var weakRef = obj as WeakReference; if (weakRef != null) { var state = Get(weakRef); return state == null ? 0 : state.hashCode; } return obj == null ? 0 : obj.GetHashCode(); } } // Theory: // 1. Keys in "values" are the same instances as values. // 2. The type of key in "values" is declared as object. // This is to allow search keys of type K using custom comparer. // 3. WeakReference instances track resurection. // This means that they keep object references during finalization. // 4. State removes its reference from "values" during finalization. // 5. States are collected after their K instances, as they are bound // using "states" weak table. // 6. Both keys and values are not stongly kept by this instance. // This means that a value can strongly refer to a key, which won't // keep them from GC, if there are no more strong references to the key. /// /// A comparer instance used to match keys. /// private readonly IEqualityComparer comparer; /// /// A dictionary storage. /// private readonly ConcurrentDictionary> values; /// /// A list of states. /// private readonly ConditionalWeakTable states = new ConditionalWeakTable(); /// /// A weak reference to the self. /// public readonly WeakReference> self; } }