Skip to content

Commit ff60836

Browse files
Added a test for custom provider and updated WeakHashTable to use ConditionalWeakTable to ensure values can be also colelcted
1 parent ba008a9 commit ff60836

File tree

5 files changed

+91
-287
lines changed

5 files changed

+91
-287
lines changed

src/libraries/System.ComponentModel.TypeConverter/src/System/ComponentModel/ContextAwareHashtable.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@
88
namespace System.ComponentModel
99
{
1010
/// <summary>
11-
/// Hashtable that maps MemberInfo object key to an object and
12-
/// uses a WeakReference for the collectible keys (if MemberInfo.IsCollectible is true).
13-
/// Uses a Hashtable for non-collectible keys and WeakHashtable for the collectible keys.
11+
/// Hashtable that maps a <see cref="MemberInfo"/> object key to an associated value.
12+
/// <para>
13+
/// For keys where <see cref="MemberInfo.IsCollectible"/> is <c>false</c>, a standard <see cref="Hashtable"/> is used.
14+
/// For keys where <see cref="MemberInfo.IsCollectible"/> is <c>true</c>, a <see cref="ConditionalWeakTable{TKey, TValue}"/> is used.
15+
/// This ensures that collectible <see cref="MemberInfo"/> instances (such as those from collectible assemblies) do not prevent their assemblies from being unloaded.
16+
/// </para>
1417
/// </summary>
1518
internal sealed class ContextAwareHashtable
1619
{

src/libraries/System.ComponentModel.TypeConverter/src/System/ComponentModel/TypeDescriptor.cs

Lines changed: 12 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Collections;
5+
using System.Collections.Generic;
56
using System.Collections.Specialized;
67
using System.ComponentModel.Design;
78
using System.Diagnostics;
@@ -622,7 +623,7 @@ public static object GetAssociation(Type type, object primary)
622623
if (!type.IsInstanceOfType(primary))
623624
{
624625
// Check our association table for a match.
625-
Hashtable assocTable = AssociationTable;
626+
WeakHashtable assocTable = AssociationTable;
626627
IList? associations = (IList?)assocTable?[primary];
627628
if (associations != null)
628629
{
@@ -2332,16 +2333,12 @@ private static void Refresh(object component, bool refreshReflectionProvider)
23322333
// ReflectTypeDescritionProvider is only bound to object, but we
23332334
// need go to through the entire table to try to find custom
23342335
// providers. If we find one, will clear our cache.
2335-
// Manual use of IDictionaryEnumerator instead of foreach to avoid
2336-
// DictionaryEntry box allocations.
2337-
IDictionaryEnumerator e = s_providerTable.GetEnumerator();
2338-
while (e.MoveNext())
2336+
foreach (KeyValuePair<object, object?> kvp in s_providerTable)
23392337
{
2340-
DictionaryEntry de = e.Entry;
2341-
Type? nodeType = de.Key as Type;
2338+
Type? nodeType = kvp.Key as Type;
23422339
if (nodeType != null && type.IsAssignableFrom(nodeType) || nodeType == typeof(object))
23432340
{
2344-
TypeDescriptionNode? node = (TypeDescriptionNode?)de.Value;
2341+
TypeDescriptionNode? node = (TypeDescriptionNode?)kvp.Value;
23452342
while (node != null && !(node.Provider is ReflectTypeDescriptionProvider))
23462343
{
23472344
found = true;
@@ -2416,16 +2413,12 @@ public static void Refresh(Type type)
24162413
// ReflectTypeDescritionProvider is only bound to object, but we
24172414
// need go to through the entire table to try to find custom
24182415
// providers. If we find one, will clear our cache.
2419-
// Manual use of IDictionaryEnumerator instead of foreach to avoid
2420-
// DictionaryEntry box allocations.
2421-
IDictionaryEnumerator e = s_providerTable.GetEnumerator();
2422-
while (e.MoveNext())
2416+
foreach (KeyValuePair<object, object?> kvp in s_providerTable)
24232417
{
2424-
DictionaryEntry de = e.Entry;
2425-
Type? nodeType = de.Key as Type;
2418+
Type? nodeType = kvp.Key as Type;
24262419
if (nodeType != null && type.IsAssignableFrom(nodeType) || nodeType == typeof(object))
24272420
{
2428-
TypeDescriptionNode? node = (TypeDescriptionNode?)de.Value;
2421+
TypeDescriptionNode? node = (TypeDescriptionNode?)kvp.Value;
24292422
while (node != null && !(node.Provider is ReflectTypeDescriptionProvider))
24302423
{
24312424
found = true;
@@ -2478,15 +2471,12 @@ public static void Refresh(Module module)
24782471

24792472
lock (s_commonSyncObject)
24802473
{
2481-
// Manual use of IDictionaryEnumerator instead of foreach to avoid DictionaryEntry box allocations.
2482-
IDictionaryEnumerator e = s_providerTable.GetEnumerator();
2483-
while (e.MoveNext())
2474+
foreach (KeyValuePair<object, object?> kvp in s_providerTable)
24842475
{
2485-
DictionaryEntry de = e.Entry;
2486-
Type? nodeType = de.Key as Type;
2476+
Type? nodeType = kvp.Key as Type;
24872477
if (nodeType != null && nodeType.Module.Equals(module) || nodeType == typeof(object))
24882478
{
2489-
TypeDescriptionNode? node = (TypeDescriptionNode?)de.Value;
2479+
TypeDescriptionNode? node = (TypeDescriptionNode?)kvp.Value;
24902480
while (node != null && !(node.Provider is ReflectTypeDescriptionProvider))
24912481
{
24922482
refreshedTypes ??= new Hashtable();
@@ -2628,7 +2618,7 @@ public static void RemoveAssociation(object primary, object secondary)
26282618
ArgumentNullException.ThrowIfNull(primary);
26292619
ArgumentNullException.ThrowIfNull(secondary);
26302620

2631-
Hashtable assocTable = AssociationTable;
2621+
WeakHashtable assocTable = AssociationTable;
26322622
IList? associations = (IList?)assocTable?[primary];
26332623
if (associations != null)
26342624
{

src/libraries/System.ComponentModel.TypeConverter/src/System/ComponentModel/WeakHashtable.cs

Lines changed: 13 additions & 234 deletions
Original file line numberDiff line numberDiff line change
@@ -3,252 +3,31 @@
33

44
using System.Collections;
55
using System.Collections.Generic;
6+
using System.Runtime.CompilerServices;
67

78
namespace System.ComponentModel
89
{
910
/// <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.
11+
/// Provides a hashtable-like collection that stores keys and values using weak references.
12+
/// This class is a thin wrapper around <see cref="ConditionalWeakTable{TKey, TValue}"/>
13+
/// and is especially useful for associating data with objects from unloadable assemblies.
1314
/// </summary>
14-
internal sealed class WeakHashtable : Hashtable
15+
internal sealed class WeakHashtable : IEnumerable<KeyValuePair<object, object?>>
1516
{
16-
private static readonly IEqualityComparer s_comparer = new WeakKeyComparer();
17+
private readonly ConditionalWeakTable<object, object?> _hashtable = new ConditionalWeakTable<object, object?>();
1718

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
19+
public object? this[object key]
12320
{
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;
21+
get => _hashtable.TryGetValue(key, out object? value) ? value : null;
22+
set => _hashtable.AddOrUpdate(key, value);
19223
}
19324

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-
}
25+
public bool ContainsKey(object key) => _hashtable.TryGetValue(key, out object? _);
22026

221-
public object? Value => _baseEnumerator.Value;
27+
public void Remove(object key) => _hashtable.Remove(key);
22228

223-
public object Current => Entry;
29+
public IEnumerator<KeyValuePair<object, object?>> GetEnumerator() => ((IEnumerable<KeyValuePair<object, object?>>)_hashtable).GetEnumerator();
22430

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-
}
31+
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable<KeyValuePair<object, object?>>)_hashtable).GetEnumerator();
25332
}
25433
}

0 commit comments

Comments
 (0)