Skip to content

Commit 84fa34e

Browse files
committed
Refactor reflection cache API and add ALC safety
Renamed cache methods for consistency and clarity. Introduced dedicated property cache dictionaries and improved ALC unload handling. Added ReflectionCacheEntryAlcNotAvailableException for unavailable cache entries. Expanded PropertyDataView with TryGet methods for safe access. Updated code to modern C# syntax. Improved memory safety by exposing only views, not strong references, to cache entries.
1 parent 5c4a7d6 commit 84fa34e

19 files changed

+587
-148
lines changed

src/BionicCode.Utilities.Reflection/DelegateProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2002,7 +2002,7 @@ private static Expression CreateTargetTypeMismatchExceptionExpression(PropertyDa
20022002
typeof(string),
20032003
])!;
20042004

2005-
TypeData helperExtensionsCommonTypeData = SymbolReflectionInfoCache.GetOrCreateSymbolInfoDataCacheEntryInternal(typeof(ReflectionHelperExtensions));
2005+
TypeData helperExtensionsCommonTypeData = SymbolReflectionInfoCache.GetOrCreateEntryInternal(typeof(ReflectionHelperExtensions));
20062006
const string extensionMethodName = nameof(ReflectionHelperExtensions.ToFullyQualifiedSignatureName);
20072007
var typeCacheKey = SymbolReflectionInfoCacheKeyInternal.CreateForType(typeof(Type));
20082008
MethodData toFullyQualifiedSignatureNameExtensionMethodData = helperExtensionsCommonTypeData.Methods[extensionMethodName, new MethodParameterInfo(typeCacheKey)];

src/BionicCode.Utilities.Reflection/EventData.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ private void ThrowIfInvalidMethodArguments(object?[]? args)
143143
internal MethodData EventInvokerMethodData => _invocatorMethodData ??= EventHandlerTypeData?.DelegateInvokeMethodData!;
144144

145145
internal TypeData EventHandlerTypeData => _eventHandlerTypeData ??= EventInfo.EventHandlerType is Type eventHandlerType
146-
? SymbolReflectionInfoCache.GetOrCreateSymbolInfoDataCacheEntryInternal(eventHandlerType)
146+
? SymbolReflectionInfoCache.GetOrCreateEntryInternal(eventHandlerType)
147147
: throw new NotSupportedException($"The underlying '{typeof(EventInfo).FullName}' for event '{EventInfo.Name}' does not have an event handler type.");
148148

149149
internal EventInfo EventInfo { get; }
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
namespace BionicCode.Utilities.Net.Reflection.Exceptions;
2+
3+
using System;
4+
5+
/// <summary>
6+
/// Exception thrown when the AssemblyLoadContext associated with a cache entry is not available since it had been unloaded.
7+
/// </summary>
8+
public class ReflectionCacheEntryAlcNotAvailableException : Exception
9+
{
10+
private const string DefaultMessage = "The AssemblyLoadContext associated with the cache entry is not available since it had been unloaded.";
11+
12+
public ReflectionCacheEntryAlcNotAvailableException()
13+
: base(DefaultMessage)
14+
{
15+
}
16+
17+
public ReflectionCacheEntryAlcNotAvailableException(string? message)
18+
: base(message ?? DefaultMessage)
19+
{
20+
}
21+
22+
public ReflectionCacheEntryAlcNotAvailableException(string? message, Exception? innerException)
23+
: base(message ?? DefaultMessage, innerException)
24+
{
25+
}
26+
}

src/BionicCode.Utilities.Reflection/FieldData.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ internal void SetValue(object? target, object? value)
209209

210210
internal override bool IsStatic => _isStatic ??= FieldInfo.IsStatic;
211211

212-
internal TypeData FieldTypeData => _fieldTypeData ??= SymbolReflectionInfoCache.GetOrCreateSymbolInfoDataCacheEntryInternal(FieldInfo.FieldType);
212+
internal TypeData FieldTypeData => _fieldTypeData ??= SymbolReflectionInfoCache.GetOrCreateEntryInternal(FieldInfo.FieldType);
213213

214214
internal bool IsRef => _isRef ??= FieldTypeData.IsByRef;
215215

src/BionicCode.Utilities.Reflection/MemberData.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,12 @@ private BindingFlags ComputeVisibilityBindingFlagsMask()
4545

4646
internal TypeData DeclaringTypeData
4747
=> _declaringTypeData ??= Type.GetTypeFromHandle(DeclaringTypeHandle) is Type declaringType
48-
? SymbolReflectionInfoCache.GetOrCreateSymbolInfoDataCacheEntryInternal(declaringType)
48+
? GetOrCreateCacheEntry(declaringType)
4949
: throw new InvalidOperationException($"The runtime type handle returned from the property '{nameof(DeclaringTypeHandle)}' is not valid.");
5050

5151
internal TypeData ImplementingTypeData
5252
=> _implementingTypeData ??= Type.GetTypeFromHandle(ImplementingTypeHandle) is Type implementingType
53-
? SymbolReflectionInfoCache.GetOrCreateSymbolInfoDataCacheEntryInternal(implementingType)
53+
? GetOrCreateCacheEntry(implementingType)
5454
: throw new InvalidOperationException($"The runtime type handle returned from the property '{nameof(ImplementingTypeHandle)}' is not valid.");
5555

5656
internal override string Namespace

src/BionicCode.Utilities.Reflection/MethodData.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1196,7 +1196,7 @@ internal bool IsAwaitableGenericTask
11961196

11971197
internal override string AssemblyName => _assemblyName ??= DeclaringTypeData.AssemblyName;
11981198

1199-
internal TypeData ReturnTypeData => _returnTypeData ??= SymbolReflectionInfoCache.GetOrCreateSymbolInfoDataCacheEntryInternal(MethodInfo.ReturnType);
1199+
internal TypeData ReturnTypeData => _returnTypeData ??= SymbolReflectionInfoCache.GetOrCreateEntryInternal(MethodInfo.ReturnType);
12001200

12011201
internal bool IsGenericMethod => _isGenericMethod ??= MethodInfo.IsGenericMethod;
12021202

src/BionicCode.Utilities.Reflection/MethodParameterInfoListBuilder.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ private static MethodParameterInfoList CreateInternal(MethodBase methodBase)
129129
return MethodParameterInfoList.Empty;
130130
}
131131

132-
IEnumerable<ParameterData> parameters = parameterInfoList.Select(SymbolReflectionInfoCache.GetOrCreateSymbolInfoDataCacheEntry);
132+
IEnumerable<ParameterData> parameters = parameterInfoList.Select(SymbolReflectionInfoCache.GetOrCreateCacheEntry);
133133

134134
return parameters.AsMethodParameterInfoList();
135135
}
@@ -142,7 +142,7 @@ private static MethodParameterInfoList CreateInternal(PropertyInfo propertyInfo)
142142
return MethodParameterInfoList.Empty;
143143
}
144144

145-
IEnumerable<ParameterData> parameters = indexParameters.Select(SymbolReflectionInfoCache.GetOrCreateSymbolInfoDataCacheEntry);
145+
IEnumerable<ParameterData> parameters = indexParameters.Select(SymbolReflectionInfoCache.GetOrCreateCacheEntry);
146146
return parameters.AsMethodParameterInfoList();
147147
}
148148

src/BionicCode.Utilities.Reflection/ParameterData.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ internal ParameterData(SymbolReflectionInfoCacheKeyInternal symbolInfoDataCacheK
169169
_ => throw new NotImplementedException(),
170170
};
171171

172-
internal TypeData ParameterTypeData => _parameterTypeData ??= SymbolReflectionInfoCache.GetOrCreateSymbolInfoDataCacheEntryInternal(ParameterInfo.ParameterType);
172+
internal TypeData ParameterTypeData => _parameterTypeData ??= SymbolReflectionInfoCache.GetOrCreateEntryInternal(ParameterInfo.ParameterType);
173173

174174
internal TypeData DeclaringTypeData => _declaringTypeData ??= MemberData.DeclaringTypeData;
175175

src/BionicCode.Utilities.Reflection/ParameterListBuilder.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ internal static ParameterList Create(IEnumerable<ParameterInfo>? items)
3636
SymbolInfoData? member = null;
3737
foreach (ParameterInfo parameterInfo in parameterInfoList)
3838
{
39-
ParameterData parameterData = SymbolReflectionInfoCache.GetOrCreateSymbolInfoDataCacheEntry(parameterInfo);
39+
ParameterData parameterData = SymbolReflectionInfoCache.GetOrCreateCacheEntry(parameterInfo);
4040

4141
if (member == null)
4242
{
@@ -123,7 +123,7 @@ internal static ParameterList Create(MethodBase methodBase)
123123
return ParameterList.Empty;
124124
}
125125

126-
IEnumerable<ParameterData> parameters = parameterInfoList.Select(SymbolReflectionInfoCache.GetOrCreateSymbolInfoDataCacheEntry);
126+
IEnumerable<ParameterData> parameters = parameterInfoList.Select(SymbolReflectionInfoCache.GetOrCreateCacheEntry);
127127

128128
return parameters.ToParameterList();
129129
}

src/BionicCode.Utilities.Reflection/PropertyData.cs

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ internal sealed class PropertyData : MemberData, IPropertyDataInvoker
5454
private RuntimeTypeHandle? _declaringTypeHandle;
5555
private RuntimeTypeHandle? _implementingTypeHandle;
5656
private bool? _isExplicitInterfaceImplementation;
57+
private IPropertyDataView? _propertyDataView;
5758

5859
internal PropertyData(SymbolReflectionInfoCacheKeyInternal symbolInfoDataCacheKey)
5960
: base(symbolInfoDataCacheKey.PropertyDescriptor.PropertyName, SymbolKind.MemberProperty, symbolInfoDataCacheKey)
@@ -766,8 +767,7 @@ internal IndexerPropertyGetter<TTarget, TIndex1, TIndex2, TIndex3, TValue> GetIn
766767
return GetIndexerGetterInternal<TTarget, TIndex1, TIndex2, TIndex3, TValue>();
767768
}
768769

769-
private Func<object?, object?> GetPropertyGetterInternal()
770-
=> _propertyGetInvoker ??= DelegateProvider.CreateGetter(this);
770+
private Func<object?, object?> GetPropertyGetterInternal() => _propertyGetInvoker ??= DelegateProvider.CreateGetter(this);
771771

772772
private PropertyGetter<TTarget, TValue> GetPropertyGetterInternal<TTarget, TValue>()
773773
{
@@ -779,8 +779,7 @@ private PropertyGetter<TTarget, TValue> GetPropertyGetterInternal<TTarget, TValu
779779
return (PropertyGetter<TTarget, TValue>)invoker;
780780
}
781781

782-
private Func<object?, object[], object?> GetIndexerGetterInternal()
783-
=> _indexerPropertyGetInvoker ??= DelegateProvider.CreateIndexerGetter(this);
782+
private Func<object?, object[], object?> GetIndexerGetterInternal() => _indexerPropertyGetInvoker ??= DelegateProvider.CreateIndexerGetter(this);
784783

785784
private IndexerPropertyGetter<TTarget, TIndex, TValue> GetIndexerGetterInternal<TTarget, TIndex, TValue>()
786785
{
@@ -822,8 +821,7 @@ private IndexerPropertyGetter<TTarget, TIndex1, TIndex2, TIndex3, TValue> GetInd
822821
return (IndexerPropertyGetter<TTarget, TIndex1, TIndex2, TIndex3, TValue>)invoker;
823822
}
824823

825-
private Action<object?, object?> GetSetInvokerInternal()
826-
=> _propertySetInvoker ??= DelegateProvider.CreateSetter(this);
824+
private Action<object?, object?> GetSetInvokerInternal() => _propertySetInvoker ??= DelegateProvider.CreateSetter(this);
827825

828826
private PropertySetter<TTarget, TValue> GetSetInvokerInternal<TTarget, TValue>() where TTarget : class
829827
{
@@ -843,6 +841,9 @@ private void GetAccessors()
843841
_getAccessorAccessModifier = getMethodModifier;
844842
}
845843

844+
internal IPropertyDataView View => _propertyDataView
845+
??= new PropertyDataView(SymbolReflectionInfoCacheKey.CreateForProperty(this));
846+
846847
internal bool IsIndexer => CanRead
847848
? PropertyGetMethodParameters.HasItems
848849
: CanWrite && PropertySetMethodParameters.Count > 1;
@@ -896,7 +897,7 @@ internal AccessModifier GetAccessorAccessModifier
896897
}
897898
}
898899

899-
internal TypeData PropertyTypeData => _propertyTypeData ??= SymbolReflectionInfoCache.GetOrCreateSymbolInfoDataCacheEntryInternal(PropertyInfo.PropertyType);
900+
internal TypeData PropertyTypeData => _propertyTypeData ??= GetOrCreateCacheEntry(PropertyInfo.PropertyType);
900901

901902
internal PropertyInfo PropertyInfo { get; }
902903

@@ -915,17 +916,24 @@ internal AccessModifier GetAccessorAccessModifier
915916

916917
internal bool CanRead => _canRead ??= PropertyInfo.CanRead;
917918

919+
/// <summary>
920+
/// Returns whether the property is an init-only property.
921+
/// </summary>
922+
/// <remarks>This is determined by checking if the property has a setter method and if that setter method is marked with the <see cref="IsExternalInit"/> attribute,
923+
/// which is used by the C# compiler to indicate init-only properties.
924+
/// If the property does not have a setter or if the setter is not marked as init-only, this property returns <see langword="false"/>.</remarks>
925+
/// <value><see langword="true"/> if the property is an init-only property; otherwise, <see langword="false"/>.</value>
918926
internal bool IsInit => _isInit ??= CanWrite && PropertyData.IsPropertyInit(this);
919927

920928
internal MethodData PropertyGetMethodData => _getMethodData ??= PropertyInfo is PropertyInfo propertyInfo && propertyInfo.CanRead
921929
? propertyInfo.GetGetMethod(true) is MethodInfo propertyGetter
922-
? SymbolReflectionInfoCache.GetOrCreateSymbolReflectionInfoCacheEntry(propertyGetter)
930+
? GetOrCreateCacheEntry(propertyGetter)
923931
: throw new NotSupportedException($"The underlying '{typeof(PropertyInfo).FullName}' for property '{PropertyInfo.Name}' does not have a get method.")
924932
: throw new NotSupportedException($"The underlying '{typeof(PropertyInfo).FullName}' for property '{PropertyInfo.Name}' does not have a get method. Check '{nameof(PropertyData)}.{nameof(PropertyData.CanRead)}' before access.");
925933

926934
internal MethodData PropertySetMethodData => _setMethodData ??= PropertyInfo is PropertyInfo propertyInfo && propertyInfo.CanWrite
927935
? propertyInfo.GetSetMethod(true) is MethodInfo propertySetter
928-
? SymbolReflectionInfoCache.GetOrCreateSymbolReflectionInfoCacheEntry(propertySetter)
936+
? GetOrCreateCacheEntry(propertySetter)
929937
: throw new NotSupportedException($"The underlying '{typeof(PropertyInfo).FullName}' for property '{PropertyInfo.Name}' does not have a set method.")
930938
: throw new NotSupportedException($"The underlying '{typeof(PropertyInfo).FullName}' for property '{PropertyInfo.Name}' does not have a set method. Check '{nameof(PropertyData)}.{nameof(PropertyData.CanWrite)}' before access.");
931939

0 commit comments

Comments
 (0)