Skip to content

Commit 96bc926

Browse files
Use separate hashtables for collectible types to support assembly unloadability for the TypeDescriptor class
1 parent 0d20f9a commit 96bc926

11 files changed

+473
-35
lines changed

src/libraries/System.ComponentModel.TypeConverter/System.ComponentModel.TypeConverter.sln

+7
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "tools\ref", "{0507C9
5757
EndProject
5858
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{4690D584-4BB0-4C37-9887-6ED204389F4D}"
5959
EndProject
60+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnloadableTestTypes", "tests\UnloadableTestTypes\UnloadableTestTypes.csproj", "{E90D2C56-7790-C4B7-FF62-7D33707AC8E8}"
61+
EndProject
6062
Global
6163
GlobalSection(SolutionConfigurationPlatforms) = preSolution
6264
Debug|Any CPU = Debug|Any CPU
@@ -147,6 +149,10 @@ Global
147149
{C4C8D5AF-7402-4C82-BB99-B8EB18A52E8A}.Debug|Any CPU.Build.0 = Debug|Any CPU
148150
{C4C8D5AF-7402-4C82-BB99-B8EB18A52E8A}.Release|Any CPU.ActiveCfg = Release|Any CPU
149151
{C4C8D5AF-7402-4C82-BB99-B8EB18A52E8A}.Release|Any CPU.Build.0 = Release|Any CPU
152+
{E90D2C56-7790-C4B7-FF62-7D33707AC8E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
153+
{E90D2C56-7790-C4B7-FF62-7D33707AC8E8}.Debug|Any CPU.Build.0 = Debug|Any CPU
154+
{E90D2C56-7790-C4B7-FF62-7D33707AC8E8}.Release|Any CPU.ActiveCfg = Release|Any CPU
155+
{E90D2C56-7790-C4B7-FF62-7D33707AC8E8}.Release|Any CPU.Build.0 = Release|Any CPU
150156
EndGlobalSection
151157
GlobalSection(SolutionProperties) = preSolution
152158
HideSolutionNode = FALSE
@@ -176,6 +182,7 @@ Global
176182
{82259970-A9FA-4264-A47F-7B697B317AE6} = {4690D584-4BB0-4C37-9887-6ED204389F4D}
177183
{C4C8D5AF-7402-4C82-BB99-B8EB18A52E8A} = {0507C945-5BC2-4234-AF07-D766E2CCCDEC}
178184
{0507C945-5BC2-4234-AF07-D766E2CCCDEC} = {4690D584-4BB0-4C37-9887-6ED204389F4D}
185+
{E90D2C56-7790-C4B7-FF62-7D33707AC8E8} = {E80037BD-B4E3-43B7-A7C8-F6C3C6DA4EF8}
179186
EndGlobalSection
180187
GlobalSection(ExtensibilityGlobals) = postSolution
181188
SolutionGuid = {1D4639D9-A4BD-410A-9491-E5F8D82DE8BF}

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

+2
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\ContextAwareConcurrentDictionary.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">
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
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 ContextAwareConcurrentDictionary<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, TValue> _collectibleTable = new ConditionalWeakTable<TKey, TValue>();
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+
return !key.IsCollectible ? _defaultTable.TryGetValue(key, out value) : _collectibleTable.TryGetValue(key, out value);
53+
}
54+
55+
public void TryAdd(TKey key, TValue value)
56+
{
57+
if (!key.IsCollectible)
58+
{
59+
_defaultTable.TryAdd(key, value);
60+
}
61+
else
62+
{
63+
_collectibleTable.TryAdd(key, value);
64+
}
65+
}
66+
67+
public void Clear()
68+
{
69+
_defaultTable.Clear();
70+
_collectibleTable.Clear();
71+
}
72+
73+
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => new Enumerator(_defaultTable.GetEnumerator(), ((IEnumerable<KeyValuePair<TKey, TValue>>)_collectibleTable).GetEnumerator());
74+
75+
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
76+
77+
/// <summary>
78+
/// A wrapper around the base HashTable enumerator that skips
79+
/// collected keys and captures the key value to prevent it
80+
/// from being collected during enumeration.
81+
/// </summary>
82+
private sealed class Enumerator : IEnumerator<KeyValuePair<TKey, TValue>>
83+
{
84+
private readonly IEnumerator<KeyValuePair<TKey, TValue>> _defaultEnumerator;
85+
private readonly IEnumerator<KeyValuePair<TKey, TValue>> _collectibleEnumerator;
86+
private bool _enumeratingCollectibleEnumerator;
87+
88+
// Captured key of the WeakHashtable so that it is not collected during enumeration
89+
private KeyValuePair<TKey, TValue> _current;
90+
91+
public Enumerator(IEnumerator<KeyValuePair<TKey, TValue>> defaultEnumerator, IEnumerator<KeyValuePair<TKey, TValue>> collectibleEnumerator)
92+
{
93+
_defaultEnumerator = defaultEnumerator;
94+
_collectibleEnumerator = collectibleEnumerator;
95+
_enumeratingCollectibleEnumerator = false;
96+
}
97+
98+
~Enumerator()
99+
{
100+
Dispose();
101+
}
102+
103+
public object Current => ((IEnumerator<KeyValuePair<TKey, TValue>>)this).Current;
104+
105+
KeyValuePair<TKey, TValue> IEnumerator<KeyValuePair<TKey, TValue>>.Current
106+
{
107+
get
108+
{
109+
// Call the default enumerator to ensure we are in a correct state
110+
// and use the cached current value for the collectible part of the enumeration.
111+
return !_enumeratingCollectibleEnumerator ? _defaultEnumerator.Current : _current;
112+
}
113+
}
114+
115+
public void Dispose()
116+
{
117+
_defaultEnumerator.Dispose();
118+
_collectibleEnumerator.Dispose();
119+
120+
GC.SuppressFinalize(this);
121+
}
122+
123+
public bool MoveNext()
124+
{
125+
if (!_enumeratingCollectibleEnumerator)
126+
{
127+
// Try to get the next item from the default enumerator.
128+
if (_defaultEnumerator.MoveNext())
129+
{
130+
return true;
131+
}
132+
// If we are done with the default enumerator, switch to the collectible enumerator.
133+
_enumeratingCollectibleEnumerator = true;
134+
}
135+
136+
if (_collectibleEnumerator.MoveNext())
137+
{
138+
_current = _collectibleEnumerator.Current;
139+
return true;
140+
}
141+
142+
// Reset the captured current value to allow the object collection.
143+
_current = default;
144+
return false;
145+
}
146+
147+
public void Reset()
148+
{
149+
_defaultEnumerator.Reset();
150+
_collectibleEnumerator.Reset();
151+
_current = default;
152+
}
153+
}
154+
}
155+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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+
7+
namespace System.ComponentModel
8+
{
9+
/// <summary>
10+
/// Hashtable that maps MemberInfo object key to an object and
11+
/// uses a WeakReference for the collectible keys (if MemberInfo.IsCollectible is true).
12+
/// Uses a Hashtable for non-collectible keys and WeakHashtable for the collectible keys.
13+
/// </summary>
14+
internal sealed class ContextAwareHashtable<TValue> where TValue : class
15+
{
16+
private readonly Hashtable _defaultTable = new Hashtable();
17+
private readonly WeakHashtable _collectibleTable = new WeakHashtable();
18+
19+
public TValue? this[MemberInfo key]
20+
{
21+
get
22+
{
23+
return (TValue?)(!key.IsCollectible ? _defaultTable[key] : ((WeakReference?)_collectibleTable[key])?.Target);
24+
}
25+
26+
set
27+
{
28+
if (key.IsCollectible)
29+
{
30+
_collectibleTable[key] = new WeakReference(value);
31+
}
32+
else
33+
{
34+
_defaultTable[key] = value;
35+
}
36+
}
37+
}
38+
}
39+
}

0 commit comments

Comments
 (0)