using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
|
|
namespace System.Runtime.CompilerServices
|
|
{
|
|
public sealed class ConditionalWeakTable<TKey, TValue> where TKey : class where TValue : class
|
|
{
|
|
private readonly Dictionary<WeakReference<TKey>, TValue> items = new Dictionary<WeakReference<TKey>, TValue>();
|
|
private readonly object _lock = new object();
|
|
|
|
private sealed class KeyComparer : IEqualityComparer<WeakReference<TKey>>
|
|
{
|
|
public bool Equals(WeakReference<TKey> x, WeakReference<TKey> y)
|
|
=> x.TryGetTarget(out var keyX) && y.TryGetTarget(out var keyY) && ReferenceEquals(keyX, keyY);
|
|
|
|
public int GetHashCode(WeakReference<TKey> obj)
|
|
=> obj.TryGetTarget(out var key) ? key.GetHashCode() : 0;
|
|
}
|
|
|
|
private static WeakReference<TKey> WeakRef(TKey key)
|
|
=> new WeakReference<TKey>(key);
|
|
|
|
private sealed class GCTracker
|
|
{
|
|
public static event Action OnGC;
|
|
private static readonly WeakReference<GCTracker> tracker = new WeakReference<GCTracker>(new GCTracker());
|
|
~GCTracker()
|
|
{
|
|
OnGC?.Invoke();
|
|
if (!AppDomain.CurrentDomain.IsFinalizingForUnload() && !Environment.HasShutdownStarted)
|
|
tracker.SetTarget(new GCTracker());
|
|
}
|
|
}
|
|
|
|
public void Add(TKey key, TValue value)
|
|
{
|
|
if (key == null)
|
|
throw new ArgumentException("Null key", nameof(key));
|
|
lock (_lock)
|
|
items.Add(WeakRef(key), value);
|
|
}
|
|
|
|
public bool TryGetValue(TKey key, out TValue value)
|
|
{
|
|
if (key == null)
|
|
throw new ArgumentException("Null key", nameof(key));
|
|
|
|
value = null;
|
|
lock (_lock)
|
|
return items.TryGetValue(WeakRef(key), out value);
|
|
}
|
|
|
|
public delegate TValue CreateValueCallback(TKey key);
|
|
|
|
public TValue GetValue(TKey key, CreateValueCallback createValueCallback)
|
|
{
|
|
if (createValueCallback == null)
|
|
throw new ArgumentException("Null create delegate", nameof(createValueCallback));
|
|
|
|
lock (_lock)
|
|
{
|
|
if (TryGetValue(key, out var value))
|
|
return value;
|
|
else
|
|
{
|
|
value = createValueCallback(key);
|
|
Add(key, value);
|
|
return value;
|
|
}
|
|
}
|
|
}
|
|
|
|
public TValue GetOrCreateValue(TKey key)
|
|
=> GetValue(key, k => Activator.CreateInstance<TValue>());
|
|
|
|
public bool Remove(TKey key)
|
|
{
|
|
if (key == null)
|
|
throw new ArgumentException("Null key", nameof(key));
|
|
|
|
return items.Remove(WeakRef(key));
|
|
}
|
|
|
|
public ConditionalWeakTable()
|
|
=> GCTracker.OnGC += OnGC;
|
|
~ConditionalWeakTable()
|
|
=> GCTracker.OnGC -= OnGC;
|
|
|
|
private void OnGC()
|
|
{
|
|
// on each GC, we want to clear the entire set of empty keys
|
|
var nullWeakRef = WeakRef(null);
|
|
while (items.Remove(nullWeakRef)) ; // just loop
|
|
}
|
|
}
|
|
}
|