Skip to content

Commit 1dae9c0

Browse files
committed
Enhance attribute handling and refactor reflection API
- Add IsDefined/HasCompilerAttribute methods for robust attribute detection - Replace GetMemberInfo/GetMethodBase with properties for clarity - Centralize cache entry creation with GetOrCreateCacheEntry methods - Refactor TypeListBuilder and TypeList for better API and lookup support - Change ToTypeData/ToMethodData to return view interfaces - Improve PropertyData/MethodData for readonly/init-only detection - Add ReflectionConstants for well-known attribute names - Make SymbolReflectionInfoCacheKey struct properties readonly - Improve documentation and code comments throughout
1 parent 599cb4a commit 1dae9c0

20 files changed

Lines changed: 553 additions & 309 deletions

‎src/BionicCode.Utilities.Net.Common/ReflectionConstants.cs‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
namespace BionicCode.Utilities.Net;
22

3+
using System.ComponentModel;
4+
using System.Runtime.InteropServices;
5+
36
/// <summary>
47
/// Provides a set of constant string values that are commonly used in reflection scenarios, particularly when working with properties, indexers, events, delegates, and operator methods in .NET. These constants can be used to ensure consistency and avoid hardcoding string literals throughout the codebase when performing reflection operations or generating code dynamically.
58
/// </summary>
@@ -81,4 +84,9 @@ public static class ReflectionConstants
8184
public const string OperatorMethodNamePrefix = "op_";
8285

8386
public const string PropertySetterValueParameterName = "value";
87+
88+
public const string IsReadOnlyAttributeFullName = "System.Runtime.CompilerServices.IsReadOnlyAttribute";
89+
public const string RequiresLocationAttributeFullName = "System.Runtime.CompilerServices.RequiresLocationAttribute";
90+
public const string InAttributeFullName = "System.Runtime.InteropServices.InAttribute";
91+
public const string IsExternalInitFullName = "System.Runtime.CompilerServices.IsExternalInit";
8492
}

‎src/BionicCode.Utilities.Reflection/ConstructorData.cs‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,7 @@ internal ConstructorData(SymbolReflectionInfoCacheKeyInternal symbolInfoDataCach
4949

5050
internal ConstructorInfo ConstructorInfo { get; }
5151

52-
internal override MethodBase GetMethodBase() => ConstructorInfo;
53-
54-
protected override MemberInfo GetMemberInfo() => ConstructorInfo;
52+
internal override MethodBase MethodBase => ConstructorInfo;
5553

5654
internal object Invoke(params object?[] arguments)
5755
{

‎src/BionicCode.Utilities.Reflection/EventData.cs‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ internal EventData(SymbolReflectionInfoCacheKeyInternal symbolInfoDataCacheKey)
5252
internal IEventDataView View => _eventDataView
5353
??= new EventDataView(SymbolReflectionInfoCacheKey.CreateForEvent(this));
5454

55-
protected override MemberInfo GetMemberInfo()
55+
protected override MemberInfo MemberInfo
5656
=> EventInfo;
5757

5858
internal object? RaiseEvent(object? target, params object?[]? arguments)
@@ -137,17 +137,17 @@ private void ThrowIfInvalidMethodArguments(object?[]? args)
137137
internal void RemoveEventHandler<TEventSource>(TEventSource eventSource, Delegate handler) => RemoveMethodData.Invoke(eventSource, handler);
138138

139139
internal MethodData AddMethodData => _addMethodData ??= EventInfo.GetAddMethod() is MethodInfo methodInfo
140-
? SymbolReflectionInfoCache.GetOrCreateSymbolReflectionInfoCacheEntry(methodInfo)
140+
? GetOrCreateCacheEntry(methodInfo)
141141
: throw new NotSupportedException($"The underlying '{typeof(EventInfo).FullName}' for event '{EventInfo.Name}' does not have an add method.");
142142

143143
internal MethodData RemoveMethodData => EventInfo.GetRemoveMethod() is MethodInfo methodInfo
144-
? SymbolReflectionInfoCache.GetOrCreateSymbolReflectionInfoCacheEntry(methodInfo)
144+
? GetOrCreateCacheEntry(methodInfo)
145145
: throw new NotSupportedException($"The underlying '{typeof(EventInfo).FullName}' for event '{EventInfo.Name}' does not have a remove method.");
146146

147147
internal MethodData EventInvokerMethodData => _invocatorMethodData ??= EventHandlerTypeData?.DelegateInvokeMethodData!;
148148

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

153153
internal EventInfo EventInfo { get; }
@@ -212,9 +212,9 @@ private void ThrowIfInvalidMethodArguments(object?[]? args)
212212
/// <inheritdoc/>
213213
internal override string AssemblyName => _assemblyName ??= DeclaringTypeData.AssemblyName;
214214

215-
internal bool CanAdd => _canAdd ??= _addMethodData is not null || (_addMethodData = EventInfo.GetAddMethod(true)?.ToMethodData()) is not null;
215+
internal bool CanAdd => _canAdd ??= _addMethodData is not null || (EventInfo.GetAddMethod(true) is MethodInfo addMethodInfo && (_addMethodData = GetOrCreateCacheEntry(addMethodInfo)) is not null);
216216

217-
internal bool CanRemove => _canRemove ??= _removeMethodData is not null || (_removeMethodData = EventInfo.GetRemoveMethod(true)?.ToMethodData()) is not null;
217+
internal bool CanRemove => _canRemove ??= _removeMethodData is not null || (EventInfo.GetRemoveMethod(true) is MethodInfo removeMethodInfo && (_removeMethodData = GetOrCreateCacheEntry(removeMethodInfo)) is not null);
218218

219219
internal bool IsOverride => _isOverride ??= AddMethodData!.IsOverride;
220220

‎src/BionicCode.Utilities.Reflection/FieldData.cs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ internal FieldData(SymbolReflectionInfoCacheKeyInternal symbolInfoDataCacheKey)
5656

5757
internal FieldInfo FieldInfo { get; }
5858

59-
protected override MemberInfo GetMemberInfo() => FieldInfo;
59+
protected override MemberInfo MemberInfo => FieldInfo;
6060

6161
internal object? GetValue(object? target) => (_getValueInvoker ??= DelegateProvider.CreateGetter(this)).Invoke(target);
6262

@@ -212,7 +212,7 @@ internal void SetValue(object? target, object? value)
212212

213213
internal override bool IsStatic => _isStatic ??= FieldInfo.IsStatic;
214214

215-
internal TypeData FieldTypeData => _fieldTypeData ??= SymbolReflectionInfoCache.GetOrCreateEntryInternal(FieldInfo.FieldType);
215+
internal TypeData FieldTypeData => _fieldTypeData ??= GetOrCreateCacheEntry(FieldInfo.FieldType);
216216

217217
internal bool IsRef => _isRef ??= FieldTypeData.IsByRef;
218218

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace BionicCode.Utilities.Net.Reflection;
2+
3+
internal interface IAttributableSymbol
4+
{
5+
bool HasCompilerAttribute(string attributeName, bool inherit = false);
6+
bool HasCompilerAttribute(Type? attributeType, string? attributeName = null, bool inherit = false);
7+
bool HasCompilerAttribute<TAttribute>(string? attributeName = null, bool inherit = false) where TAttribute : Attribute;
8+
bool IsDefined(Type attributeType, bool inherit = false);
9+
bool IsDefined<TAttribute>(bool inherit = false) where TAttribute : Attribute;
10+
}

‎src/BionicCode.Utilities.Reflection/IParameterDataView.cs‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,18 @@
22

33
public interface IParameterDataView : ISymbolInfoDataView
44
{
5+
bool IsDefined<TAttribute>(bool inherit = false) where TAttribute : Attribute;
6+
bool IsDefined(Type attributeType, bool inherit = false);
7+
bool HasOptionalCustomModifier(string modifierName);
8+
bool HasRequiredCustomModifier(string modifierName);
59
ITypeDataView DeclaringTypeData { get; }
610
RuntimeTypeHandle DeclaringTypeHandle { get; }
711
object? DefaultValue { get; }
812
bool IsByRef { get; }
913
bool IsGenericMethodParameter { get; }
1014
bool IsGenericTypeParameter { get; }
1115
bool IsIn { get; }
16+
bool IsMethodReturnParameter { get; }
1217
bool IsIndexerPropertyGetterParameter { get; }
1318
bool IsIndexerPropertyParameter { get; }
1419
bool IsIndexerPropertySetterParameter { get; }
@@ -24,4 +29,6 @@ public interface IParameterDataView : ISymbolInfoDataView
2429
ITypeDataView ParameterTypeData { get; }
2530
RuntimeTypeHandle ParameterTypeHandle { get; }
2631
int Position { get; }
32+
TypeList RequiredModifiers { get; }
33+
TypeList OptionalModifiers { get; }
2734
}

‎src/BionicCode.Utilities.Reflection/MemberData.cs‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ protected MemberData(string memberName, SymbolKind symbolKind, SymbolReflectionI
1717
{
1818
}
1919

20-
protected abstract MemberInfo GetMemberInfo();
20+
protected abstract MemberInfo MemberInfo { get; }
2121

2222
private BindingFlags ComputeVisibilityBindingFlagsMask()
2323
{
@@ -56,6 +56,9 @@ internal TypeData ImplementingTypeData
5656
internal override string Namespace
5757
=> _namespace ??= DeclaringTypeData!.Namespace;
5858

59+
internal override bool IsDefined(Type attributeType, bool inherit = false) => MemberInfo.IsDefined(attributeType, inherit);
60+
internal override bool IsDefined<TAttribute>(bool inherit = false) => MemberInfo.IsDefined(typeof(TAttribute), inherit);
61+
5962
/// <summary>
6063
/// The declaring type handle of the member. This is the runtime type handle of the type that declares the member. For example, for a method declared in a class, this would be the runtime type handle of that class.
6164
/// <para/>For an explicit interface implementation, this would be the runtime type handle of the interface that declares the member.
@@ -99,5 +102,5 @@ internal override string Namespace
99102
internal BindingFlags BindingFlagsVisibilityMask => _bindingFlagsVisibilityMask ??= ComputeVisibilityBindingFlagsMask();
100103

101104
/// <inheritdoc/>
102-
internal override IList<CustomAttributeData> AttributeData => _attributeData ??= [.. GetMemberInfo().GetCustomAttributesData()];
105+
internal override IList<CustomAttributeData> AttributeData => _attributeData ??= [.. MemberInfo.GetCustomAttributesData()];
103106
}

‎src/BionicCode.Utilities.Reflection/MethodData.cs‎

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ internal sealed partial class MethodData : ParameterizedMemberData, IMethodDataI
7676
private RuntimeTypeHandle? _implementingTypeHandle;
7777
private bool? _isExplicitInterfaceImplementation;
7878
private IMethodDataView? _methodDataView;
79+
private bool? _isReadonly;
7980

8081
internal MethodData(SymbolReflectionInfoCacheKeyInternal symbolReflectionInfoCacheKey)
8182
: base(symbolReflectionInfoCacheKey)
@@ -95,21 +96,19 @@ internal MethodData(SymbolReflectionInfoCacheKeyInternal symbolReflectionInfoCac
9596

9697
internal MethodInfo MethodInfo { get; }
9798

98-
protected override MemberInfo GetMemberInfo() => MethodInfo;
99-
100-
internal override MethodBase GetMethodBase() => MethodInfo;
99+
internal override MethodBase MethodBase => MethodInfo;
101100

102101
internal MethodData MakeGenericMethodData(TypeList typeDataArguments)
103102
{
104103
Type[] typeArguments = typeDataArguments.Select(t => t.Type).ToArray();
105104
MethodInfo genericMethodInfo = MethodInfo.MakeGenericMethod(typeArguments);
106-
return SymbolReflectionInfoCache.GetOrCreateSymbolReflectionInfoCacheEntry(genericMethodInfo);
105+
return GetOrCreateCacheEntry(genericMethodInfo);
107106
}
108107

109108
internal MethodData MakeGenericMethodData(params Type[] typeArguments)
110109
{
111110
MethodInfo genericMethodInfo = MethodInfo.MakeGenericMethod(typeArguments);
112-
return SymbolReflectionInfoCache.GetOrCreateSymbolReflectionInfoCacheEntry(genericMethodInfo);
111+
return GetOrCreateCacheEntry(genericMethodInfo);
113112
}
114113

115114
internal MethodInfo MakeGenericMethodInfo(TypeList typeArguments) => MethodInfo.MakeGenericMethod(typeArguments.Select(t => t.Type).ToArray());
@@ -904,7 +903,7 @@ internal BasicMethodFingerprint BasicMethodFingerprint
904903
if (IsGenericMethod && !IsGenericMethodDefinition)
905904
{
906905
MethodInfo genericMethodDefinition = MethodInfo.GetGenericMethodDefinition();
907-
var genericMethodDefinitionData = genericMethodDefinition.ToMethodData();
906+
MethodData genericMethodDefinitionData = GetOrCreateCacheEntry(genericMethodDefinition);
908907
_basicMethodFingerprint = genericMethodDefinitionData.BasicMethodFingerprint;
909908
}
910909
else
@@ -933,7 +932,7 @@ internal MethodData GenericMethodDefinitionData
933932
else
934933
{
935934
MethodInfo genericMethodDefinitionMethodInfo = MethodInfo.GetGenericMethodDefinition();
936-
_genericMethodDefinitionData = SymbolReflectionInfoCache.GetOrCreateSymbolReflectionInfoCacheEntry(genericMethodDefinitionMethodInfo);
935+
_genericMethodDefinitionData = GetOrCreateCacheEntry(genericMethodDefinitionMethodInfo);
937936
}
938937

939938
return _genericMethodDefinitionData;
@@ -1165,9 +1164,12 @@ internal bool IsAwaitableGenericTask
11651164
}
11661165
}
11671166

1167+
internal bool IsReadonly => _isReadonly
1168+
??= DeclaringTypeData.IsStruct && HasCompilerAttribute<IsReadOnlyAttribute>(ReflectionConstants.IsReadOnlyAttributeFullName);
1169+
11681170
internal bool IsOverride => _isOverride ??= MethodData.IsMethodOverride(this);
11691171

1170-
internal bool IsReturnValueReadOnly => _isReturnValueReadOnly ??= MethodInfo.ReturnParameter.GetCustomAttribute<IsReadOnlyAttribute>() != null;
1172+
internal bool IsReturnValueReadOnly => _isReturnValueReadOnly ??= ReturnParameterData.IsRefReadOnly;
11711173

11721174
internal bool IsReturnValueByRef => _isReturnValueByRef ??= ReturnTypeData.IsByRef;
11731175

@@ -1201,9 +1203,9 @@ internal bool IsAwaitableGenericTask
12011203

12021204
internal override string AssemblyName => _assemblyName ??= DeclaringTypeData.AssemblyName;
12031205

1204-
internal TypeData ReturnTypeData => _returnTypeData ??= SymbolReflectionInfoCache.GetOrCreateEntryInternal(MethodInfo.ReturnType);
1206+
internal TypeData ReturnTypeData => _returnTypeData ??= GetOrCreateCacheEntry(MethodInfo.ReturnType);
12051207

1206-
internal ParameterData ReturnParameterData => _returnParameterData ??= SymbolReflectionInfoCache.GetOrCreateEntryInternal(MethodInfo.ReturnParameter);
1208+
internal ParameterData ReturnParameterData => _returnParameterData ??= GetOrCreateCacheEntry(MethodInfo.ReturnParameter);
12071209

12081210
internal bool IsGenericMethod => _isGenericMethod ??= MethodInfo.IsGenericMethod;
12091211

@@ -1237,15 +1239,15 @@ private static bool IsMethodExtensionMethod(MethodData methodData)
12371239
return false;
12381240
}
12391241

1240-
MethodInfo methodInfo = methodData.MethodInfo;
1241-
Attribute methodExtensionAttribute = methodInfo.GetCustomAttribute(ReflectionHelperExtensions.ExtensionAttributeType, false);
1242-
if (methodExtensionAttribute == null)
1242+
bool hasExtensionMethodMarker = methodData.HasCompilerAttribute(ReflectionHelperExtensions.ExtensionAttributeType, ReflectionHelperExtensions.ExtensionAttributeType.FullName);
1243+
if (!hasExtensionMethodMarker)
12431244
{
12441245
return false;
12451246
}
12461247

1247-
// Must have at least the 'this' parameter
12481248
ParameterList parameterInfoData = methodData.Parameters;
1249+
1250+
// Must have at least the 'this' parameter
12491251
if (parameterInfoData.Count < 1)
12501252
{
12511253
return false;
@@ -1434,7 +1436,7 @@ private static bool TryGetExplicitInterfaceImplementationInfo(MethodData methodD
14341436
interfaceDeclaration = null;
14351437
declaringInterfaceTypeData = null;
14361438

1437-
TypeData implementingType = methodData.MethodInfo.DeclaringType?.ToTypeData() ?? throw new InvalidOperationException("Declaring type handle is not available.");
1439+
TypeData implementingType = GetOrCreateCacheEntry(methodData.MethodInfo.DeclaringType ?? throw new InvalidOperationException("Declaring type is not available."));
14381440

14391441
if (implementingType.IsInterface
14401442
|| methodData.IsPublic)
@@ -1458,7 +1460,7 @@ private static bool TryGetExplicitInterfaceImplementationInfo(MethodData methodD
14581460
if (interfaceMapping.ImplementedMethodCacheKeyTable.TryGetValue(methodData.CacheKey, out int mappingIndex)
14591461
&& interfaceMapping.ReverseInterfaceMethodsCacheKeyTable.TryGetValue(mappingIndex, out SymbolReflectionInfoCacheKeyInternal interfaceMethodCacheKey))
14601462
{
1461-
MethodData interfaceMethod = SymbolReflectionInfoCache.GetOrCreateMethodDataCacheEntry(interfaceMethodCacheKey);
1463+
MethodData interfaceMethod = GetOrCreateCacheEntry<MethodData>(interfaceMethodCacheKey);
14621464
declaringInterfaceTypeData = interfaceMethod.DeclaringTypeData;
14631465
interfaceDeclaration = interfaceMethod;
14641466
implementingTypeData = implementingType;
@@ -1473,8 +1475,8 @@ private static bool TryGetExplicitInterfaceImplementationInfo(MethodData methodD
14731475
private static InterfaceMappingEntry InterfaceMappingEntryFactory(TypeData declaringTypeData, TypeData interfaceTypeData)
14741476
{
14751477
InterfaceMapping mapping = declaringTypeData.Type.GetInterfaceMap(interfaceTypeData.Type);
1476-
IEnumerable<SymbolReflectionInfoCacheKeyInternal> implementedMethodCacheKeys = mapping.TargetMethods.Select(m => m.ToMethodData().CacheKey);
1477-
IEnumerable<SymbolReflectionInfoCacheKeyInternal> interfaceMethodCacheKeys = mapping.InterfaceMethods.Select(m => m.ToMethodData().CacheKey);
1478+
IEnumerable<SymbolReflectionInfoCacheKeyInternal> implementedMethodCacheKeys = mapping.TargetMethods.Select(m => GetOrCreateCacheEntry(m).CacheKey);
1479+
IEnumerable<SymbolReflectionInfoCacheKeyInternal> interfaceMethodCacheKeys = mapping.InterfaceMethods.Select(m => GetOrCreateCacheEntry(m).CacheKey);
14781480

14791481
return new InterfaceMappingEntry(implementedMethodCacheKeys, interfaceMethodCacheKeys);
14801482
}

0 commit comments

Comments
 (0)