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