Skip to content

Commit d0f61a2

Browse files
Use separate hashtables for collectible types to support assembly unloadability for the TypeDescriptor class
1 parent 27f527e commit d0f61a2

11 files changed

+425
-28
lines changed

src/libraries/System.ComponentModel.TypeConverter/System.ComponentModel.TypeConverter.slnx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,9 @@
263263
<Build Solution="Checked|x64" Project="false" />
264264
<Build Solution="Checked|x86" Project="false" />
265265
</Project>
266+
<Project Path="tests/UnloadableTestTypes/UnloadableTestTypes.csproj">
267+
<BuildType Solution="Checked|*" Project="Release" />
268+
</Project>
266269
</Folder>
267270
<Folder Name="/tools/" />
268271
<Folder Name="/tools/gen/">

src/libraries/System.ComponentModel.TypeConverter/src/System.ComponentModel.TypeConverter.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<Compile Include="System\ComponentModel\ByteConverter.cs" />
1616
<Compile Include="System\ComponentModel\CharConverter.cs" />
1717
<Compile Include="System\ComponentModel\CollectionConverter.cs" />
18+
<Compile Include="System\ComponentModel\ContextAwareConcurrentHashtable.cs" />
1819
<Compile Include="System\ComponentModel\DateOnlyConverter.cs" />
1920
<Compile Include="System\ComponentModel\DateTimeConverter.cs" />
2021
<Compile Include="System\ComponentModel\DateTimeOffsetConverter.cs" />
@@ -44,6 +45,7 @@
4445
<Compile Include="System\ComponentModel\UInt64Converter.cs" />
4546
<Compile Include="System\ComponentModel\UriTypeConverter.cs" />
4647
<Compile Include="System\ComponentModel\VersionConverter.cs" />
48+
<Compile Include="System\ComponentModel\ContextAwareHashtable.cs" />
4749
<Compile Include="System\Timers\ElapsedEventArgs.cs" />
4850
<Compile Include="System\Timers\ElapsedEventHandler.cs" />
4951
<Compile Include="System\Timers\Timer.cs">
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
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+
4+
using System.Collections;
5+
using System.Collections.Concurrent;
6+
using System.Collections.Generic;
7+
using System.Diagnostics.CodeAnalysis;
8+
using System.Reflection;
9+
using System.Runtime.CompilerServices;
10+
11+
namespace System.ComponentModel
12+
{
13+
/// <summary>
14+
/// Concurrent dictionary that maps MemberInfo object key to an object.
15+
/// Uses ConditionalWeakTable for the collectible keys (if MemberInfo.IsCollectible is true) and
16+
/// ConcurrentDictionary for non-collectible keys.
17+
/// </summary>
18+
internal sealed class ContextAwareConcurrentHashtable<TKey, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TValue> : IEnumerable<KeyValuePair<TKey, TValue>>
19+
where TKey : MemberInfo
20+
where TValue : class?
21+
{
22+
private readonly ConcurrentDictionary<TKey, TValue> _defaultTable = new ConcurrentDictionary<TKey, TValue>();
23+
private readonly ConditionalWeakTable<TKey, object?> _collectibleTable = new ConditionalWeakTable<TKey, object?>();
24+
25+
public TValue? this[TKey key]
26+
{
27+
get
28+
{
29+
return TryGetValue(key, out TValue? value) ? value : default;
30+
}
31+
32+
set
33+
{
34+
if (!key.IsCollectible)
35+
{
36+
_defaultTable[key] = value!;
37+
}
38+
else
39+
{
40+
_collectibleTable.AddOrUpdate(key, value!);
41+
}
42+
}
43+
}
44+
45+
public bool Contains(TKey key)
46+
{
47+
return !key.IsCollectible ? _defaultTable.ContainsKey(key) : _collectibleTable.TryGetValue(key, out _);
48+
}
49+
50+
public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value)
51+
{
52+
if (!key.IsCollectible)
53+
return _defaultTable.TryGetValue(key, out value);
54+
55+
if (_collectibleTable.TryGetValue(key, out object? valueObj) && valueObj != null)
56+
{
57+
value = (TValue)valueObj;
58+
return true;
59+
}
60+
61+
value = default;
62+
return false;
63+
}
64+
65+
public bool TryAdd(TKey key, TValue value)
66+
{
67+
return !key.IsCollectible
68+
? _defaultTable.TryAdd(key, value)
69+
: _collectibleTable.TryAdd(key, value);
70+
}
71+
72+
public void Clear()
73+
{
74+
_defaultTable.Clear();
75+
_collectibleTable.Clear();
76+
}
77+
78+
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() =>
79+
new Enumerator(_defaultTable.GetEnumerator(), ((IEnumerable<KeyValuePair<TKey, object?>>)_collectibleTable).GetEnumerator());
80+
81+
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
82+
83+
private sealed class Enumerator : IEnumerator<KeyValuePair<TKey, TValue>>
84+
{
85+
private readonly IEnumerator<KeyValuePair<TKey, TValue>> _defaultEnumerator;
86+
private readonly IEnumerator<KeyValuePair<TKey, object?>> _collectibleEnumerator;
87+
private bool _enumeratingCollectibleEnumerator;
88+
89+
public Enumerator(IEnumerator<KeyValuePair<TKey, TValue>> defaultEnumerator, IEnumerator<KeyValuePair<TKey, object?>> collectibleEnumerator)
90+
{
91+
_defaultEnumerator = defaultEnumerator;
92+
_collectibleEnumerator = collectibleEnumerator;
93+
_enumeratingCollectibleEnumerator = false;
94+
}
95+
96+
public KeyValuePair<TKey, TValue> Current { get; private set; }
97+
98+
object IEnumerator.Current => Current;
99+
100+
public void Dispose()
101+
{
102+
_defaultEnumerator.Dispose();
103+
_collectibleEnumerator.Dispose();
104+
}
105+
106+
public bool MoveNext()
107+
{
108+
if (!_enumeratingCollectibleEnumerator && _defaultEnumerator.MoveNext())
109+
{
110+
Current = _defaultEnumerator.Current;
111+
return true;
112+
}
113+
114+
_enumeratingCollectibleEnumerator = true;
115+
116+
while (_collectibleEnumerator.MoveNext())
117+
{
118+
if (_collectibleEnumerator.Current.Value is TValue value)
119+
{
120+
Current = new KeyValuePair<TKey, TValue>(_collectibleEnumerator.Current.Key, value);
121+
return true;
122+
}
123+
}
124+
125+
Current = default;
126+
return false;
127+
}
128+
129+
public void Reset()
130+
{
131+
_defaultEnumerator.Reset();
132+
_collectibleEnumerator.Reset();
133+
Current = default;
134+
}
135+
}
136+
}
137+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
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+
4+
using System.Collections;
5+
using System.Reflection;
6+
using System.Runtime.CompilerServices;
7+
8+
namespace System.ComponentModel
9+
{
10+
/// <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.
14+
/// </summary>
15+
internal sealed class ContextAwareHashtable
16+
{
17+
private readonly Hashtable _defaultTable = new Hashtable();
18+
private readonly ConditionalWeakTable<object, object> _collectibleTable = new ConditionalWeakTable<object, object>();
19+
20+
public object? this[MemberInfo key]
21+
{
22+
get
23+
{
24+
return !key.IsCollectible ? _defaultTable[key] : (_collectibleTable.TryGetValue(key, out object? value) ? value : null);
25+
}
26+
27+
set
28+
{
29+
if (!key.IsCollectible)
30+
{
31+
_defaultTable[key] = value!;
32+
}
33+
else
34+
{
35+
_collectibleTable.AddOrUpdate(key, value!);
36+
}
37+
}
38+
}
39+
}
40+
}

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

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
using System.Collections;
55
using System.Collections.Generic;
6-
using System.Collections.Concurrent;
76
using System.ComponentModel.Design;
87
using System.Diagnostics;
98
using System.Diagnostics.CodeAnalysis;
@@ -24,7 +23,7 @@ namespace System.ComponentModel
2423
internal sealed partial class ReflectTypeDescriptionProvider : TypeDescriptionProvider
2524
{
2625
// ReflectedTypeData contains all of the type information we have gathered for a given type.
27-
private readonly ConcurrentDictionary<Type, ReflectedTypeData> _typeData = new ConcurrentDictionary<Type, ReflectedTypeData>();
26+
private readonly ContextAwareConcurrentHashtable<Type, ReflectedTypeData> _typeData = new ContextAwareConcurrentHashtable<Type, ReflectedTypeData>();
2827

2928
// This is the signature we look for when creating types that are generic, but
3029
// want to know what type they are dealing with. Enums are a good example of this;
@@ -49,10 +48,10 @@ internal sealed partial class ReflectTypeDescriptionProvider : TypeDescriptionPr
4948
// on Control, Component and object are also automatically filled
5049
// in. The keys to the property and event caches are types.
5150
// The keys to the attribute cache are either MemberInfos or types.
52-
private static Hashtable? s_propertyCache;
53-
private static Hashtable? s_eventCache;
54-
private static Hashtable? s_attributeCache;
55-
private static Hashtable? s_extendedPropertyCache;
51+
private static ContextAwareHashtable? s_propertyCache;
52+
private static ContextAwareHashtable? s_eventCache;
53+
private static ContextAwareHashtable? s_attributeCache;
54+
private static ContextAwareHashtable? s_extendedPropertyCache;
5655

5756
// These are keys we stuff into our object cache. We use this
5857
// cache data to store extender provider info for an object.
@@ -193,13 +192,13 @@ private static Dictionary<object, IntrinsicTypeConverterData> IntrinsicTypeConve
193192
Justification = "IntrinsicTypeConverters is marked with RequiresUnreferencedCode. It is the only place that should call this.")]
194193
private static NullableConverter CreateNullableConverter(Type type) => new NullableConverter(type);
195194

196-
private static Hashtable PropertyCache => LazyInitializer.EnsureInitialized(ref s_propertyCache, () => new Hashtable());
195+
private static ContextAwareHashtable PropertyCache => LazyInitializer.EnsureInitialized(ref s_propertyCache, () => new ContextAwareHashtable());
197196

198-
private static Hashtable EventCache => LazyInitializer.EnsureInitialized(ref s_eventCache, () => new Hashtable());
197+
private static ContextAwareHashtable EventCache => LazyInitializer.EnsureInitialized(ref s_eventCache, () => new ContextAwareHashtable());
199198

200-
private static Hashtable AttributeCache => LazyInitializer.EnsureInitialized(ref s_attributeCache, () => new Hashtable());
199+
private static ContextAwareHashtable AttributeCache => LazyInitializer.EnsureInitialized(ref s_attributeCache, () => new ContextAwareHashtable());
201200

202-
private static Hashtable ExtendedPropertyCache => LazyInitializer.EnsureInitialized(ref s_extendedPropertyCache, () => new Hashtable());
201+
private static ContextAwareHashtable ExtendedPropertyCache => LazyInitializer.EnsureInitialized(ref s_extendedPropertyCache, () => new ContextAwareHashtable());
203202

204203
/// <summary>Clear the global caches this maintains on top of reflection.</summary>
205204
internal static void ClearReflectionCaches()
@@ -982,14 +981,14 @@ private ReflectedTypeData GetTypeDataFromRegisteredType(Type type)
982981
{
983982
Type componentType = typeof(T);
984983

985-
if (_typeData.ContainsKey(componentType))
984+
if (_typeData.Contains(componentType))
986985
{
987986
return;
988987
}
989988

990989
lock (TypeDescriptor.s_commonSyncObject)
991990
{
992-
if (_typeData.ContainsKey(componentType))
991+
if (_typeData.Contains(componentType))
993992
{
994993
return;
995994
}
@@ -1096,7 +1095,7 @@ internal bool IsPopulated(Type type)
10961095
/// </summary>
10971096
internal static Attribute[] ReflectGetAttributes(Type type)
10981097
{
1099-
Hashtable attributeCache = AttributeCache;
1098+
ContextAwareHashtable attributeCache = AttributeCache;
11001099
Attribute[]? attrs = (Attribute[]?)attributeCache[type];
11011100
if (attrs != null)
11021101
{
@@ -1124,7 +1123,7 @@ internal static Attribute[] ReflectGetAttributes(Type type)
11241123
/// </summary>
11251124
internal static Attribute[] ReflectGetAttributes(MemberInfo member)
11261125
{
1127-
Hashtable attributeCache = AttributeCache;
1126+
ContextAwareHashtable attributeCache = AttributeCache;
11281127
Attribute[]? attrs = (Attribute[]?)attributeCache[member];
11291128
if (attrs != null)
11301129
{
@@ -1152,7 +1151,7 @@ internal static Attribute[] ReflectGetAttributes(MemberInfo member)
11521151
/// </summary>
11531152
private static EventDescriptor[] ReflectGetEvents(Type type)
11541153
{
1155-
Hashtable eventCache = EventCache;
1154+
ContextAwareHashtable eventCache = EventCache;
11561155
EventDescriptor[]? events = (EventDescriptor[]?)eventCache[type];
11571156
if (events != null)
11581157
{
@@ -1252,7 +1251,7 @@ private static PropertyDescriptor[] ReflectGetExtendedProperties(IExtenderProvid
12521251
// property store.
12531252
//
12541253
Type providerType = provider.GetType();
1255-
Hashtable extendedPropertyCache = ExtendedPropertyCache;
1254+
ContextAwareHashtable extendedPropertyCache = ExtendedPropertyCache;
12561255
ReflectPropertyDescriptor[]? extendedProperties = (ReflectPropertyDescriptor[]?)extendedPropertyCache[providerType];
12571256
if (extendedProperties == null)
12581257
{
@@ -1337,7 +1336,7 @@ private static PropertyDescriptor[] ReflectGetPropertiesFromRegisteredType(Type
13371336

13381337
private static PropertyDescriptor[] ReflectGetPropertiesImpl(Type type)
13391338
{
1340-
Hashtable propertyCache = PropertyCache;
1339+
ContextAwareHashtable propertyCache = PropertyCache;
13411340
PropertyDescriptor[]? properties = (PropertyDescriptor[]?)propertyCache[type];
13421341
if (properties != null)
13431342
{

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

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

44
using System.Collections;
5-
using System.Collections.Concurrent;
6-
using System.Collections.Generic;
75
using System.Collections.Specialized;
86
using System.ComponentModel.Design;
97
using System.Diagnostics;
@@ -67,12 +65,12 @@ public sealed class TypeDescriptor
6765
internal static readonly object s_commonSyncObject = new object();
6866

6967
// A direct mapping from type to provider.
70-
private static readonly ConcurrentDictionary<Type, TypeDescriptionNode> s_providerTypeTable = new ConcurrentDictionary<Type, TypeDescriptionNode>();
68+
private static readonly ContextAwareConcurrentHashtable<Type, TypeDescriptionNode> s_providerTypeTable = new ContextAwareConcurrentHashtable<Type, TypeDescriptionNode>();
7169

7270
// Tracks DefaultTypeDescriptionProviderAttributes.
7371
// A value of `null` indicates initialization is in progress.
7472
// A value of s_initializedDefaultProvider indicates the provider is initialized.
75-
private static readonly ConcurrentDictionary<Type, object?> s_defaultProviderInitialized = new ConcurrentDictionary<Type, object?>();
73+
private static readonly ContextAwareConcurrentHashtable<Type, object?> s_defaultProviderInitialized = new ContextAwareConcurrentHashtable<Type, object?>();
7674

7775
private static readonly object s_initializedDefaultProvider = new object();
7876

@@ -293,7 +291,7 @@ public static void AddProvider(TypeDescriptionProvider provider, object instance
293291
refreshNeeded = s_providerTable.ContainsKey(instance);
294292
TypeDescriptionNode node = NodeFor(instance, true);
295293
var head = new TypeDescriptionNode(provider) { Next = node };
296-
s_providerTable.SetWeak(instance, head);
294+
s_providerTable[instance] = head;
297295
s_providerTypeTable.Clear();
298296
}
299297

@@ -363,7 +361,7 @@ private static void AddDefaultProvider(Type type)
363361
{
364362
bool providerAdded = false;
365363

366-
if (s_defaultProviderInitialized.ContainsKey(type))
364+
if (s_defaultProviderInitialized.Contains(type))
367365
{
368366
// Either another thread finished initializing for this type, or we are recursing on the same thread.
369367
return;
@@ -433,7 +431,7 @@ public static void CreateAssociation(object primary, object secondary)
433431
if (associations == null)
434432
{
435433
associations = new ArrayList(4);
436-
associationTable.SetWeak(primary, associations);
434+
associationTable[primary] = associations;
437435
}
438436
}
439437
}

0 commit comments

Comments
 (0)