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.

91 lines
3.1 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. lock (_lock)
  34. items.Add(WeakRef(key), value);
  35. }
  36. public bool TryGetValue(TKey key, out TValue value)
  37. {
  38. if (key == null)
  39. throw new ArgumentException("Null key", nameof(key));
  40. value = null;
  41. lock (_lock)
  42. return items.TryGetValue(WeakRef(key), out value);
  43. }
  44. public delegate TValue CreateValueCallback(TKey key);
  45. public TValue GetValue(TKey key, CreateValueCallback createValueCallback)
  46. {
  47. if (createValueCallback == null)
  48. throw new ArgumentException("Null create delegate", nameof(createValueCallback));
  49. lock (_lock)
  50. {
  51. if (TryGetValue(key, out var value))
  52. return value;
  53. else
  54. {
  55. value = createValueCallback(key);
  56. Add(key, value);
  57. return value;
  58. }
  59. }
  60. }
  61. public TValue GetOrCreateValue(TKey key)
  62. => GetValue(key, k => Activator.CreateInstance<TValue>());
  63. public bool Remove(TKey key)
  64. => items.Remove(WeakRef(key));
  65. public ConditionalWeakTable()
  66. => GCTracker.OnGC += OnGC;
  67. ~ConditionalWeakTable()
  68. => GCTracker.OnGC -= OnGC;
  69. private void OnGC()
  70. {
  71. // on each GC, we want to clear the entire set of empty keys
  72. var nullWeakRef = WeakRef(null);
  73. while (items.Remove(nullWeakRef)) ; // just loop
  74. }
  75. }
  76. }