|
3 | 3 |
|
4 | 4 | using System.Collections;
|
5 | 5 | using System.Collections.Generic;
|
| 6 | +using System.Linq; |
| 7 | +using System.Runtime.CompilerServices; |
6 | 8 |
|
7 | 9 | namespace System.ComponentModel
|
8 | 10 | {
|
9 | 11 | /// <summary>
|
10 |
| - /// This is a hashtable that stores object keys as weak references. |
11 |
| - /// It monitors memory usage and will periodically scavenge the |
12 |
| - /// hash table to clean out dead references. |
| 12 | + /// Provides a hashtable-like collection that stores keys and values using weak references. |
| 13 | + /// This class is a thin wrapper around <see cref="ConditionalWeakTable{TKey, TValue}"/> |
| 14 | + /// and is especially useful for associating data with objects from unloadable assemblies. |
13 | 15 | /// </summary>
|
14 |
| - internal sealed class WeakHashtable : Hashtable |
| 16 | + internal sealed class WeakHashtable : IEnumerable<KeyValuePair<object, object?>> |
15 | 17 | {
|
16 |
| - private static readonly IEqualityComparer s_comparer = new WeakKeyComparer(); |
| 18 | + private readonly ConditionalWeakTable<object, object?> _hashtable = new ConditionalWeakTable<object, object?>(); |
17 | 19 |
|
18 |
| - private long _lastGlobalMem; |
19 |
| - private int _lastHashCount; |
20 |
| - |
21 |
| - internal WeakHashtable() : base(s_comparer) |
22 |
| - { |
23 |
| - } |
24 |
| - |
25 |
| - /// <summary> |
26 |
| - /// Adds a key-value pair to the hashtable, wrapping the key in a weak reference. |
27 |
| - /// </summary> |
28 |
| - public override void Add(object key, object? value) |
29 |
| - { |
30 |
| - ScavengeKeys(); |
31 |
| - base.Add(new EqualityWeakReference(key), value); |
32 |
| - } |
33 |
| - |
34 |
| - /// <summary> |
35 |
| - /// Gets or sets the value associated with the specified key. |
36 |
| - /// </summary> |
37 |
| - public override object? this[object key] |
38 |
| - { |
39 |
| - get |
40 |
| - { |
41 |
| - return base[key]; |
42 |
| - } |
43 |
| - |
44 |
| - set |
45 |
| - { |
46 |
| - ScavengeKeys(); |
47 |
| - base[new EqualityWeakReference(key)] = value; |
48 |
| - } |
49 |
| - } |
50 |
| - |
51 |
| - /// <summary> |
52 |
| - /// Override of GetEnumerator that returns enumerator which skips not alive keys |
53 |
| - /// during enumeration. |
54 |
| - /// </summary> |
55 |
| - public override IDictionaryEnumerator GetEnumerator() => new WeakHashtableEnumerator(base.GetEnumerator()); |
56 |
| - |
57 |
| - /// <summary> |
58 |
| - /// This method checks to see if it is necessary to |
59 |
| - /// scavenge keys, and if it is it performs a scan |
60 |
| - /// of all keys to see which ones are no longer valid. |
61 |
| - /// To determine if we need to scavenge keys we need to |
62 |
| - /// try to track the current GC memory. Our rule of |
63 |
| - /// thumb is that if GC memory is decreasing and our |
64 |
| - /// key count is constant we need to scavenge. We |
65 |
| - /// will need to see if this is too often for extreme |
66 |
| - /// use cases like the CompactFramework (they add |
67 |
| - /// custom type data for every object at design time). |
68 |
| - /// </summary> |
69 |
| - private void ScavengeKeys() |
70 |
| - { |
71 |
| - int hashCount = Count; |
72 |
| - |
73 |
| - if (hashCount == 0) |
74 |
| - { |
75 |
| - return; |
76 |
| - } |
77 |
| - |
78 |
| - if (_lastHashCount == 0) |
79 |
| - { |
80 |
| - _lastHashCount = hashCount; |
81 |
| - return; |
82 |
| - } |
83 |
| - |
84 |
| - long globalMem = GC.GetTotalMemory(false); |
85 |
| - |
86 |
| - if (_lastGlobalMem == 0) |
87 |
| - { |
88 |
| - _lastGlobalMem = globalMem; |
89 |
| - return; |
90 |
| - } |
91 |
| - |
92 |
| - float memDelta = (globalMem - _lastGlobalMem) / (float)_lastGlobalMem; |
93 |
| - float hashDelta = (hashCount - _lastHashCount) / (float)_lastHashCount; |
94 |
| - |
95 |
| - if (memDelta < 0 && hashDelta >= 0) |
96 |
| - { |
97 |
| - // Perform a scavenge through our keys, looking |
98 |
| - // for dead references. |
99 |
| - List<object>? cleanupList = null; |
100 |
| - foreach (object o in Keys) |
101 |
| - { |
102 |
| - if (o is WeakReference wr && !wr.IsAlive) |
103 |
| - { |
104 |
| - cleanupList ??= new List<object>(); |
105 |
| - cleanupList.Add(wr); |
106 |
| - } |
107 |
| - } |
108 |
| - |
109 |
| - if (cleanupList != null) |
110 |
| - { |
111 |
| - foreach (object o in cleanupList) |
112 |
| - { |
113 |
| - Remove(o); |
114 |
| - } |
115 |
| - } |
116 |
| - } |
117 |
| - |
118 |
| - _lastGlobalMem = globalMem; |
119 |
| - _lastHashCount = hashCount; |
120 |
| - } |
121 |
| - |
122 |
| - private sealed class WeakKeyComparer : IEqualityComparer |
| 20 | + public object? this[object key] |
123 | 21 | {
|
124 |
| - bool IEqualityComparer.Equals(object? x, object? y) |
125 |
| - { |
126 |
| - if (x == null) |
127 |
| - { |
128 |
| - return y == null; |
129 |
| - } |
130 |
| - if (y != null && x.GetHashCode() == y.GetHashCode()) |
131 |
| - { |
132 |
| - if (x is WeakReference wX) |
133 |
| - { |
134 |
| - if (!wX.IsAlive) |
135 |
| - { |
136 |
| - return false; |
137 |
| - } |
138 |
| - x = wX.Target; |
139 |
| - } |
140 |
| - |
141 |
| - if (y is WeakReference wY) |
142 |
| - { |
143 |
| - if (!wY.IsAlive) |
144 |
| - { |
145 |
| - return false; |
146 |
| - } |
147 |
| - y = wY.Target; |
148 |
| - } |
149 |
| - |
150 |
| - return object.ReferenceEquals(x, y); |
151 |
| - } |
152 |
| - |
153 |
| - return false; |
154 |
| - } |
155 |
| - |
156 |
| - int IEqualityComparer.GetHashCode(object obj) => obj.GetHashCode(); |
157 |
| - } |
158 |
| - |
159 |
| - /// <summary> |
160 |
| - /// A subclass of WeakReference that overrides GetHashCode and |
161 |
| - /// Equals so that the weak reference returns the same equality |
162 |
| - /// semantics as the object it wraps. This will always return |
163 |
| - /// the object's hash code and will return True for a Equals |
164 |
| - /// comparison of the object it is wrapping. If the object |
165 |
| - /// it is wrapping has finalized, Equals always returns false. |
166 |
| - /// </summary> |
167 |
| - private sealed class EqualityWeakReference : WeakReference |
168 |
| - { |
169 |
| - private readonly int _hashCode; |
170 |
| - |
171 |
| - internal EqualityWeakReference(object o) : base(o) |
172 |
| - { |
173 |
| - _hashCode = o.GetHashCode(); |
174 |
| - } |
175 |
| - |
176 |
| - public override bool Equals(object? o) |
177 |
| - { |
178 |
| - if (o?.GetHashCode() != _hashCode) |
179 |
| - { |
180 |
| - return false; |
181 |
| - } |
182 |
| - |
183 |
| - if (o == this || (IsAlive && ReferenceEquals(o, Target))) |
184 |
| - { |
185 |
| - return true; |
186 |
| - } |
187 |
| - |
188 |
| - return false; |
189 |
| - } |
190 |
| - |
191 |
| - public override int GetHashCode() => _hashCode; |
| 22 | + get => _hashtable.TryGetValue(key, out object? value) ? value : null; |
| 23 | + set => _hashtable.AddOrUpdate(key, value); |
192 | 24 | }
|
193 | 25 |
|
194 |
| - /// <summary> |
195 |
| - /// A wrapper around the base HashTable enumerator that skips |
196 |
| - /// collected keys and captures the key value to prevent it |
197 |
| - /// from being collected during enumeration. |
198 |
| - /// </summary> |
199 |
| - private sealed class WeakHashtableEnumerator : IDictionaryEnumerator |
200 |
| - { |
201 |
| - private readonly IDictionaryEnumerator _baseEnumerator; |
202 |
| - // Captured key of the WeakHashtable so that it is not collected during enumeration |
203 |
| - private object? _currentKey; |
204 |
| - |
205 |
| - public WeakHashtableEnumerator(IDictionaryEnumerator baseEnumerator) |
206 |
| - { |
207 |
| - _baseEnumerator = baseEnumerator; |
208 |
| - } |
209 |
| - |
210 |
| - public DictionaryEntry Entry => new DictionaryEntry(Key, Value); |
211 |
| - |
212 |
| - public object Key |
213 |
| - { |
214 |
| - get |
215 |
| - { |
216 |
| - object key = _baseEnumerator.Key; |
217 |
| - return ((EqualityWeakReference)key).Target!; |
218 |
| - } |
219 |
| - } |
| 26 | + public bool Contains(object key) => _hashtable.TryGetValue(key, out object? _); |
220 | 27 |
|
221 |
| - public object? Value => _baseEnumerator.Value; |
| 28 | + public void Remove(object key) => _hashtable.Remove(key); |
222 | 29 |
|
223 |
| - public object Current => Entry; |
| 30 | + public IEnumerator<KeyValuePair<object, object?>> GetEnumerator() => ((IEnumerable<KeyValuePair<object, object?>>)_hashtable).GetEnumerator(); |
224 | 31 |
|
225 |
| - public bool MoveNext() |
226 |
| - { |
227 |
| - while (_baseEnumerator.MoveNext()) |
228 |
| - { |
229 |
| - // The key must always be an EqualityWeakReference by design. |
230 |
| - EqualityWeakReference key = (EqualityWeakReference)_baseEnumerator.Key; |
231 |
| - |
232 |
| - // If the weak reference is alive, capture it and return true. |
233 |
| - _currentKey = key.Target; |
234 |
| - if (_currentKey != null) |
235 |
| - { |
236 |
| - return true; |
237 |
| - } |
238 |
| - |
239 |
| - // Otherwise, the key has been collected and we need to skip it. |
240 |
| - } |
241 |
| - |
242 |
| - // Cleanup the captured key and return false if there is nothing more in the table. |
243 |
| - _currentKey = null; |
244 |
| - return false; |
245 |
| - } |
246 |
| - |
247 |
| - public void Reset() |
248 |
| - { |
249 |
| - _baseEnumerator.Reset(); |
250 |
| - _currentKey = null; |
251 |
| - } |
252 |
| - } |
| 32 | + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable<KeyValuePair<object, object?>>)_hashtable).GetEnumerator(); |
253 | 33 | }
|
254 | 34 | }
|
0 commit comments