Skip to content

Commit ec21868

Browse files
committed
Refactor reflection utilities: enums, lookup, equality
Major improvements to reflection and collection utilities: - Introduced `EqualityComparisonResult` and `MemberLookupState` enums for standardized equality and lookup results. - Refactored method and parameter lookup APIs to return lookup states instead of throwing exceptions. - Simplified `ParameterDescriptor` and improved validation in `ParameterDescriptorList`. - Added `IsEmpty` and `HasItems` properties to list view interfaces. - Provided static `Equals` methods for comparing list views and backing lists. - Replaced indexer-based method lookup with `TryGetMethod` supporting generic parameters and ambiguity handling. - Enhanced `ParameterListEqualityComparer` with nuanced equality checks. - Improved documentation, validation, and code consistency throughout.
1 parent f86131f commit ec21868

20 files changed

+589
-538
lines changed

src/BionicCode.Utilities.Net.Common/Exception/ArgumentExceptionHelpers.cs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -704,15 +704,11 @@ public static void ThrowIfContainsDuplicate<TItem>(IEnumerable<TItem> items, IEq
704704
ArgumentNullException.ThrowIfNull(items);
705705
equalityComparer ??= EqualityComparer<TItem>.Default;
706706

707-
var set = new HashSet<TItem>(equalityComparer);
708-
foreach (TItem item in items)
707+
if (items.HasDuplicates(equalityComparer))
709708
{
710-
if (!set.Add(item))
711-
{
712-
throw new ArgumentException(
713-
message ?? "The sequence contains duplicate items.",
714-
paramName);
715-
}
709+
throw new ArgumentException(
710+
message ?? "The sequence contains duplicate items.",
711+
paramName);
716712
}
717713
}
718714
}

src/BionicCode.Utilities.Net.Common/Extensions/CollectionHelperExtensions.cs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,7 @@ static HelperExtensionsCommon()
3030
/// <param name="source"></param>
3131
/// <returns><see langword="true"/> if <paramref name="source"/> is empty. Otherwise <see langword="false"/></returns>
3232
/// <exception cref="ArgumentNullException"><paramref name="source"/> is <see langword="null"/>.</exception>
33-
public static bool IsEmpty<TItem>(this IEnumerable<TItem> source)
34-
=> !source.Any();
33+
public static bool IsEmpty<TItem>(this IEnumerable<TItem> source) => !source.Any();
3534

3635
/// <summary>
3736
/// Determines whether a sequence is empty.
@@ -1193,5 +1192,30 @@ public static TCollection OrEmpty<TCollection>(this TCollection? source, Func<TC
11931192
/// <param name="source">The <see cref="List{TItem}"/> to return if it is not <see langword="null"/>.</param>
11941193
/// <returns>The original <see cref="List{TItem}"/> if it is not <see langword="null"/>; otherwise, an empty <see cref="List{TItem}"/>.</returns>
11951194
public static List<TItem> OrEmpty<TItem>(this List<TItem>? source) => source ?? [];
1195+
1196+
/// <summary>
1197+
/// Determines whether the specified collection contains duplicate elements.
1198+
/// </summary>
1199+
/// <typeparam name="TItem">The type of the elements in the collection.</typeparam>
1200+
/// <param name="items">The collection to check for duplicates.</param>
1201+
/// <param name="equalityComparer">The equality comparer to use for comparing elements.</param>
1202+
/// <returns><see langword="true"/> if the collection contains duplicate elements; otherwise, <see langword="false"/>.</returns>
1203+
/// <exception cref="ArgumentNullException"><paramref name="items"/> is <see langword="null"/>.</exception>
1204+
public static bool HasDuplicates<TItem>(this IEnumerable<TItem> items, IEqualityComparer<TItem>? equalityComparer = null)
1205+
{
1206+
ArgumentNullException.ThrowIfNull(items);
1207+
equalityComparer ??= EqualityComparer<TItem>.Default;
1208+
1209+
var set = new HashSet<TItem>(equalityComparer);
1210+
foreach (TItem item in items)
1211+
{
1212+
if (!set.Add(item))
1213+
{
1214+
return true;
1215+
}
1216+
}
1217+
1218+
return false;
1219+
}
11961220
}
11971221

src/BionicCode.Utilities.Reflection/ConstructorList.cs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -293,26 +293,34 @@ public bool Equals(IConstructorListView? other)
293293
return true;
294294
}
295295

296-
internal bool Equals(ConstructorList? other)
296+
internal static bool Equals(IConstructorListView? constructorDataViews, ConstructorList? constructorList)
297297
{
298-
if (other is null)
298+
// Return FALSE if exactly one of the constructor lists is NULL, otherwise compare the constructor lists for equality.
299+
if (constructorDataViews is null ^ constructorList is null)
299300
{
300301
return false;
301302
}
302303

303-
if (Count != other.Count)
304+
// If the constructor list view is NULL, the constructor list must also be NULL at this point, so return TRUE.
305+
// If both constructor lists are NULL, consider them equal.
306+
if (constructorDataViews is null)
307+
{
308+
return true;
309+
}
310+
311+
if (constructorDataViews.Count != constructorList!.Count)
304312
{
305313
return false;
306314
}
307315

308-
if (!ReferenceEquals(DeclaringType, other.DeclaringTypeData.View))
316+
if (!ReferenceEquals(constructorDataViews.DeclaringType, constructorList.DeclaringTypeData.View))
309317
{
310318
return false;
311319
}
312320

313-
for (int index = 0; index < Count; index++)
321+
for (int index = 0; index < constructorDataViews.Count; index++)
314322
{
315-
if (!Constructors[index].Equals(other.Constructors[index].View))
323+
if (!ReferenceEquals(constructorDataViews[index], constructorList.Constructors[index].View))
316324
{
317325
return false;
318326
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace BionicCode.Utilities.Net.Reflection;
2+
3+
public enum EqualityComparisonResult
4+
{
5+
True,
6+
False,
7+
TrueButAmbiguous,
8+
}

src/BionicCode.Utilities.Reflection/EventList.cs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -360,26 +360,34 @@ public bool Equals(IEventListView? other)
360360
return true;
361361
}
362362

363-
internal bool Equals(EventList? other)
363+
internal static bool Equals(IEventListView? eventDataViews, EventList? eventList)
364364
{
365-
if (other is null)
365+
// Return FALSE if exactly one of the event lists is NULL, otherwise compare the event lists for equality.
366+
if (eventList is null ^ eventDataViews is null)
366367
{
367368
return false;
368369
}
369370

370-
if (Count != other.Count)
371+
// If the event list view is NULL, the event list must also be NULL at this point, so return TRUE.
372+
// If both event lists are NULL, consider them equal.
373+
if (eventDataViews is null)
374+
{
375+
return true;
376+
}
377+
378+
if (eventDataViews.Count != eventList!.Count)
371379
{
372380
return false;
373381
}
374382

375-
if (!ReferenceEquals(DeclaringType, other.DeclaringTypeData.View))
383+
if (!ReferenceEquals(eventDataViews.DeclaringType, eventList.DeclaringTypeData.View))
376384
{
377385
return false;
378386
}
379387

380-
for (int index = 0; index < Count; index++)
388+
for (int index = 0; index < eventDataViews.Count; index++)
381389
{
382-
if (!Events[index].Equals(other.Events[index].View))
390+
if (!ReferenceEquals(eventDataViews[index], eventList.Events[index].View))
383391
{
384392
return false;
385393
}

src/BionicCode.Utilities.Reflection/FieldList.cs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -332,27 +332,35 @@ public bool Equals(IFieldListView? other)
332332
return true;
333333
}
334334

335-
internal bool Equals(FieldList? other)
335+
internal static bool Equals(IFieldListView? fieldListView, FieldList? fieldList)
336336
{
337-
if (other is null)
337+
// Return FALSE if exactly one of the field lists is NULL, otherwise compare the field lists for equality.
338+
if (fieldList is null ^ fieldListView is null)
338339
{
339340
return false;
340341
}
341342

342-
if (Count != other.Count)
343+
// If the field list view is NULL, the field list must also be NULL at this point, so return TRUE.
344+
// If both field lists are NULL, consider them equal.
345+
if (fieldListView is null)
346+
{
347+
return true;
348+
}
349+
350+
if (fieldListView.Count != fieldList!.Count)
343351
{
344352
return false;
345353
}
346354

347-
if (!ReferenceEquals(DeclaringType, other.DeclaringTypeData.View))
355+
if (!ReferenceEquals(fieldListView.DeclaringType, fieldList.DeclaringTypeData.View))
348356
{
349357
return false;
350358
}
351359

352360
bool isEqual = false;
353-
for (int index = 0; index < Count && !isEqual; index++)
361+
for (int index = 0; index < fieldListView.Count && !isEqual; index++)
354362
{
355-
if (!ReferenceEquals(Fields[index], other.Fields[index].View))
363+
if (!ReferenceEquals(fieldListView.Fields[index], fieldList.Fields[index].View))
356364
{
357365
return false;
358366
}

src/BionicCode.Utilities.Reflection/IConstructorListView.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
public interface IConstructorListView : ICollection, IReadOnlyList<IConstructorDataView>, IEquatable<IConstructorListView>
77
{
88
new int Count { get; }
9+
bool IsEmpty { get; }
10+
bool HasItems { get; }
911
ImmutableList<IConstructorDataView> Constructors { get; }
1012
ITypeDataView DeclaringType { get; }
1113
}

src/BionicCode.Utilities.Reflection/IEventListView.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
public interface IEventListView : ICollection, IReadOnlyList<IEventDataView>, IEquatable<IEventListView>
77
{
88
new int Count { get; }
9+
bool IsEmpty { get; }
10+
bool HasItems { get; }
911
ITypeDataView DeclaringType { get; }
1012
ImmutableList<IEventDataView> Events { get; }
1113
bool TryGetEventByName(string eventName, out IEventDataView? eventData);

src/BionicCode.Utilities.Reflection/IMethodListView.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
public interface IMethodListView : ICollection, IReadOnlyList<IMethodDataView>, IEquatable<IMethodListView>
77
{
88
new int Count { get; }
9-
IMethodDataView this[string? methodName, ParameterDescriptorList? methodParameters] { get; }
109
ITypeDataView DeclaringType { get; }
1110
bool HasItems { get; }
1211
bool IsEmpty { get; }
@@ -17,4 +16,14 @@ public interface IMethodListView : ICollection, IReadOnlyList<IMethodDataView>,
1716
new IEnumerator<IMethodDataView> GetEnumerator();
1817
int GetHashCode();
1918
bool TryGetMethodsByName(string methodName, out IMethodListView methodList);
19+
20+
/// <summary>
21+
/// Gets the method data for the method with the specified name and parameter signature.
22+
/// </summary>
23+
/// <param name="methodName">The name of the method to retrieve. This value is case-sensitive.</param>
24+
/// <param name="genericMethodParameters">Optional. A <see cref="TypeList"/> of generic method parameter types that describe the expected generic parameters for the method. Should be <see langword="null"/> or empty if the method is not generic. If the method is a generic method, consider providing the expected generic parameters to disambiguate the method.</param>
25+
/// <param name="methodParameters">Optional. A <see cref="ParameterDescriptorList"/> of parameter information objects that describe the expected parameter types, positions, and modifiers for the method signature. Should be <see langword="null"/> or empty if method is parameterless. If the method has parameters, consider providing the expected parameters to disambiguate the method.</param>
26+
/// <param name="methodDataView">When this method returns, contains the method data that matches the specified name and parameter signature, if found; otherwise, null.</param>
27+
/// <returns>A <see cref="MemberLookupState"/> value indicating the result of the method lookup.</returns>
28+
MemberLookupState TryGetMethod(string? methodName, ITypeListView? genericMethodParameters, ParameterDescriptorList? methodParameters, out IMethodDataView? methodDataView);
2029
}

src/BionicCode.Utilities.Reflection/IParameterListView.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
public interface IParameterListView : ICollection, IReadOnlyList<IParameterDataView>, IEquatable<IParameterListView>
77
{
88
new int Count { get; }
9+
bool IsEmpty { get; }
10+
bool HasItems { get; }
911
IParameterizedMemberDataView DeclaringMember { get; }
1012
ImmutableList<IParameterDataView> Parameters { get; }
1113
bool TryGetParameterByName(string parameterName, out IParameterDataView? parameterData);

0 commit comments

Comments
 (0)