Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1055,6 +1055,14 @@
<Build Solution="Checked|x64" Project="false" />
<Build Solution="Checked|x86" Project="false" />
</Project>
<Project Path="tests/UnloadableTestTypes/UnloadableTestTypes.csproj">
<BuildType Solution="Checked|*" Project="Release" />
<Build Solution="*|arm" Project="false" />
<Build Solution="*|arm64" Project="false" />
<Build Solution="Checked|Any CPU" Project="false" />
<Build Solution="Checked|x64" Project="false" />
<Build Solution="Checked|x86" Project="false" />
</Project>
</Folder>
<Folder Name="/tools/" />
<Folder Name="/tools/gen/">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<Compile Include="System\ComponentModel\ByteConverter.cs" />
<Compile Include="System\ComponentModel\CharConverter.cs" />
<Compile Include="System\ComponentModel\CollectionConverter.cs" />
<Compile Include="System\ComponentModel\CollectibleKeyConcurrentHashtable.cs" />
<Compile Include="System\ComponentModel\DateOnlyConverter.cs" />
<Compile Include="System\ComponentModel\DateTimeConverter.cs" />
<Compile Include="System\ComponentModel\DateTimeOffsetConverter.cs" />
Expand Down Expand Up @@ -44,6 +45,7 @@
<Compile Include="System\ComponentModel\UInt64Converter.cs" />
<Compile Include="System\ComponentModel\UriTypeConverter.cs" />
<Compile Include="System\ComponentModel\VersionConverter.cs" />
<Compile Include="System\ComponentModel\CollectibleKeyHashtable.cs" />
<Compile Include="System\Timers\ElapsedEventArgs.cs" />
<Compile Include="System\Timers\ElapsedEventHandler.cs" />
<Compile Include="System\Timers\Timer.cs">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;

namespace System.ComponentModel
{
/// <summary>
/// Concurrent dictionary that maps MemberInfo object key to an object.
/// Uses ConditionalWeakTable for the collectible keys (if MemberInfo.IsCollectible is true) and
/// ConcurrentDictionary for non-collectible keys.
/// </summary>
internal sealed class CollectibleKeyConcurrentHashtable<TKey, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TValue> : IEnumerable<KeyValuePair<TKey, TValue>>
where TKey : MemberInfo
where TValue : class?
{
private readonly ConcurrentDictionary<TKey, TValue> _defaultTable = new ConcurrentDictionary<TKey, TValue>();
private readonly ConditionalWeakTable<TKey, object?> _collectibleTable = new ConditionalWeakTable<TKey, object?>();

public TValue? this[TKey key]
{
get
{
return TryGetValue(key, out TValue? value) ? value : default;
}

set
{
if (!key.IsCollectible)
{
_defaultTable[key] = value!;
}
else
{
_collectibleTable.AddOrUpdate(key, value);
}
}
}

public bool ContainsKey(TKey key)
{
return !key.IsCollectible ? _defaultTable.ContainsKey(key) : _collectibleTable.TryGetValue(key, out _);
}

public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value)
{
if (!key.IsCollectible)
return _defaultTable.TryGetValue(key, out value);

if (_collectibleTable.TryGetValue(key, out object? valueObj) && valueObj != null)
{
value = (TValue)valueObj;
return true;
}

value = default;
return false;
}

public bool TryAdd(TKey key, TValue value)
{
return !key.IsCollectible
? _defaultTable.TryAdd(key, value)
: _collectibleTable.TryAdd(key, value);
}

public void Clear()
{
_defaultTable.Clear();
_collectibleTable.Clear();
}

public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() =>
new Enumerator(_defaultTable.GetEnumerator(), ((IEnumerable<KeyValuePair<TKey, object?>>)_collectibleTable).GetEnumerator());

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

private sealed class Enumerator : IEnumerator<KeyValuePair<TKey, TValue>>
{
private readonly IEnumerator<KeyValuePair<TKey, TValue>> _defaultEnumerator;
private readonly IEnumerator<KeyValuePair<TKey, object?>> _collectibleEnumerator;
private bool _enumeratingCollectibleEnumerator;

public Enumerator(IEnumerator<KeyValuePair<TKey, TValue>> defaultEnumerator, IEnumerator<KeyValuePair<TKey, object?>> collectibleEnumerator)
{
_defaultEnumerator = defaultEnumerator;
_collectibleEnumerator = collectibleEnumerator;
_enumeratingCollectibleEnumerator = false;
}

public KeyValuePair<TKey, TValue> Current { get; private set; }

object IEnumerator.Current => Current;

public void Dispose()
{
_defaultEnumerator.Dispose();
_collectibleEnumerator.Dispose();
}

public bool MoveNext()
{
if (!_enumeratingCollectibleEnumerator && _defaultEnumerator.MoveNext())
{
Current = _defaultEnumerator.Current;
return true;
}

_enumeratingCollectibleEnumerator = true;

while (_collectibleEnumerator.MoveNext())
{
if (_collectibleEnumerator.Current.Value is TValue value)
{
Current = new KeyValuePair<TKey, TValue>(_collectibleEnumerator.Current.Key, value);
return true;
}
}

Current = default;
return false;
}

public void Reset()
{
_defaultEnumerator.Reset();
_collectibleEnumerator.Reset();
_enumeratingCollectibleEnumerator = false;
Current = default;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections;
using System.Reflection;
using System.Runtime.CompilerServices;

namespace System.ComponentModel
{
/// <summary>
/// Hashtable that maps a <see cref="MemberInfo"/> object key to an associated value.
/// <para>
/// For keys where <see cref="MemberInfo.IsCollectible"/> is <c>false</c>, a standard <see cref="Hashtable"/> is used.
/// For keys where <see cref="MemberInfo.IsCollectible"/> is <c>true</c>, a <see cref="ConditionalWeakTable{TKey, TValue}"/> is used.
/// This ensures that collectible <see cref="MemberInfo"/> instances (such as those from collectible assemblies) do not prevent their assemblies from being unloaded.
/// </para>
/// </summary>
internal sealed class CollectibleKeyHashtable
{
private readonly Hashtable _defaultTable = new Hashtable();
private readonly ConditionalWeakTable<object, object?> _collectibleTable = new ConditionalWeakTable<object, object?>();

public object? this[MemberInfo key]
{
get
{
return !key.IsCollectible ? _defaultTable[key] : (_collectibleTable.TryGetValue(key, out object? value) ? value : null);
}

set
{
if (!key.IsCollectible)
{
_defaultTable[key] = value;
}
else
{
_collectibleTable.AddOrUpdate(key, value);
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

using System.Collections;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
Expand All @@ -24,7 +23,7 @@ namespace System.ComponentModel
internal sealed partial class ReflectTypeDescriptionProvider : TypeDescriptionProvider
{
// ReflectedTypeData contains all of the type information we have gathered for a given type.
private readonly ConcurrentDictionary<Type, ReflectedTypeData> _typeData = new ConcurrentDictionary<Type, ReflectedTypeData>();
private readonly CollectibleKeyConcurrentHashtable<Type, ReflectedTypeData> _typeData = new CollectibleKeyConcurrentHashtable<Type, ReflectedTypeData>();

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

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

private static Hashtable PropertyCache => LazyInitializer.EnsureInitialized(ref s_propertyCache, () => new Hashtable());
private static CollectibleKeyHashtable PropertyCache => LazyInitializer.EnsureInitialized(ref s_propertyCache, () => new CollectibleKeyHashtable());

private static Hashtable EventCache => LazyInitializer.EnsureInitialized(ref s_eventCache, () => new Hashtable());
private static CollectibleKeyHashtable EventCache => LazyInitializer.EnsureInitialized(ref s_eventCache, () => new CollectibleKeyHashtable());

private static Hashtable AttributeCache => LazyInitializer.EnsureInitialized(ref s_attributeCache, () => new Hashtable());
private static CollectibleKeyHashtable AttributeCache => LazyInitializer.EnsureInitialized(ref s_attributeCache, () => new CollectibleKeyHashtable());

private static Hashtable ExtendedPropertyCache => LazyInitializer.EnsureInitialized(ref s_extendedPropertyCache, () => new Hashtable());
private static CollectibleKeyHashtable ExtendedPropertyCache => LazyInitializer.EnsureInitialized(ref s_extendedPropertyCache, () => new CollectibleKeyHashtable());

/// <summary>Clear the global caches this maintains on top of reflection.</summary>
internal static void ClearReflectionCaches()
Expand Down Expand Up @@ -1096,7 +1095,7 @@ internal bool IsPopulated(Type type)
/// </summary>
internal static Attribute[] ReflectGetAttributes(Type type)
{
Hashtable attributeCache = AttributeCache;
CollectibleKeyHashtable attributeCache = AttributeCache;
Attribute[]? attrs = (Attribute[]?)attributeCache[type];
if (attrs != null)
{
Expand Down Expand Up @@ -1124,7 +1123,7 @@ internal static Attribute[] ReflectGetAttributes(Type type)
/// </summary>
internal static Attribute[] ReflectGetAttributes(MemberInfo member)
{
Hashtable attributeCache = AttributeCache;
CollectibleKeyHashtable attributeCache = AttributeCache;
Attribute[]? attrs = (Attribute[]?)attributeCache[member];
if (attrs != null)
{
Expand Down Expand Up @@ -1152,7 +1151,7 @@ internal static Attribute[] ReflectGetAttributes(MemberInfo member)
/// </summary>
private static EventDescriptor[] ReflectGetEvents(Type type)
{
Hashtable eventCache = EventCache;
CollectibleKeyHashtable eventCache = EventCache;
EventDescriptor[]? events = (EventDescriptor[]?)eventCache[type];
if (events != null)
{
Expand Down Expand Up @@ -1252,7 +1251,7 @@ private static PropertyDescriptor[] ReflectGetExtendedProperties(IExtenderProvid
// property store.
//
Type providerType = provider.GetType();
Hashtable extendedPropertyCache = ExtendedPropertyCache;
CollectibleKeyHashtable extendedPropertyCache = ExtendedPropertyCache;
ReflectPropertyDescriptor[]? extendedProperties = (ReflectPropertyDescriptor[]?)extendedPropertyCache[providerType];
if (extendedProperties == null)
{
Expand Down Expand Up @@ -1337,7 +1336,7 @@ private static PropertyDescriptor[] ReflectGetPropertiesFromRegisteredType(Type

private static PropertyDescriptor[] ReflectGetPropertiesImpl(Type type)
{
Hashtable propertyCache = PropertyCache;
CollectibleKeyHashtable propertyCache = PropertyCache;
PropertyDescriptor[]? properties = (PropertyDescriptor[]?)propertyCache[type];
if (properties != null)
{
Expand Down
Loading
Loading