Skip to content

Commit 484d61e

Browse files
committed
Refactor list types to use explicit declaring references
Refactored ConstructorList, EventList, ParameterList, and their builders to use direct references to declaring type/member instead of cache keys. Introduced builder interfaces, updated extension methods, and improved validation and equality logic. Enhanced type safety, maintainability, and API clarity throughout reflection utilities. Also includes minor improvements and code cleanup.
1 parent a8c6fed commit 484d61e

12 files changed

Lines changed: 453 additions & 647 deletions

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -490,7 +490,7 @@ or OverflowException
490490
}
491491
else
492492
{
493-
TypeData iConvertibleTypeData = SymbolReflectionInfoCache.GetOrCreateSymbolInfoDataCacheEntry(typeof(IConvertible));
493+
ITypeDataView iConvertibleTypeData = SymbolReflectionInfoCache.GetOrCreateSymbolInfoDataCacheEntry(typeof(IConvertible));
494494
const string iConvertibleToStringMethodName = nameof(IConvertible.ToString);
495495
MethodData? iConvertibleToStringMethodData = iConvertibleTypeData.Methods.TryGetMethodsByName(iConvertibleToStringMethodName, out MethodList methods) && methods.HasItems
496496
? methods[0]
@@ -579,7 +579,7 @@ public static void ThrowIfNotAssignableTo<TValue, TTarget>([NotNull] TValue valu
579579
/// <param name="paramName">The name of the parameter representing the type to validate. This value is typically provided automatically
580580
/// and should not be set explicitly in most cases.</param>
581581
/// <param name="message">An optional exception message.</param>
582-
/// <exception cref="ArgumentException">Thrown if <paramref name="value"/> is not assignable to <paramref name="target"/>.</exception>
582+
/// <exception cref="ArgumentException">Thrown if <typeparamref name="TValue"/> is not assignable to <typeparamref name="TTarget"/>.</exception>
583583
public static void ThrowIfNotAssignableTo<TValue, TTarget>(string? message = null)
584584
{
585585
Type targetType = typeof(TTarget);
@@ -600,8 +600,8 @@ public static void ThrowIfNotAssignableTo<TValue, TTarget>(string? message = nul
600600
/// and should not be set explicitly in most cases.</param>
601601
/// <typeparam name="TTarget"> The target type to check assignability against. Cannot be null.</typeparam>
602602
/// <param name="message">An optional exception message.</param>
603-
/// <exception cref="ArgumentException">Thrown if <paramref name="value"/> is not assignable to <paramref name="target"/>.</exception>
604-
public static void ThrowIfNotAssignableTo<TTarget>([NotNull] object value, [CallerArgumentExpression(nameof(value))] string? paramName = null, string? message = null)
603+
/// <exception cref="ArgumentException">Thrown if <paramref name="value"/> is not assignable to <typeparamref name="TTarget"/>.</exception>
604+
public static void ThrowIfNotAssignableTo<TTarget>([NotNull] object? value, [CallerArgumentExpression(nameof(value))] string? paramName = null, string? message = null)
605605
{
606606
ArgumentNullException.ThrowIfNull(value, paramName);
607607
Type targetType = typeof(TTarget);

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

Lines changed: 22 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,48 +3,49 @@
33
using System;
44
using System.Collections.Generic;
55
using System.Collections.Immutable;
6-
using System.Linq;
76

87
internal sealed class ConstructorList : IReadOnlyList<ConstructorData>, IEquatable<ConstructorList>
98
{
109
public static ConstructorList Empty { get; } = new ConstructorList();
1110
private readonly int _hashCode; // precomputed
12-
private readonly SymbolReflectionInfoCacheKeyInternal _declaringTypeCacheKey;
11+
private readonly TypeData _declaringTypeData;
1312

14-
public ConstructorList(ConstructorData[] items) : this((IEnumerable<ConstructorData>)items)
13+
public ConstructorList(ConstructorData[] items, TypeData? declaringType) : this((IEnumerable<ConstructorData>)items, declaringType)
1514
{
1615
}
1716

18-
public ConstructorList(IEnumerable<ConstructorData> items)
17+
public ConstructorList(IEnumerable<ConstructorData> items, TypeData? declaringType)
1918
{
20-
Constructors = items?.ToImmutableList() ?? ImmutableList<ConstructorData>.Empty;
19+
ArgumentNullExceptionAdvanced.ThrowIfNull(declaringType);
20+
21+
_declaringTypeData = declaringType;
2122

23+
Constructors = items?.ToImmutableList() ?? ImmutableList<ConstructorData>.Empty;
2224
if (HasItems)
2325
{
24-
_declaringTypeCacheKey = Constructors.First().DeclaringTypeData.CacheKey;
2526

2627
ArgumentExceptionAdvanced.ThrowIfAny(
2728
Constructors,
28-
constructorData => constructorData.DeclaringTypeData.CacheKey != DeclaringTypeCacheKey,
29+
constructorData => !ReferenceEquals(constructorData.DeclaringTypeData, _declaringTypeData),
2930
nameof(items),
3031
$"At least one item in the argument sequence '{nameof(items)}' has a different value for the '{nameof(ConstructorData)}.{nameof(MemberData.DeclaringTypeHandle)}' declaring type handle. All constructors must belong to the same declaring type.");
3132
}
3233

3334
_hashCode = ComputeHashCode();
3435
}
3536

36-
internal ConstructorList(IEnumerable<ConstructorData> items, bool isIntegrityValidationEnabled)
37+
internal ConstructorList(IEnumerable<ConstructorData> items, TypeData? declaringType, bool isIntegrityValidationEnabled)
3738
{
38-
Constructors = items?.ToImmutableList() ?? ImmutableList<ConstructorData>.Empty;
39-
_declaringTypeCacheKey = HasItems
40-
? Constructors.First().DeclaringTypeData.CacheKey
41-
: default;
39+
ArgumentNullExceptionAdvanced.ThrowIfNull(declaringType);
4240

41+
_declaringTypeData = declaringType;
42+
43+
Constructors = items?.ToImmutableList() ?? ImmutableList<ConstructorData>.Empty;
4344
if (isIntegrityValidationEnabled && HasItems)
4445
{
4546
ArgumentExceptionAdvanced.ThrowIfAny(
4647
Constructors,
47-
constructorData => constructorData.DeclaringTypeData.CacheKey != DeclaringTypeCacheKey,
48+
constructorData => !ReferenceEquals(constructorData.DeclaringTypeData, _declaringTypeData),
4849
nameof(items),
4950
$"At least one item in the argument sequence '{nameof(items)}' has a different value for the '{nameof(ConstructorData)}.{nameof(MemberData.DeclaringTypeHandle)}' declaring type handle. All constructors must belong to the same declaring type.");
5051
}
@@ -53,27 +54,17 @@ internal ConstructorList(IEnumerable<ConstructorData> items, bool isIntegrityVal
5354
}
5455

5556
private ConstructorList()
56-
=> Constructors = ImmutableList<ConstructorData>.Empty;
57+
{
58+
Constructors = ImmutableList<ConstructorData>.Empty;
59+
_declaringTypeData = default!;
60+
_hashCode = ComputeHashCode();
61+
}
5762

5863
public int Count => Constructors.Count;
5964
public bool IsEmpty => Constructors.IsEmpty;
6065
public bool HasItems => !IsEmpty;
6166
public ImmutableList<ConstructorData> Constructors { get; }
62-
public SymbolReflectionInfoCacheKeyInternal DeclaringTypeCacheKey
63-
=> HasItems
64-
? _declaringTypeCacheKey
65-
: throw new InvalidOperationException(ExceptionMessages.GetInvalidAccessCollectionEmptyExceptionMessage(GetType().Name, nameof(DeclaringTypeCacheKey)));
66-
67-
public TypeData DeclaringTypeData
68-
{
69-
get
70-
{
71-
SymbolReflectionInfoCacheKeyInternal cacheKey = DeclaringTypeCacheKey;
72-
return HasItems
73-
? SymbolReflectionInfoCache.GetOrCreateTypeDataCacheEntry(ref cacheKey)
74-
: throw new InvalidOperationException(ExceptionMessages.GetInvalidAccessCollectionEmptyExceptionMessage(GetType().Name, nameof(DeclaringTypeData)));
75-
}
76-
}
67+
public TypeData DeclaringTypeData => _declaringTypeData ?? throw new InvalidOperationException(ExceptionMessages.GetInvalidAccessCollectionEmptyExceptionMessage(GetType().Name, nameof(DeclaringTypeData)));
7768

7869
public ConstructorData this[int index]
7970
{
@@ -106,7 +97,7 @@ public bool Equals(ConstructorList? other)
10697
return false;
10798
}
10899

109-
if (DeclaringTypeCacheKey != other.DeclaringTypeCacheKey)
100+
if (!ReferenceEquals(DeclaringTypeData, other.DeclaringTypeData))
110101
{
111102
return false;
112103
}
@@ -134,7 +125,7 @@ private int ComputeHashCode()
134125
{
135126
var hashCode = new HashCode();
136127
hashCode.Add(Count);
137-
hashCode.Add(DeclaringTypeCacheKey);
128+
hashCode.Add(DeclaringTypeData);
138129
for (int index = 0; index < Constructors.Count; index++)
139130
{
140131
hashCode.Add(Constructors[index]);
Lines changed: 70 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,107 +1,98 @@
1-
namespace BionicCode.Utilities.Net.Reflection
1+
namespace BionicCode.Utilities.Net.Reflection;
2+
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Reflection;
7+
8+
internal interface IConstructorListBuilder
9+
{
10+
TypeData DeclaringType { get; }
11+
IConstructorListBuilder Add(ConstructorData constructorData);
12+
ConstructorList Build();
13+
}
14+
15+
internal class ConstructorListBuilder : SymbolDataListBuilder<ConstructorData>, IConstructorListBuilder
216
{
3-
using System;
4-
using System.Collections.Generic;
5-
using System.Linq;
6-
using System.Reflection;
17+
private ConstructorList? _builderResult;
18+
private readonly TypeData _declaringType;
719

8-
internal interface IConstructorListBuilder
20+
private ConstructorListBuilder(TypeData declaringType) : base(declaringType.Handle) => _declaringType = declaringType;
21+
22+
public static IConstructorListBuilder New(TypeData declaringType)
923
{
10-
IConstructorListBuilder Add(ConstructorData constructorData);
11-
ConstructorList Build();
24+
ArgumentNullException.ThrowIfNull(declaringType);
25+
26+
var builder = new ConstructorListBuilder(declaringType);
27+
return builder;
1228
}
1329

14-
internal class ConstructorListBuilder : SymbolDataListBuilder<ConstructorData>, IConstructorListBuilder
30+
internal static ConstructorList Create(IEnumerable<ConstructorInfo>? items)
1531
{
16-
private ConstructorList? _builderResult;
17-
18-
private ConstructorListBuilder(RuntimeTypeHandle declaringTypeHandle) : base(declaringTypeHandle)
32+
var constructorInfoList = items?.ToList();
33+
if (constructorInfoList is null || constructorInfoList.IsEmpty())
1934
{
35+
return ConstructorList.Empty;
2036
}
2137

22-
public static IConstructorListBuilder New(RuntimeTypeHandle declaringTypeHandle)
38+
var constructors = new List<ConstructorData>(constructorInfoList.Count);
39+
TypeData? declaringType = null;
40+
foreach (ConstructorInfo constructorInfo in constructorInfoList)
2341
{
24-
var builder = new ConstructorListBuilder(declaringTypeHandle);
25-
return builder;
26-
}
42+
ConstructorData constructorData = GetOrCreateCacheEntry(constructorInfo);
2743

28-
internal static ConstructorList Create(IEnumerable<ConstructorInfo>? items)
29-
{
30-
List<ConstructorInfo>? constructorInfoList = items?.ToList();
31-
if (constructorInfoList is null || constructorInfoList.IsEmpty())
32-
{
33-
return ConstructorList.Empty;
34-
}
44+
declaringType ??= constructorData.DeclaringTypeData;
3545

36-
List<ConstructorData> constructors = new List<ConstructorData>(constructorInfoList.Count);
37-
RuntimeTypeHandle declaringTypeHandle = default;
38-
foreach (ConstructorInfo constructorInfo in constructorInfoList)
46+
if (!ReferenceEquals(constructorData.DeclaringTypeData, declaringType))
3947
{
40-
ConstructorData constructorData = SymbolReflectionInfoCache.GetOrCreateSymbolReflectionInfoCacheEntry(constructorInfo);
41-
42-
if (declaringTypeHandle.Equals(default))
43-
{
44-
declaringTypeHandle = constructorData.DeclaringTypeHandle;
45-
}
46-
47-
if (!constructorData.DeclaringTypeHandle.Equals(declaringTypeHandle))
48-
{
49-
throw new ArgumentException($"The argument '{nameof(items)}' contains invalid items. Reason: All '{nameof(ConstructorInfo)}' items must belong to the same declaring type.");
50-
}
51-
52-
constructors.Add(constructorData);
48+
throw new ArgumentException($"The argument '{nameof(items)}' contains invalid items. Reason: All '{nameof(ConstructorInfo)}' items must belong to the same declaring type.");
5349
}
5450

55-
return constructors.ToConstructorList();
51+
constructors.Add(constructorData);
5652
}
5753

58-
internal static ConstructorList Create(TypeData declaringTypeData)
59-
{
60-
ArgumentNullException.ThrowIfNull(declaringTypeData);
61-
return CreateInternal(declaringTypeData.Type);
62-
}
54+
return constructors.ToConstructorList(declaringType);
55+
}
6356

64-
internal static ConstructorList Create(Type declaringType)
65-
{
66-
ArgumentNullException.ThrowIfNull(declaringType);
67-
return CreateInternal(declaringType);
68-
}
57+
internal static ConstructorList Create(TypeData declaringTypeData)
58+
{
59+
ArgumentNullException.ThrowIfNull(declaringTypeData);
6960

70-
private static ConstructorList CreateInternal(Type declaringType)
71-
{
72-
ConstructorInfo[] constructorInfoList = declaringType.GetConstructors(ReflectionHelperExtensions.AllMembersFullHierarchyFlags);
73-
if (constructorInfoList.IsEmpty())
74-
{
75-
return ConstructorList.Empty;
76-
}
61+
return CreateInternal(declaringTypeData);
62+
}
7763

78-
IEnumerable<ConstructorData> constructors = constructorInfoList.Select(SymbolReflectionInfoCache.GetOrCreateSymbolReflectionInfoCacheEntry);
64+
internal static ConstructorList Create(Type declaringType)
65+
{
66+
ArgumentNullException.ThrowIfNull(declaringType);
7967

80-
return constructors.ToConstructorList();
81-
}
68+
TypeData declaringTypeData = GetOrCreateCacheEntry(declaringType);
69+
return CreateInternal(declaringTypeData);
70+
}
8271

83-
IConstructorListBuilder IConstructorListBuilder.Add(ConstructorData constructorData)
84-
{
85-
Add(constructorData);
86-
return this;
87-
}
72+
private static ConstructorList CreateInternal(TypeData declaringType) => declaringType.EnumerateConstructors().ToConstructorList(declaringType);
8873

89-
ConstructorList IConstructorListBuilder.Build()
90-
=> _builderResult ??= new ConstructorList(Build(), isIntegrityValidationEnabled: false);
91-
}
74+
TypeData IConstructorListBuilder.DeclaringType => _declaringType;
9275

93-
internal static class ConstructorListBuilderExtensions
76+
IConstructorListBuilder IConstructorListBuilder.Add(ConstructorData constructorData)
9477
{
78+
Add(constructorData);
79+
return this;
80+
}
9581

96-
internal static ConstructorList ToConstructorList(this IEnumerable<ConstructorData> items)
97-
=> items is null || items.IsEmpty() ? ConstructorList.Empty : new ConstructorList(items);
82+
ConstructorList IConstructorListBuilder.Build()
83+
=> _builderResult ??= new ConstructorList(Build(), ((IConstructorListBuilder)this).DeclaringType, isIntegrityValidationEnabled: false);
84+
}
9885

99-
/// <summary>
100-
/// Returns an empty <see cref="ConstructorList"/> if the provided instance is <see langword="null"/>.
101-
/// </summary>
102-
/// <param name="items"></param>
103-
/// <returns>A <see cref="ConstructorList"/> that is empty if the provided instance is <see langword="null"/>. Otherwise, returns the original instance.</returns>
104-
public static ConstructorList OrEmpty(this ConstructorList items)
105-
=> items ?? ConstructorList.Empty;
106-
}
86+
internal static class ConstructorListBuilderExtensions
87+
{
88+
internal static ConstructorList ToConstructorList(this IEnumerable<ConstructorData> items, TypeData? declaringType)
89+
=> items is null || items.IsEmpty() ? ConstructorList.Empty : new ConstructorList(items, declaringType);
90+
91+
/// <summary>
92+
/// Returns an empty <see cref="ConstructorList"/> if the provided instance is <see langword="null"/>.
93+
/// </summary>
94+
/// <param name="items"></param>
95+
/// <returns>A <see cref="ConstructorList"/> that is empty if the provided instance is <see langword="null"/>. Otherwise, returns the original instance.</returns>
96+
public static ConstructorList OrEmpty(this ConstructorList items)
97+
=> items ?? ConstructorList.Empty;
10798
}

0 commit comments

Comments
 (0)