1 // Licensed to the .NET Foundation under one or more agreements. 2 // The .NET Foundation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System.Collections; 6 using System.Collections.Generic; 7 8 namespace System.ComponentModel 9 { 10 /// <summary> 11 /// This is a hashtable that stores object keys as weak references. 12 /// It monitors memory usage and will periodically scavenge the 13 /// hash table to clean out dead references. 14 /// </summary> 15 internal sealed class WeakHashtable : Hashtable 16 { 17 private static readonly IEqualityComparer s_comparer = new WeakKeyComparer(); 18 19 private long _lastGlobalMem; 20 private int _lastHashCount; 21 WeakHashtable()22 internal WeakHashtable() : base(s_comparer) 23 { 24 } 25 26 /// <summary> 27 /// Override of clear that performs a scavenge. 28 /// </summary> Clear()29 public override void Clear() 30 { 31 base.Clear(); 32 } 33 34 /// <summary> 35 /// Override of remove that performs a scavenge. 36 /// </summary> Remove(object key)37 public override void Remove(object key) 38 { 39 base.Remove(key); 40 } 41 42 /// <summary> 43 /// Override of Item that wraps a weak reference around the 44 /// key and performs a scavenge. 45 /// </summary> SetWeak(object key, object value)46 public void SetWeak(object key, object value) 47 { 48 ScavengeKeys(); 49 this[new EqualityWeakReference(key)] = value; 50 } 51 52 /// <summary> 53 /// This method checks to see if it is necessary to 54 /// scavenge keys, and if it is it performs a scan 55 /// of all keys to see which ones are no longer valid. 56 /// To determine if we need to scavenge keys we need to 57 /// try to track the current GC memory. Our rule of 58 /// thumb is that if GC memory is decreasing and our 59 /// key count is constant we need to scavenge. We 60 /// will need to see if this is too often for extreme 61 /// use cases like the CompactFramework (they add 62 /// custom type data for every object at design time). 63 /// </summary> ScavengeKeys()64 private void ScavengeKeys() 65 { 66 int hashCount = Count; 67 68 if (hashCount == 0) 69 { 70 return; 71 } 72 73 if (_lastHashCount == 0) 74 { 75 _lastHashCount = hashCount; 76 return; 77 } 78 79 long globalMem = GC.GetTotalMemory(false); 80 81 if (_lastGlobalMem == 0) 82 { 83 _lastGlobalMem = globalMem; 84 return; 85 } 86 87 float memDelta = (float)(globalMem - _lastGlobalMem) / (float)_lastGlobalMem; 88 float hashDelta = (float)(hashCount - _lastHashCount) / (float)_lastHashCount; 89 90 if (memDelta < 0 && hashDelta >= 0) 91 { 92 // Perform a scavenge through our keys, looking 93 // for dead references. 94 // 95 List<object> cleanupList = null; 96 foreach (object o in Keys) 97 { 98 WeakReference wr = o as WeakReference; 99 if (wr != null && !wr.IsAlive) 100 { 101 if (cleanupList == null) 102 { 103 cleanupList = new List<object>(); 104 } 105 106 cleanupList.Add(wr); 107 } 108 } 109 110 if (cleanupList != null) 111 { 112 foreach (object o in cleanupList) 113 { 114 Remove(o); 115 } 116 } 117 } 118 119 _lastGlobalMem = globalMem; 120 _lastHashCount = hashCount; 121 } 122 123 private class WeakKeyComparer : IEqualityComparer 124 { IEqualityComparer.Equals(Object x, Object y)125 bool IEqualityComparer.Equals(Object x, Object y) 126 { 127 if (x == null) 128 { 129 return y == null; 130 } 131 if (y != null && x.GetHashCode() == y.GetHashCode()) 132 { 133 WeakReference wX = x as WeakReference; 134 WeakReference wY = y as WeakReference; 135 136 if (wX != null) 137 { 138 if (!wX.IsAlive) 139 { 140 return false; 141 } 142 x = wX.Target; 143 } 144 145 if (wY != null) 146 { 147 if (!wY.IsAlive) 148 { 149 return false; 150 } 151 y = wY.Target; 152 } 153 154 return object.ReferenceEquals(x, y); 155 } 156 157 return false; 158 } 159 IEqualityComparer.GetHashCode(Object obj)160 int IEqualityComparer.GetHashCode(Object obj) 161 { 162 return obj.GetHashCode(); 163 } 164 } 165 166 /// <summary> 167 /// A subclass of WeakReference that overrides GetHashCode and 168 /// Equals so that the weak reference returns the same equality 169 /// semantics as the object it wraps. This will always return 170 /// the object's hash code and will return True for a Equals 171 /// comparison of the object it is wrapping. If the object 172 /// it is wrapping has finalized, Equals always returns false. 173 /// </summary> 174 private sealed class EqualityWeakReference : WeakReference 175 { 176 private int _hashCode; EqualityWeakReference(object o)177 internal EqualityWeakReference(object o) : base(o) 178 { 179 _hashCode = o.GetHashCode(); 180 } 181 Equals(object o)182 public override bool Equals(object o) 183 { 184 if (o?.GetHashCode() != _hashCode) 185 { 186 return false; 187 } 188 189 if (o == this || (IsAlive && object.ReferenceEquals(o, Target))) 190 { 191 return true; 192 } 193 194 return false; 195 } 196 GetHashCode()197 public override int GetHashCode() 198 { 199 return _hashCode; 200 } 201 } 202 } 203 } 204