Skip to content

Commit 599cb4a

Browse files
committed
Refactor symbol cache to unified, view-based API
Major overhaul of the symbol reflection info cache: - Introduced view-based, memory-safe, ALC-aware API for all symbol kinds (type, method, property, event, field, parameter, constructor) - Added TryGet*CacheEntry and GetOrCreateCacheEntry methods returning view interfaces (e.g., ITypeDataView) instead of internal cache entries - Unified symbol lookup with s_wellKnownSymbolCacheKeyTable keyed by object - Updated all data classes to expose their view via a .View property - Improved parameter handling, including normalization of ambiguous indexer parameters - Enhanced detection of ref readonly and in parameters - Improved and clarified XML documentation throughout - Moved AnonymousSymbolDescriptorContainer to its own file and restored implementation - General code cleanup, null checks, and error handling improvements
1 parent 84fa34e commit 599cb4a

12 files changed

Lines changed: 1208 additions & 771 deletions
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
namespace BionicCode.Utilities.Net.Reflection;
2+
3+
using System;
4+
using System.Diagnostics.CodeAnalysis;
5+
using System.Reflection;
6+
using System.Runtime.CompilerServices;
7+
8+
internal readonly struct AnonymousSymbolDescriptorContainer : IEquatable<AnonymousSymbolDescriptorContainer>
9+
{
10+
/// <summary>
11+
/// Represents an unknown or unspecified parameter count.
12+
/// </summary>
13+
/// <remarks>Use this constant to indicate that the number of parameters is not known or cannot be
14+
/// determined. This value is typically used in APIs where the parameter count is optional or
15+
/// variable.</remarks>
16+
public const int UnknownParameterCountOrPosition = SymbolReflectionInfoCacheKeyInternal.UnknownParameterCountOrPosition;
17+
18+
/// <summary>
19+
/// Gets the name of the symbol represented by this instance.
20+
/// </summary>
21+
/// <value>The name of the symbol, such as the method name, property name, event name, field name, or type name.</value>
22+
public readonly string SymbolName { get; }
23+
24+
/// <summary>
25+
/// Gets the kind of symbol represented by this instance.
26+
/// </summary>
27+
/// <value>The kind of symbol, such as type, method, property, event, field, constructor, or parameter.</value>
28+
public readonly SymbolKind SymbolKind { get; }
29+
30+
private readonly AnonymousParameterDescriptor _parameterDescriptor;
31+
public AnonymousParameterDescriptor ParameterDescriptor => SymbolKind is SymbolKind.Parameter
32+
? _parameterDescriptor
33+
: ThrowInvalidPropertyContextException<AnonymousParameterDescriptor>([SymbolKind.Parameter]);
34+
35+
private readonly AnonymousPropertyDescriptor _propertyDescriptor;
36+
public AnonymousPropertyDescriptor PropertyDescriptor => SymbolKind is SymbolKind.MemberProperty
37+
? _propertyDescriptor
38+
: ThrowInvalidPropertyContextException<AnonymousPropertyDescriptor>([SymbolKind.MemberProperty]);
39+
40+
private readonly AnonymousMethodDescriptor _methodDescriptor;
41+
public AnonymousMethodDescriptor MethodDescriptor => SymbolKind is SymbolKind.MemberMethod
42+
? _methodDescriptor
43+
: ThrowInvalidPropertyContextException<AnonymousMethodDescriptor>([SymbolKind.MemberMethod]);
44+
45+
private readonly AnonymousConstructorDescriptor _constructorDescriptor;
46+
public AnonymousConstructorDescriptor ConstructorDescriptor => SymbolKind is SymbolKind.MemberConstructor
47+
? _constructorDescriptor
48+
: ThrowInvalidPropertyContextException<AnonymousConstructorDescriptor>([SymbolKind.MemberConstructor]);
49+
50+
private readonly AnonymousFieldDescriptor _fieldDescriptor;
51+
public AnonymousFieldDescriptor FieldDescriptor => SymbolKind is SymbolKind.MemberField
52+
? _fieldDescriptor
53+
: ThrowInvalidPropertyContextException<AnonymousFieldDescriptor>([SymbolKind.MemberField]);
54+
55+
private readonly AnonymousEventDescriptor _eventDescriptor;
56+
public AnonymousEventDescriptor EventDescriptor => SymbolKind is SymbolKind.MemberEvent
57+
? _eventDescriptor
58+
: ThrowInvalidPropertyContextException<AnonymousEventDescriptor>([SymbolKind.MemberEvent]);
59+
60+
private readonly bool _isExplicitInterfaceImplementation;
61+
public bool IsExplicitInterfaceImplementation => SymbolKind is SymbolKind.MemberProperty or SymbolKind.MemberEvent or SymbolKind.MemberMethod
62+
? _isExplicitInterfaceImplementation
63+
: ThrowInvalidPropertyContextException<bool>([SymbolKind.MemberProperty, SymbolKind.MemberEvent, SymbolKind.MemberMethod]);
64+
65+
private readonly int _hashCode;
66+
67+
private AnonymousSymbolDescriptorContainer(
68+
string name,
69+
SymbolKind symbolKind,
70+
AnonymousParameterDescriptor parameterDescriptor,
71+
AnonymousMethodDescriptor methodDescriptor,
72+
AnonymousConstructorDescriptor constructorDescriptor,
73+
AnonymousPropertyDescriptor propertyDescriptor,
74+
AnonymousFieldDescriptor fieldDescriptor,
75+
AnonymousEventDescriptor eventDescriptor)
76+
{
77+
ArgumentExceptionAdvanced.ThrowIfEnumIsNotDefined<SymbolKind>(symbolKind, nameof(symbolKind));
78+
ArgumentExceptionAdvanced.ThrowIfEnumEqualsAny(symbolKind, [SymbolKind.Undefined], nameof(symbolKind));
79+
ArgumentNullExceptionAdvanced.ThrowIfNullOrWhiteSpace(name, nameof(name));
80+
81+
SymbolKind = symbolKind;
82+
_parameterDescriptor = parameterDescriptor;
83+
_constructorDescriptor = constructorDescriptor;
84+
_methodDescriptor = methodDescriptor;
85+
_propertyDescriptor = propertyDescriptor;
86+
_fieldDescriptor = fieldDescriptor;
87+
_eventDescriptor = eventDescriptor;
88+
SymbolName = name;
89+
90+
_hashCode = ComputeHashCode();
91+
}
92+
93+
/// <summary>
94+
/// Creates a cache key for an event symbol.
95+
/// </summary>
96+
/// <param name="eventDescriptor">The event descriptor.</param>
97+
/// <returns>The unique cache key for the event symbol.</returns>
98+
/// <exception cref="ArgumentNullException">Thrown when <paramref name="eventDescriptor"/> is <see langword="null"/>.</exception>
99+
public static AnonymousSymbolDescriptorContainer CreateForEvent(AnonymousEventDescriptor eventDescriptor)
100+
{
101+
ArgumentNullExceptionAdvanced.ThrowIfDefault(eventDescriptor);
102+
103+
return new AnonymousSymbolDescriptorContainer(
104+
eventDescriptor.EventName,
105+
SymbolKind.MemberEvent,
106+
default,
107+
default,
108+
default,
109+
default,
110+
default,
111+
eventDescriptor);
112+
}
113+
114+
public static AnonymousSymbolDescriptorContainer CreateForProperty(AnonymousPropertyDescriptor propertyDescriptor)
115+
{
116+
ArgumentNullExceptionAdvanced.ThrowIfDefault(propertyDescriptor);
117+
118+
// Anonymous keys will never be used to cache items.
119+
// Instead they will always be converted to well-known keys by attempting to identify the runtime metadata object
120+
// based on the provided information. Therefore we can use an empty AssemblyId here.
121+
return new AnonymousSymbolDescriptorContainer(
122+
propertyDescriptor.PropertyName,
123+
SymbolKind.MemberProperty,
124+
default,
125+
default,
126+
default,
127+
propertyDescriptor,
128+
default,
129+
default);
130+
}
131+
132+
/// <summary>
133+
/// Creates a cache key for an anonymous method symbol.
134+
/// </summary>
135+
/// <param name="methodDescriptor">The <see cref="Net.AnonymousMethodDescriptor"/> that describes an anonymous method (which is where the <see cref="MethodInfo"/> is not available and instead only the signature information is available).</param>
136+
/// <remarks>This method creates a unique cache key for well-known or anonymous method symbols, including regular methods, property accessors, and event accessors.
137+
/// <para/>For maximum performance and zero ambiguity, always prefer to create method cache keys using well-known <see cref="MethodInfo"/> instances via the <see cref="Net.WellKnownMethodDescriptor"/>.</remarks>
138+
/// <returns>The unique cache key for the well-known method symbol.</returns>
139+
public static AnonymousSymbolDescriptorContainer CreateForMethod(AnonymousMethodDescriptor methodDescriptor)
140+
{
141+
ArgumentNullExceptionAdvanced.ThrowIfDefault(methodDescriptor);
142+
143+
return new AnonymousSymbolDescriptorContainer(
144+
methodDescriptor.MethodName,
145+
SymbolKind.MemberMethod,
146+
default,
147+
methodDescriptor,
148+
default,
149+
default,
150+
default,
151+
default);
152+
}
153+
154+
/// <summary>
155+
/// Creates a new cache key for an anonymous field using the specified <see cref="AnonymousFieldDescriptor"/>.
156+
/// </summary>
157+
/// <param name="fieldDescriptor">The <see cref="Net.AnonymousFieldDescriptor"/> that describes an anonymous field (which is where the <see cref="FieldInfo"/> is not available and instead only the signature information is available).</param>
158+
/// <remarks>This method is used to create a unique cache key for field symbols of which the caller does not have a direct representation <see cref="FieldInfo"/> and instead only signature information is available.</remarks>
159+
/// <returns>A new instance of <see cref="SymbolReflectionInfoCacheKeyInternal"/> representing the specified anonymous field.</returns>
160+
/// <exception cref="ArgumentNullException">Thrown when <paramref name="fieldDescriptor"/> is <see langword="default"/>.</exception>
161+
public static AnonymousSymbolDescriptorContainer CreateForField(AnonymousFieldDescriptor fieldDescriptor)
162+
{
163+
ArgumentNullExceptionAdvanced.ThrowIfDefault(fieldDescriptor);
164+
165+
return new AnonymousSymbolDescriptorContainer(
166+
fieldDescriptor.FieldName,
167+
SymbolKind.MemberField,
168+
default,
169+
default,
170+
default,
171+
default,
172+
fieldDescriptor,
173+
default);
174+
}
175+
176+
/// <summary>
177+
/// Creates a new cache key for an anonymous constructor using the specified <see cref="AnonymousMethodDescriptor"/>.
178+
/// </summary>
179+
/// <remarks>This method is used to create a unique cache key for constructor symbols of which the caller does not have a direct representation <see cref="ConstructorInfo"/> and instead only signature information is available.
180+
///<para/>For maximum performance and zero ambiguity, always prefer to create constructor cache keys using well-known <see cref="ConstructorInfo"/> instances via the <see cref="Net.WellKnownMethodDescriptor"/>.</remarks>
181+
/// <param name="constructorDescriptor"></param>
182+
/// <returns>A new instance of <see cref="SymbolReflectionInfoCacheKeyInternal"/> representing the specified anonymous constructor.</returns>
183+
/// <exception cref="ArgumentNullException">Thrown when <paramref name="constructorDescriptor"/> is <see langword="default"/>.</exception>
184+
public static AnonymousSymbolDescriptorContainer CreateForConstructor(AnonymousConstructorDescriptor constructorDescriptor)
185+
{
186+
ArgumentNullExceptionAdvanced.ThrowIfDefault(constructorDescriptor);
187+
188+
return new AnonymousSymbolDescriptorContainer(
189+
string.Empty,
190+
SymbolKind.MemberConstructor,
191+
default,
192+
default,
193+
constructorDescriptor,
194+
default,
195+
default,
196+
default);
197+
}
198+
199+
public static AnonymousSymbolDescriptorContainer CreateForParameter(AnonymousParameterDescriptor parameterDescriptor)
200+
{
201+
ArgumentNullExceptionAdvanced.ThrowIfDefault(parameterDescriptor);
202+
203+
return new AnonymousSymbolDescriptorContainer(
204+
parameterDescriptor.ParameterName,
205+
SymbolKind.Parameter,
206+
parameterDescriptor,
207+
default,
208+
default,
209+
default,
210+
default,
211+
default);
212+
}
213+
214+
public override int GetHashCode()
215+
=> _hashCode;
216+
217+
private int ComputeHashCode()
218+
{
219+
unchecked
220+
{
221+
if (_hashCode != 0)
222+
{
223+
return _hashCode;
224+
}
225+
226+
var hashCode = new HashCode();
227+
hashCode.Add(SymbolName);
228+
hashCode.Add(SymbolKind);
229+
hashCode.Add(_parameterDescriptor);
230+
hashCode.Add(_methodDescriptor);
231+
hashCode.Add(_constructorDescriptor);
232+
hashCode.Add(_propertyDescriptor);
233+
hashCode.Add(_fieldDescriptor);
234+
hashCode.Add(_eventDescriptor);
235+
hashCode.Add(_isExplicitInterfaceImplementation);
236+
237+
return hashCode.ToHashCode();
238+
}
239+
}
240+
241+
public override bool Equals(object obj) => obj is AnonymousSymbolDescriptorContainer other && Equals(other);
242+
243+
public bool Equals(AnonymousSymbolDescriptorContainer other) => SymbolName == other.SymbolName
244+
&& SymbolKind == other.SymbolKind
245+
&& _parameterDescriptor == other._parameterDescriptor
246+
&& _methodDescriptor == other._methodDescriptor
247+
&& _constructorDescriptor == other._constructorDescriptor
248+
&& _propertyDescriptor == other._propertyDescriptor
249+
&& _fieldDescriptor == other._fieldDescriptor
250+
&& _eventDescriptor == other._eventDescriptor
251+
&& _isExplicitInterfaceImplementation == other._isExplicitInterfaceImplementation;
252+
253+
public static bool operator ==(AnonymousSymbolDescriptorContainer left, AnonymousSymbolDescriptorContainer right) => left.Equals(right);
254+
public static bool operator !=(AnonymousSymbolDescriptorContainer left, AnonymousSymbolDescriptorContainer right) => !(left == right);
255+
256+
[DoesNotReturn]
257+
private TResult ThrowInvalidPropertyContextException<TResult>(ReadOnlySpan<SymbolKind> allowedSymbolKinds, [CallerMemberName] string? propertyName = null)
258+
{
259+
ArgumentExceptionAdvanced.ThrowIfTrue(allowedSymbolKinds.IsEmpty, nameof(allowedSymbolKinds), "At least one allowed symbol kind must be provided.");
260+
261+
string allowedKinds = allowedSymbolKinds.JoinToString(kind => $"{typeof(SymbolKind).FullName}.{kind}", ", ");
262+
return allowedSymbolKinds.Length > 1
263+
? throw new InvalidOperationException($"The property '{propertyName}' is only available for symbols, where the property '{nameof(SymbolKind)}' returns any of the following values: {allowedKinds}.")
264+
: throw new InvalidOperationException($"The property '{propertyName}' is only available for symbols, where the property '{nameof(SymbolKind)}' returns the value '{allowedKinds[0]}'.");
265+
}
266+
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ internal sealed class ConstructorData : ParameterizedMemberData
3030
private Func<object?[], object>? _invocator;
3131
private string? _assemblyName;
3232
private SymbolComponentInfo? _symbolComponentInfo;
33+
private IConstructorDataView? _constructorDataView;
3334

3435
internal ConstructorData(SymbolReflectionInfoCacheKeyInternal symbolInfoDataCacheKey)
3536
: base(symbolInfoDataCacheKey)
@@ -43,6 +44,9 @@ internal ConstructorData(SymbolReflectionInfoCacheKeyInternal symbolInfoDataCach
4344
IsExplicitInterfaceImplementation = false;
4445
}
4546

47+
internal IConstructorDataView View => _constructorDataView
48+
??= new ConstructorDataView(SymbolReflectionInfoCacheKey.CreateForConstructor(this));
49+
4650
internal ConstructorInfo ConstructorInfo { get; }
4751

4852
internal override MethodBase GetMethodBase() => ConstructorInfo;

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ internal sealed class EventData : MemberData
3838
private RuntimeTypeHandle? _declaringTypeHandle;
3939
private RuntimeTypeHandle? _implementingTypeHandle;
4040
private bool? _isExplicitInterfaceImplementation;
41+
private IEventDataView? _eventDataView;
4142

4243
internal EventData(SymbolReflectionInfoCacheKeyInternal symbolInfoDataCacheKey)
4344
: base(symbolInfoDataCacheKey.EventDescriptor.EventName, SymbolKind.MemberEvent, symbolInfoDataCacheKey)
@@ -48,6 +49,9 @@ internal EventData(SymbolReflectionInfoCacheKeyInternal symbolInfoDataCacheKey)
4849
EventInfo = symbolInfoDataCacheKey.EventDescriptor.EventInfo;
4950
}
5051

52+
internal IEventDataView View => _eventDataView
53+
??= new EventDataView(SymbolReflectionInfoCacheKey.CreateForEvent(this));
54+
5155
protected override MemberInfo GetMemberInfo()
5256
=> EventInfo;
5357

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
using System;
44
using System.Reflection;
55

6-
public class EventDataView : IEventDataView
6+
public class EventDataView : SymbolReflectionInfoCache.SymbolDataViewBase, IEventDataView
77
{
8-
internal EventDataView(SymbolReflectionInfoCacheKey cacheKey) => CacheKey = cacheKey;
8+
internal EventDataView(SymbolReflectionInfoCacheKey cacheKey) : base(cacheKey)
9+
{ }
910

1011
public AccessModifier AccessModifier => SymbolReflectionInfoCache.GetOrCreateEventDataCacheEntry(CacheKey).AccessModifier;
1112
public IMethodDataView AddMethodDataView { get; }

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ internal sealed class FieldData : MemberData, IFieldDataInvoker
3636
private readonly WellKnownFieldDescriptor _descriptor;
3737
private string? _assemblyName;
3838
private SymbolComponentInfo? _symbolComponentInfo;
39+
private IFieldDataView? _fieldDataView;
3940

4041
internal FieldData(SymbolReflectionInfoCacheKeyInternal symbolInfoDataCacheKey)
4142
: base(symbolInfoDataCacheKey.FieldDescriptor.FieldName, SymbolKind.MemberField, symbolInfoDataCacheKey)
@@ -50,6 +51,8 @@ internal FieldData(SymbolReflectionInfoCacheKeyInternal symbolInfoDataCacheKey)
5051
IsExplicitInterfaceImplementation = false; // Fields cannot be explicit interface implementations
5152
ImplementingTypeHandle = DeclaringTypeHandle;
5253
}
54+
internal IFieldDataView View => _fieldDataView
55+
??= new FieldDataView(SymbolReflectionInfoCacheKey.CreateForField(this));
5356

5457
internal FieldInfo FieldInfo { get; }
5558

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public interface IMethodDataView : IParameterizedMemberDataView, IMemberDataView
3939
bool IsReturnValueReadOnly { get; }
4040
bool IsVoidMethod { get; }
4141
ITypeDataView ReturnTypeData { get; }
42+
IParameterDataView ReturnParameterData { get; }
4243
bool EqualsBySignature(IMethodDataView other);
4344
object? Invoke(object? target, params object?[]? args);
4445
object? Invoke(object? target, ReadOnlySpan<object?> args);

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ internal sealed partial class MethodData : ParameterizedMemberData, IMethodDataI
3939
private string? _shortCompactSignature;
4040
private string? _fullyQualifiedSignature;
4141
private TypeData? _returnTypeData;
42+
private ParameterData? _returnParameterData;
4243
private bool? _isGenericMethod;
4344
private bool? _isGenericTypeMethod;
4445
private MethodData? _genericMethodDefinitionData;
@@ -74,6 +75,7 @@ internal sealed partial class MethodData : ParameterizedMemberData, IMethodDataI
7475
private RuntimeTypeHandle? _declaringTypeHandle;
7576
private RuntimeTypeHandle? _implementingTypeHandle;
7677
private bool? _isExplicitInterfaceImplementation;
78+
private IMethodDataView? _methodDataView;
7779

7880
internal MethodData(SymbolReflectionInfoCacheKeyInternal symbolReflectionInfoCacheKey)
7981
: base(symbolReflectionInfoCacheKey)
@@ -88,6 +90,9 @@ internal MethodData(SymbolReflectionInfoCacheKeyInternal symbolReflectionInfoCac
8890
_methodSignatureEqualityComparer = new MethodSignatureEqualityComparer();
8991
}
9092

93+
internal IMethodDataView View => _methodDataView
94+
??= new MethodDataView(SymbolReflectionInfoCacheKey.CreateForMethod(this));
95+
9196
internal MethodInfo MethodInfo { get; }
9297

9398
protected override MemberInfo GetMemberInfo() => MethodInfo;
@@ -1198,6 +1203,8 @@ internal bool IsAwaitableGenericTask
11981203

11991204
internal TypeData ReturnTypeData => _returnTypeData ??= SymbolReflectionInfoCache.GetOrCreateEntryInternal(MethodInfo.ReturnType);
12001205

1206+
internal ParameterData ReturnParameterData => _returnParameterData ??= SymbolReflectionInfoCache.GetOrCreateEntryInternal(MethodInfo.ReturnParameter);
1207+
12011208
internal bool IsGenericMethod => _isGenericMethod ??= MethodInfo.IsGenericMethod;
12021209

12031210
internal bool IsGenericMethodDefinition => _isGenericTypeMethod ??= MethodInfo.IsGenericMethodDefinition;

0 commit comments

Comments
 (0)