You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

98 lines
3.3 KiB

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. namespace System.Runtime.CompilerServices
  6. {
  7. public sealed class ConditionalWeakTable<TKey, TValue> where TKey : class where TValue : class
  8. {
  9. private readonly Dictionary<WeakReference<TKey>, TValue> items = new Dictionary<WeakReference<TKey>, TValue>();
  10. private readonly object _lock = new object();
  11. private sealed class KeyComparer : IEqualityComparer<WeakReference<TKey>>
  12. {
  13. public bool Equals(WeakReference<TKey> x, WeakReference<TKey> y)
  14. => x.TryGetTarget(out var keyX) && y.TryGetTarget(out var keyY) && ReferenceEquals(keyX, keyY);
  15. public int GetHashCode(WeakReference<TKey> obj)
  16. => obj.TryGetTarget(out var key) ? key.GetHashCode() : 0;
  17. }
  18. private static WeakReference<TKey> WeakRef(TKey key)
  19. => new WeakReference<TKey>(key);
  20. private sealed class GCTracker
  21. {
  22. public static event Action OnGC;
  23. private static readonly WeakReference<GCTracker> tracker = new WeakReference<GCTracker>(new GCTracker());
  24. ~GCTracker()
  25. {
  26. OnGC?.Invoke();
  27. if (!AppDomain.CurrentDomain.IsFinalizingForUnload() && !Environment.HasShutdownStarted)
  28. tracker.SetTarget(new GCTracker());
  29. }
  30. }
  31. public void Add(TKey key, TValue value)
  32. {
  33. if (key == null)
  34. throw new ArgumentException("Null key", nameof(key));
  35. lock (_lock)
  36. items.Add(WeakRef(key), value);
  37. }
  38. public bool TryGetValue(TKey key, out TValue value)
  39. {
  40. if (key == null)
  41. throw new ArgumentException("Null key", nameof(key));
  42. value = null;
  43. lock (_lock)
  44. return items.TryGetValue(WeakRef(key), out value);
  45. }
  46. public delegate TValue CreateValueCallback(TKey key);
  47. public TValue GetValue(TKey key, CreateValueCallback createValueCallback)
  48. {
  49. if (createValueCallback == null)
  50. throw new ArgumentException("Null create delegate", nameof(createValueCallback));
  51. lock (_lock)
  52. {
  53. if (TryGetValue(key, out var value))
  54. return value;
  55. else
  56. {
  57. value = createValueCallback(key);
  58. Add(key, value);
  59. return value;
  60. }
  61. }
  62. }
  63. public TValue GetOrCreateValue(TKey key)
  64. => GetValue(key, k => Activator.CreateInstance<TValue>());
  65. public bool Remove(TKey key)
  66. {
  67. if (key == null)
  68. throw new ArgumentException("Null key", nameof(key));
  69. return items.Remove(WeakRef(key));
  70. }
  71. public ConditionalWeakTable()
  72. => GCTracker.OnGC += OnGC;
  73. ~ConditionalWeakTable()
  74. => GCTracker.OnGC -= OnGC;
  75. private void OnGC()
  76. {
  77. // on each GC, we want to clear the entire set of empty keys
  78. var nullWeakRef = WeakRef(null);
  79. while (items.Remove(nullWeakRef)) ; // just loop
  80. }
  81. }
  82. }