using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace System.Runtime.CompilerServices { public sealed class ConditionalWeakTable where TKey : class where TValue : class { private readonly Dictionary, TValue> items = new Dictionary, TValue>(); private readonly object _lock = new object(); private sealed class KeyComparer : IEqualityComparer> { public bool Equals(WeakReference x, WeakReference y) => x.TryGetTarget(out var keyX) && y.TryGetTarget(out var keyY) && ReferenceEquals(keyX, keyY); public int GetHashCode(WeakReference obj) => obj.TryGetTarget(out var key) ? key.GetHashCode() : 0; } private static WeakReference WeakRef(TKey key) => new WeakReference(key); private sealed class GCTracker { public static event Action OnGC; private static readonly WeakReference tracker = new WeakReference(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()); 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 } } }