Skip to content

Commit 99c1d71

Browse files
committed
Replace MethodParameterInfo with ParameterDescriptor types
Refactored parameter representation by introducing ParameterDescriptor and ParameterDescriptorList, replacing MethodParameterInfo and MethodParameterInfoList. Updated all usages, builders, and equality comparers to use the new types. ParameterDescriptor provides richer metadata and improved ambiguity handling; ParameterDescriptorList ensures immutability, uniqueness, and consistency. Updated documentation and comments. Improves accuracy, performance, and maintainability of reflection-based parameter lookups.
1 parent c793a36 commit 99c1d71

16 files changed

+383
-351
lines changed

src/BionicCode.Utilities.Reflection/AnonymousConstructorDescriptor.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
/// <para/>For best accuracy and performance always use the <see cref="WellKnownConstructorDescriptor"/>, which requires the caller to have direct access to the <see cref="MethodInfo"/> or <see cref="ConstructorInfo"/> representation of the method or constructor.
2020
/// </remarks>
2121
/// <param name="declaringTypeHandle">The runtime type handle representing the declaring type of the anonymous constructor.</param>
22-
/// <param name="constructorParameters">The list of parameters for the anonymous method. Can be <see cref="MethodParameterInfoList.Empty"/> or <see langword="null"/> to indicate no parameters.</param>
22+
/// <param name="constructorParameters">The list of parameters for the anonymous method. Can be <see cref="ParameterDescriptorList.Empty"/> or <see langword="null"/> to indicate no parameters.</param>
2323
/// <returns>A new instance of <see cref="AnonymousConstructorDescriptor"/> representing the specified anonymous method.</returns>
2424
/// <exception cref="ArgumentNullException">Thrown when
2525
/// <list type="bullet">
@@ -29,7 +29,7 @@
2929
/// </exception>
3030
public AnonymousConstructorDescriptor(
3131
RuntimeTypeHandle declaringTypeHandle,
32-
MethodParameterInfoList? constructorParameters)
32+
ParameterDescriptorList? constructorParameters)
3333
{
3434
ArgumentNullExceptionAdvanced.ThrowIfDefault(declaringTypeHandle);
3535
var declaringType = Type.GetTypeFromHandle(declaringTypeHandle);
@@ -44,7 +44,7 @@ public AnonymousConstructorDescriptor(
4444
}
4545

4646
public RuntimeTypeHandle DeclaringTypeHandle { get; }
47-
public MethodParameterInfoList ConstructorParameterList { get; }
47+
public ParameterDescriptorList ConstructorParameterList { get; }
4848
public bool IsAnonymous { get; }
4949

5050
public bool Equals(AnonymousConstructorDescriptor other)

src/BionicCode.Utilities.Reflection/AnonymousMethodDescriptor.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
/// </exception>
5151
public AnonymousMethodDescriptor(
5252
RuntimeTypeHandle declaringTypeHandle,
53-
MethodParameterInfoList? methodParameters,
53+
ParameterDescriptorList? methodParameters,
5454
string? methodName,
5555
ITypeListView? genericMethodParameters,
5656
bool isExplicitInterfaceImplementation,
@@ -101,7 +101,7 @@ public AnonymousMethodDescriptor(
101101
public RuntimeTypeHandle ImplementingTypeHandle { get; }
102102
public RuntimeTypeHandle DeclaringInterfaceTypeHandle { get; }
103103
public string MethodName { get; }
104-
public MethodParameterInfoList MethodParameterList { get; }
104+
public ParameterDescriptorList MethodParameterList { get; }
105105
public TypeList GenericMethodParameters { get; }
106106
public bool IsExplicitInterfaceImplementation { get; }
107107
public bool IsAnonymous { get; }

src/BionicCode.Utilities.Reflection/AnonymousPropertyDescriptor.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@
3131
/// then the <paramref name="implementingTypeHandle"/> can be <see langword="null"/> since it will be ignored.</param>
3232
/// <param name="propertyName">The name of the anonymous property. Cannot be null, empty, or consist only of white-space characters.</param>
3333
/// <param name="declaredPropertyAccessors">Specifies the accessor that the property declares. Can't be <see cref="PropertyAccessors.None"/> or <see cref="PropertyAccessors.None"/>.</param>
34-
/// <param name="indexerGetterParameters">The list of parameters for the anonymous indexer getter. Can be <see cref="MethodParameterInfoList.Empty"/> or <see langword="null"/> to indicate no parameters in case of a normal property. For normal properties, this parameter is ignored.</param>
35-
/// <param name="indexerSetterParameters">The list of parameters for the anonymous indexer setter. Can be <see cref="MethodParameterInfoList.Empty"/> or <see langword="null"/> to indicate no parameters in case of a normal property. For normal properties, this parameter is ignored.</param>
34+
/// <param name="indexerGetterParameters">The list of parameters for the anonymous indexer getter. Can be <see cref="ParameterDescriptorList.Empty"/> or <see langword="null"/> to indicate no parameters in case of a normal property. For normal properties, this parameter is ignored.</param>
35+
/// <param name="indexerSetterParameters">The list of parameters for the anonymous indexer setter. Can be <see cref="ParameterDescriptorList.Empty"/> or <see langword="null"/> to indicate no parameters in case of a normal property. For normal properties, this parameter is ignored.</param>
3636
/// <param name="isExplicitInterfaceImplementation"><see langword="true"/> if the property is an explicit interface implementation; otherwise, <see langword="false"/>. If set to <see langword="true"/>, the <paramref name="declaringTypeHandle"/> must represent an interface type.
3737
/// </param>
3838
/// <returns>A new instance of <see cref="AnonymousPropertyDescriptor"/> representing the specified anonymous property.</returns>
@@ -57,8 +57,8 @@ public AnonymousPropertyDescriptor(
5757
RuntimeTypeHandle declaringTypeHandle,
5858
string propertyName,
5959
PropertyAccessors declaredPropertyAccessors,
60-
MethodParameterInfoList? indexerGetterParameters,
61-
MethodParameterInfoList? indexerSetterParameters,
60+
ParameterDescriptorList? indexerGetterParameters,
61+
ParameterDescriptorList? indexerSetterParameters,
6262
bool isExplicitInterfaceImplementation,
6363
RuntimeTypeHandle? implementingTypeHandle)
6464
{
@@ -121,8 +121,8 @@ public AnonymousPropertyDescriptor(
121121
public RuntimeTypeHandle DeclaringInterfaceTypeHandle { get; }
122122
public string PropertyName { get; }
123123
public PropertyAccessors DeclaredAccessors { get; }
124-
public MethodParameterInfoList IndexerGetterMethodParameterInfoList { get; }
125-
public MethodParameterInfoList IndexerSetterMethodParameterInfoList { get; }
124+
public ParameterDescriptorList IndexerGetterMethodParameterInfoList { get; }
125+
public ParameterDescriptorList IndexerSetterMethodParameterInfoList { get; }
126126
public bool IsExplicitInterfaceImplementation { get; init; }
127127
public bool IsIndexerProperty { get; }
128128
public bool IsAnonymous { get; }

src/BionicCode.Utilities.Reflection/AnonymousSymbolDescriptorContainer.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@
2727
/// <value>The kind of symbol, such as type, method, property, event, field, constructor, or parameter.</value>
2828
public readonly SymbolKind SymbolKind { get; }
2929

30-
private readonly AnonymousParameterDescriptor _parameterDescriptor;
31-
public AnonymousParameterDescriptor ParameterDescriptor => SymbolKind is SymbolKind.Parameter
30+
private readonly ParameterDescriptor _parameterDescriptor;
31+
public ParameterDescriptor ParameterDescriptor => SymbolKind is SymbolKind.Parameter
3232
? _parameterDescriptor
33-
: ThrowInvalidPropertyContextException<AnonymousParameterDescriptor>([SymbolKind.Parameter]);
33+
: ThrowInvalidPropertyContextException<ParameterDescriptor>([SymbolKind.Parameter]);
3434

3535
private readonly AnonymousPropertyDescriptor _propertyDescriptor;
3636
public AnonymousPropertyDescriptor PropertyDescriptor => SymbolKind is SymbolKind.MemberProperty
@@ -67,7 +67,7 @@
6767
private AnonymousSymbolDescriptorContainer(
6868
string name,
6969
SymbolKind symbolKind,
70-
AnonymousParameterDescriptor parameterDescriptor,
70+
ParameterDescriptor parameterDescriptor,
7171
AnonymousMethodDescriptor methodDescriptor,
7272
AnonymousConstructorDescriptor constructorDescriptor,
7373
AnonymousPropertyDescriptor propertyDescriptor,
@@ -196,7 +196,7 @@ public static AnonymousSymbolDescriptorContainer CreateForConstructor(AnonymousC
196196
default);
197197
}
198198

199-
public static AnonymousSymbolDescriptorContainer CreateForParameter(AnonymousParameterDescriptor parameterDescriptor)
199+
public static AnonymousSymbolDescriptorContainer CreateForParameter(ParameterDescriptor parameterDescriptor)
200200
{
201201
ArgumentNullExceptionAdvanced.ThrowIfDefault(parameterDescriptor);
202202

src/BionicCode.Utilities.Reflection/IMethodListView.cs

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

6-
internal interface IMethodListView : ICollection, IReadOnlyList<IMethodDataView>, IEquatable<IMethodListView>
6+
public interface IMethodListView : ICollection, IReadOnlyList<IMethodDataView>, IEquatable<IMethodListView>
77
{
88
new int Count { get; }
9-
IMethodDataView this[string? methodName, MethodParameterInfoList? methodParameters] { get; }
9+
IMethodDataView this[string? methodName, ParameterDescriptorList? methodParameters] { get; }
1010
ITypeDataView DeclaringType { get; }
1111
bool HasItems { get; }
1212
bool IsEmpty { get; }

src/BionicCode.Utilities.Reflection/ITypeDataView.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,19 +62,19 @@ public interface ITypeDataView : ISymbolInfoDataView
6262
IEnumerable<FieldData> EnumerateFields(BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy);
6363
IEnumerable<MethodData> EnumerateMethods(BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy);
6464
IEnumerable<PropertyData> EnumerateProperties(BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy);
65-
bool TryGetConstructorByParameterList(MethodParameterInfoList parameters, out ConstructorData? constructorData);
65+
bool TryGetConstructorByParameterList(ParameterDescriptorList parameters, out ConstructorData? constructorData);
6666
bool TryGetConstructorByParameterList(ParameterList parameters, out ConstructorData? constructorData);
6767
bool TryGetEventByName(string eventName, out EventData? eventData);
6868
bool TryGetExplicitInterfaceEvent(RuntimeTypeHandle declaringInterfaceTypeHandle, string eventName, out EventData? eventData);
69-
bool TryGetExplicitInterfaceIndexerPropertyByParameterList(MethodParameterInfoList indexerParameters, PropertyAccessors indexerPropertyAccessor, out PropertyData? propertyData);
69+
bool TryGetExplicitInterfaceIndexerPropertyByParameterList(ParameterDescriptorList indexerParameters, PropertyAccessors indexerPropertyAccessor, out PropertyData? propertyData);
7070
bool TryGetExplicitInterfaceIndexerPropertyByParameterList(ParameterList parameters, PropertyAccessors propertyAccessor, out PropertyData? propertyData);
71-
bool TryGetExplicitInterfaceMethod(string methodName, TypeList genericMethodParameters, MethodParameterInfoList parameters, out MethodData? methodData);
71+
bool TryGetExplicitInterfaceMethod(string methodName, TypeList genericMethodParameters, ParameterDescriptorList parameters, out MethodData? methodData);
7272
bool TryGetExplicitInterfaceMethod(string methodName, TypeList genericMethodParameters, ParameterList parameters, out MethodData? methodData);
7373
bool TryGetExplicitInterfacePropertyByName(string propertyName, out PropertyData? propertyData);
7474
bool TryGetFieldByName(string fieldName, out FieldData? fieldData);
75-
bool TryGetIndexerPropertyByParameterList(MethodParameterInfoList indexerParameters, PropertyAccessors indexerPropertyAccessor, out PropertyData? propertyData);
75+
bool TryGetIndexerPropertyByParameterList(ParameterDescriptorList indexerParameters, PropertyAccessors indexerPropertyAccessor, out PropertyData? propertyData);
7676
bool TryGetIndexerPropertyByParameterList(ParameterList parameters, PropertyAccessors propertyAccessor, out PropertyData? propertyData);
77-
bool TryGetMethod(string methodName, TypeList genericMethodParameters, MethodParameterInfoList parameters, out MethodData? methodData);
77+
bool TryGetMethod(string methodName, TypeList genericMethodParameters, ParameterDescriptorList parameters, out MethodData? methodData);
7878
bool TryGetMethod(string methodName, TypeList genericMethodParameters, ParameterList parameters, out MethodData? methodData);
7979
bool TryGetMethodByName(string methodName, out MethodList? methods);
8080
bool TryGetPropertyByName(string propertyName, out PropertyData? propertyData);

src/BionicCode.Utilities.Reflection/MethodList.cs

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ public MethodData this[int index]
126126
/// <exception cref="InvalidOperationException">Thrown if the method index has not been initialized.</exception>
127127
/// <exception cref="KeyNotFoundException">Thrown if no method with the specified name exists, or if no method with the specified name matches the
128128
/// provided parameter signature.</exception>
129-
public MethodData this[string? methodName, MethodParameterInfoList? methodParameters]
129+
public MethodData this[string? methodName, ParameterDescriptorList? methodParameters]
130130
{
131131
get
132132
{
@@ -170,19 +170,52 @@ public MethodData this[int index]
170170
foreach (MethodData method in methods)
171171
{
172172
ParameterList parameters = method.Parameters;
173-
if (parameters.Count != methodParameters.Count)
173+
174+
if (methodParameters.DeclaringMethodParameterCount != ParameterDescriptor.UnknownParameterCountOrPosition
175+
&& parameters.Count != methodParameters.DeclaringMethodParameterCount)
174176
{
175177
continue;
176178
}
177179

178-
for (int parameterIndex = 0; parameterIndex < parameters.Count; parameterIndex++)
180+
// 'methodParameters' could be an incomplete parameter descriptor list that only specifies a subset of the parameters
181+
// of the method signature (e.g. only the first two parameters of a method with 4 parameters).
182+
// In this case, we only compare the specified subset of parameters and ignore the rest for matching purposes.
183+
for (int parameterIndex = 0; parameterIndex < methodParameters.Count; parameterIndex++)
179184
{
180-
ParameterData parameter = parameters[parameterIndex];
181-
MethodParameterInfo parameterInfo = methodParameters[parameterIndex];
182-
AnonymousParameterDescriptor parameterDescriptor = parameterInfo.ParameterDescriptor;
185+
ParameterDescriptor parameterDescriptor = methodParameters[parameterIndex];
186+
187+
if (parameterDescriptor.HasParameterPosition
188+
&& parameterDescriptor.ParameterPosition > parameters.Count)
189+
{
190+
break;
191+
}
192+
193+
// If ALL parameter descriptors have an explicitly specified parameter position (in this case 'methodParameters.IsSortedByParameterPosition' is true),
194+
// we use it to retrieve the corresponding parameter from the method signature for comparison to improve speed.
195+
// Otherwise, we must iterate through all parameters of the method signature to find the corresponding parameter for comparison, which is slower.
196+
ParameterData parameter = methodParameters.IsSortedByParameterPosition
197+
? parameters[parameterDescriptor.ParameterPosition]
198+
: parameters[parameterIndex];
183199

184200
// Parameter is valid if it matches position in method signature, parameter type and modifier.
185201
// Since parameter collections are always ordered by parameter position in ascending order, we don't have to check the order explicitly (only count - see above).
202+
203+
// TODO:: Split algorithm into two separate algorithms:
204+
// - one for the case when ALL parameter descriptors have an explicitly specified parameter position
205+
// - and one for the case when parameter position information is missing for at least one parameter descriptor
206+
// to improve readability and maintainability.
207+
208+
// When the source is unsorted we still must anticipate the possibility of parameter position information being
209+
// randomly provided in the descriptor to find the corresponding parameter for comparison.
210+
// If the position information is provided but does not match the current parameter index, we skip this parameter
211+
// and continue searching for the corresponding parameter in the method signature.
212+
if (!methodParameters.IsSortedByParameterPosition
213+
&& parameterDescriptor.HasParameterPosition
214+
&& parameterDescriptor.ParameterPosition != parameterIndex)
215+
{
216+
continue;
217+
}
218+
186219
if (parameterDescriptor.HasParameterTypeHandle
187220
&& !parameter.ParameterTypeData.Handle.Equals(parameterDescriptor.ParameterTypeHandle))
188221
{
@@ -272,7 +305,7 @@ private int ComputeHashCode()
272305
}
273306

274307
[DebuggerDisplay($"Count = {{{nameof(Count)}}}")]
275-
internal sealed class MethodListView : IReadOnlyList<IMethodDataView>, ICollection, IEmptyCollectionProvider<IMethodListView>, IEquatable<IMethodListView>, IMethodListView
308+
public sealed class MethodListView : IReadOnlyList<IMethodDataView>, ICollection, IEmptyCollectionProvider<IMethodListView>, IEquatable<IMethodListView>, IMethodListView
276309
{
277310
public static IMethodListView Empty { get; } = new MethodListView();
278311
private readonly int _hashCode; // precomputed
@@ -389,7 +422,7 @@ public IMethodDataView this[int index]
389422
/// <exception cref="InvalidOperationException">Thrown if the method index has not been initialized.</exception>
390423
/// <exception cref="KeyNotFoundException">Thrown if no method with the specified name exists, or if no method with the specified name matches the
391424
/// provided parameter signature.</exception>
392-
public IMethodDataView this[string? methodName, MethodParameterInfoList? methodParameters]
425+
public IMethodDataView this[string? methodName, ParameterDescriptorList? methodParameters]
393426
{
394427
get
395428
{
@@ -441,8 +474,8 @@ public IMethodDataView this[int index]
441474
for (int parameterIndex = 0; parameterIndex < parameters.Count; parameterIndex++)
442475
{
443476
IParameterDataView parameter = parameters[parameterIndex];
444-
MethodParameterInfo parameterInfo = methodParameters[parameterIndex];
445-
AnonymousParameterDescriptor parameterDescriptor = parameterInfo.ParameterDescriptor;
477+
ParameterDescriptor parameterInfo = methodParameters[parameterIndex];
478+
ParameterDescriptor parameterDescriptor = parameterInfo.ParameterDescriptor;
446479

447480
// Parameter is valid if it matches position in method signature, parameter type and modifier.
448481
// Since parameter collections are always ordered by parameter position in ascending order, we don't have to check the order explicitly (only count - see above).

0 commit comments

Comments
 (0)