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;
}
}