Skip to content

Commit c793a36

Browse files
committed
Refactor validation, collections, and metadata handling
- Enhanced argument validation with new helpers and improved XML docs - Generalized and expanded OrEmpty/OrNew collection extensions - Added duplicate detection logic to core collection types - Renamed ParameterKind to ParameterModifier for clarity - Refactored method/parameter metadata and ambiguity handling - Introduced IMethodListView, MethodListView, and ISymbolListView interfaces - Updated interfaces to use ImmutableList and removed redundancies - Added AddRangeMode enum for duplicate key handling - Fixed minor bugs and improved documentation throughout
1 parent a7c3490 commit c793a36

35 files changed

+905
-491
lines changed

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

Lines changed: 139 additions & 93 deletions
Large diffs are not rendered by default.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
namespace BionicCode.Utilities.Net;
2+
3+
/// <summary>
4+
/// Defines the options for handling duplicate keys when adding items to a collection.
5+
/// </summary>
6+
/// <remarks>This enumeration allows developers to specify whether duplicate keys should result in an exception or
7+
/// be ignored during addition operations. Use the appropriate value to control the behavior based on the requirements
8+
/// of your collection and application logic.</remarks>
9+
public enum AddRangeMode
10+
{
11+
/// <summary>
12+
/// Request throwing an <see cref="ArgumentException"/> if a duplicate key is detected.
13+
/// </summary>
14+
ThrowOnDuplicateKey,
15+
/// <summary>
16+
/// Request silently skipping the duplicate key if detected.
17+
/// </summary>
18+
SkipDuplicateKey,
19+
}
20+

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

Lines changed: 50 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
using System.Collections.Immutable;
88
using System.Linq;
99
using System.Reflection;
10-
using static BionicCode.Utilities.Net.HelperExtensionsCommon;
1110

1211
/// <summary>
1312
/// A collection of extension methods for various default types
@@ -915,7 +914,7 @@ public static TItem[] MoveRange<TItem>(this TItem[] array, Range range, int newI
915914
{
916915
ArgumentNullException.ThrowIfNull(predicate, nameof(predicate));
917916

918-
return TryFindLast(source, predicate, out TItem result)
917+
return TryFindLast(source, predicate, out TItem? result)
919918
? result
920919
: default;
921920
}
@@ -947,13 +946,13 @@ public static TItem LastInSorted<TItem>(this IEnumerable<TItem> source, Func<TIt
947946
ArgumentNullException.ThrowIfNull(predicate, nameof(predicate));
948947

949948
return source.IsEmpty()
950-
? throw new InvalidOperationException(ExceptionMessages.InvalidOperationExceptionMessage_CollectionEmpty())
951-
: TryFindLast(source, predicate, out TItem result)
952-
? result
949+
? throw new InvalidOperationException(ExceptionMessages.InvalidOperationExceptionMessage_CollectionEmpty)
950+
: TryFindLast(source, predicate, out TItem? result)
951+
? result!
953952
: throw new InvalidOperationException(ExceptionMessages.GetInvalidOperationExceptionMessage_ItemNotFound(nameof(predicate)));
954953
}
955954

956-
private static bool TryFindLast<TItem>(IEnumerable<TItem> source, Func<TItem, bool> predicate, out TItem result)
955+
private static bool TryFindLast<TItem>(IEnumerable<TItem> source, Func<TItem, bool> predicate, out TItem? result)
957956
{
958957
ArgumentNullException.ThrowIfNull(source, nameof(source));
959958
ArgumentNullException.ThrowIfNull(predicate, nameof(predicate));
@@ -1128,47 +1127,71 @@ public static string JoinToString<TItem>(this ReadOnlySpan<TItem> source, Func<T
11281127
string result = stringBuilder.ToString();
11291128
return result;
11301129
}
1131-
1130+
11321131
/// <summary>
1133-
/// Returns the specified collection if it is not <see langword="null"/>; otherwise, creates and returns a new instance of the
1134-
/// collection type.
1132+
/// Returns the specified value if it is not <see langword="null"/>; otherwise, creates and returns a new instance of the
1133+
/// value by invoking its parameterless constructor.
11351134
/// </summary>
1136-
/// <remarks>Use this method to ensure that a collection is always available, which helps prevent <see cref="NullReferenceException"/> when working with collections.</remarks>
1137-
/// <typeparam name="TCollection">The type of collection to return. Must be a reference type that implements <see cref="ICollection"/> and has a parameterless
1138-
/// constructor.</typeparam>
1139-
/// <param name="source">The collection to return if it is not <see langword="null"/>; otherwise, a new instance of the specified collection type.</param>
1140-
/// <returns>The original collection if it is not <see langword="null"/>; otherwise, a new instance of the specified collection type.</returns>
1141-
public static TCollection OrNew<TCollection>(this TCollection? source) where TCollection : class, ICollection, new() => source ?? new TCollection();
1135+
/// <remarks>Use this method to ensure that an instance is always available, which helps prevent <see cref="NullReferenceException"/> when working with parameters or return values.</remarks>
1136+
/// <typeparam name="T">The type of instance to validate. Must be a reference or value type that has a parameterless constructor.</typeparam>
1137+
/// <param name="value">The value to return if it is not <see langword="null"/>; otherwise, a new instance of the specified type.</param>
1138+
/// <returns>The original value if it is not <see langword="null"/>; otherwise, a new instance of the specified type.</returns>
1139+
public static T OrNew<T>(this T? value) where T : class, new() => value ?? new T();
11421140

11431141
/// <summary>
11441142
/// Returns the specified collection if it is not <see langword="null"/>; otherwise, returns an empty collection of the same type.
11451143
/// </summary>
11461144
/// <remarks>This method simplifies null checks by ensuring that a collection is never null, which can
11471145
/// help prevent <see cref="NullReferenceException"/> in client code.</remarks>
1148-
/// <typeparam name="TCollection">The type of the collection, which must implement both <see cref="IEmptyCollectionProvider{TCollection}"/> and <see cref="ICollection"/>.</typeparam>
1149-
/// <param name="source">The collection to return if it is not <see langword="null"/>; otherwise, an empty collection of the same type.</param>
1146+
/// <typeparam name="TCollection">The type of the collection, which must implement both <see cref="IEmptyCollectionProvider{TCollection}"/> and <see cref="IEnumerable"/>.</typeparam>
1147+
/// <param name="source">The collection to return if it is not <see langword="null"/>.</param>
11501148
/// <returns>The original collection if it is not <see langword="null"/>; otherwise, an empty collection of type <typeparamref name="TCollection"/>.</returns>
1151-
public static TCollection OrEmpty<TCollection>(this TCollection? source) where TCollection : IEmptyCollectionProvider<TCollection>, ICollection => source ?? TCollection.Empty;
1149+
public static TCollection OrEmpty<TCollection>(this TCollection? source) where TCollection : IEmptyCollectionProvider<TCollection> => source ?? TCollection.Empty;
11521150

11531151
/// <summary>
11541152
/// Returns the specified collection if it is not <see langword="null"/>; otherwise, returns an empty collection of the same type by invoking the provided factory <paramref name="emptyCollectionFactory"/>.
11551153
/// </summary>
1156-
/// <remarks>This method simplifies null checks by ensuring that a collection is never null, which can
1154+
/// <remarks>This method simplifies <see langword="null"/> checks by ensuring that a collection is never <see langword="null"/>, which can
11571155
/// help prevent <see cref="NullReferenceException"/> in client code.</remarks>
1158-
/// <typeparam name="TCollection">The type of the collection, which must implement both <see cref="IEmptyCollectionProvider{TCollection}"/> and <see cref="ICollection"/>.</typeparam>
1159-
/// <param name="source">The collection to return if it is not <see langword="null"/>; otherwise, an empty collection of the same type.</param>
1156+
/// <typeparam name="TCollection">The type of the collection, which must implement <see cref="IEnumerable"/>.</typeparam>
1157+
/// <param name="source">The collection to return if it is not <see langword="null"/>.</param>
11601158
/// <param name="emptyCollectionFactory">A factory function to create an empty collection if the source is <see langword="null"/>.</param>
11611159
/// <returns>The original collection if it is not <see langword="null"/>; otherwise, an empty collection of type <typeparamref name="TCollection"/> as the result of invoking the <paramref name="emptyCollectionFactory"/>.</returns>
1162-
public static TCollection OrEmpty<TCollection>(this TCollection? source, Func<TCollection> emptyCollectionFactory) where TCollection : IEmptyCollectionProvider<TCollection>, ICollection
1160+
public static TCollection OrEmpty<TCollection>(this TCollection? source, Func<TCollection> emptyCollectionFactory) where TCollection : IEnumerable
11631161
{
11641162
ArgumentNullExceptionAdvanced.ThrowIfNull(emptyCollectionFactory);
11651163

11661164
return source ?? emptyCollectionFactory.Invoke();
11671165
}
1168-
}
1169-
public enum AddRangeMode
1170-
{
1171-
ThrowOnDuplicateKey,
1172-
SkipDuplicateKey,
1166+
1167+
/// <summary>
1168+
/// Returns the specified array if it is not <see langword="null"/>; otherwise, returns an empty array of the same type.
1169+
/// </summary>
1170+
/// <remarks>This method simplifies <see langword="null"/> checks by ensuring that an array is never <see langword="null"/>, which can
1171+
/// help prevent <see cref="NullReferenceException"/> in client code.</remarks>
1172+
/// <typeparam name="TItem">The type of the array items.</typeparam>
1173+
/// <param name="source">The array to return if it is not <see langword="null"/>.</param>
1174+
/// <returns>The original array if it is not <see langword="null"/>; otherwise, an empty array of type <typeparamref name="TItem"/>.</returns>
1175+
public static TItem[] OrEmpty<TItem>(this TItem[]? source) => source ?? Array.Empty<TItem>();
1176+
1177+
/// <summary>
1178+
/// Returns the specified <see cref="IEnumerable{TItem}"/> if it is not <see langword="null"/>; otherwise, returns an empty <see cref="IEnumerable{TItem}"/>.
1179+
/// </summary>
1180+
/// <remarks>This method simplifies <see langword="null"/> checks by ensuring that a <see cref="IEnumerable{TItem}"/> is never <see langword="null"/>, which can
1181+
/// help prevent <see cref="NullReferenceException"/> in client code.</remarks>
1182+
/// <typeparam name="TItem">The type of the <see cref="IEnumerable{TItem}"/> items..</typeparam>
1183+
/// <param name="source">The <see cref="IEnumerable{TItem}"/> to return if it is not <see langword="null"/>.</param>
1184+
/// <returns>The original <see cref="IEnumerable{TItem}"/> if it is not <see langword="null"/>; otherwise, an empty <see cref="IEnumerable{TItem}"/>.</returns>
1185+
public static IEnumerable<TItem> OrEmpty<TItem>(this IEnumerable<TItem>? source) => source ?? Enumerable.Empty<TItem>();
1186+
1187+
/// <summary>
1188+
/// Returns the specified <see cref="List{TItem}"/> if it is not <see langword="null"/>; otherwise, returns an empty <see cref="List{TItem}"/>.
1189+
/// </summary>
1190+
/// <remarks>This method simplifies <see langword="null"/> checks by ensuring that a <see cref="List{TItem}"/> is never <see langword="null"/>, which can
1191+
/// help prevent <see cref="NullReferenceException"/> in client code.</remarks>
1192+
/// <typeparam name="TItem">The type of the <see cref="List{TItem}"/> items..</typeparam>
1193+
/// <param name="source">The <see cref="List{TItem}"/> to return if it is not <see langword="null"/>.</param>
1194+
/// <returns>The original <see cref="List{TItem}"/> if it is not <see langword="null"/>; otherwise, an empty <see cref="List{TItem}"/>.</returns>
1195+
public static List<TItem> OrEmpty<TItem>(this List<TItem>? source) => source ?? [];
11731196
}
11741197

src/BionicCode.Utilities.Reflection/AnonymousParameterDescriptor.cs

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -23,35 +23,32 @@
2323
/// <summary>
2424
/// Constructs a descriptor that provides the specified parameter information for an anonymous parameter.
2525
/// </summary>
26-
/// <param name="declaringMethodDescriptor">The <see cref="AnonymousMethodDescriptor"/> representing the anonymous method that declares the parameter.</param>
2726
/// <param name="parameterTypeHandle">Conditionally optional. The runtime type handle representing the type of the anonymous parameter.<para/>
28-
/// Must be provided if all of the following arguments are missing: <paramref name="parameterName"/> AND <paramref name="parameterKind"/> AND <paramref name="parameterPosition"/>.</param>
27+
/// Must be provided if all of the following arguments are missing: <paramref name="parameterName"/> AND <paramref name="parameterModifier"/> AND <paramref name="parameterPosition"/>.</param>
2928
/// <param name="parameterName">Conditionally optional. The name of the anonymous parameter.<para/>
30-
/// Must be provided if all of the following arguments are missing: <paramref name="parameterTypeHandle"/> AND <paramref name="parameterKind"/> AND <paramref name="parameterPosition"/>.</param>
29+
/// Must be provided if all of the following arguments are missing: <paramref name="parameterTypeHandle"/> AND <paramref name="parameterModifier"/> AND <paramref name="parameterPosition"/>.</param>
3130
/// <param name="parameterPosition">Conditionally optional.The index of the parameter.<para/>
32-
/// Must be provided if all of the following arguments are missing: <paramref name="parameterName"/> AND <paramref name="parameterTypeHandle"/> AND <paramref name="parameterKind"/>.</param>
33-
/// <param name="parameterKind">Conditionally optional. The modifier of the parameter.<para/>
31+
/// Must be provided if all of the following arguments are missing: <paramref name="parameterName"/> AND <paramref name="parameterTypeHandle"/> AND <paramref name="parameterModifier"/>.</param>
32+
/// <param name="parameterModifier">Conditionally optional. The modifier of the parameter.<para/>
3433
/// Must be provided if all of the following arguments are missing: <paramref name="parameterName"/> AND <paramref name="parameterTypeHandle"/> AND <paramref name="parameterPosition"/>.</param>
3534
public AnonymousParameterDescriptor(
36-
AnonymousMethodDescriptor declaringMethodDescriptor,
3735
string? parameterName = null,
3836
int parameterPosition = UnknownParameterCountOrPosition,
39-
ParameterKind parameterKind = ParameterKind.Undefined,
37+
ParameterModifier parameterModifier = ParameterModifier.Undefined,
4038
RuntimeTypeHandle? parameterTypeHandle = null)
4139
{
42-
ArgumentExceptionAdvanced.ThrowIfEnumIsNotDefined<ParameterKind>(parameterKind);
40+
ArgumentExceptionAdvanced.ThrowIfEnumIsNotDefined<ParameterModifier>(parameterModifier);
4341
if (parameterTypeHandle.Equals(default)
4442
&& string.IsNullOrWhiteSpace(parameterName)
45-
&& parameterKind == ParameterKind.Undefined
43+
&& parameterModifier == ParameterModifier.Undefined
4644
&& parameterPosition == UnknownParameterCountOrPosition)
4745
{
48-
throw new ArgumentException($"At least one of the following arguments must be provided to avoid ambiguity when using the created key for lookups: '{nameof(parameterTypeHandle)}', '{nameof(parameterName)}', '{nameof(parameterKind)}', '{nameof(parameterPosition)}'.");
46+
throw new ArgumentException($"At least one of the following arguments must be provided to avoid ambiguity when using the created key for lookups: '{nameof(parameterTypeHandle)}', '{nameof(parameterName)}', '{nameof(parameterModifier)}', '{nameof(parameterPosition)}'.");
4947
}
5048

51-
DeclaringMethodDescriptor = declaringMethodDescriptor;
5249
ParameterName = parameterName ?? string.Empty;
5350
ParameterPosition = parameterPosition;
54-
ParameterKind = parameterKind;
51+
ParameterModifier = parameterModifier;
5552
ParameterTypeHandle = parameterTypeHandle ?? default;
5653
IsAnonymous = true;
5754
}
@@ -60,31 +57,28 @@ public AnonymousParameterDescriptor(
6057

6158
public bool HasParameterPosition => ParameterPosition > UnknownParameterCountOrPosition;
6259

63-
public bool HasParameterKind => ParameterKind != ParameterKind.Undefined;
60+
public bool HasParameterModifier => ParameterModifier != ParameterModifier.Undefined;
6461

6562
public bool HasParameterTypeHandle => !ParameterTypeHandle.Equals(default);
6663

6764
public bool IsAnonymous { get; }
6865

69-
public AnonymousMethodDescriptor DeclaringMethodDescriptor { get; }
7066
public string ParameterName { get; }
7167
public int ParameterPosition { get; }
72-
public ParameterKind ParameterKind { get; }
68+
public ParameterModifier ParameterModifier { get; }
7369
public RuntimeTypeHandle ParameterTypeHandle { get; }
7470

7571
public bool Equals(AnonymousParameterDescriptor other) => ParameterName.Equals(other.ParameterName, StringComparison.Ordinal)
7672
&& ParameterPosition == other.ParameterPosition
77-
&& ParameterKind == other.ParameterKind
73+
&& ParameterModifier == other.ParameterModifier
7874
&& ParameterTypeHandle.Equals(other.ParameterTypeHandle)
79-
&& DeclaringMethodDescriptor.Equals(other.DeclaringMethodDescriptor)
8075
&& IsAnonymous == other.IsAnonymous;
8176

8277
public override int GetHashCode() => HashCode.Combine(
8378
ParameterName,
8479
ParameterPosition,
85-
ParameterKind,
80+
ParameterModifier,
8681
ParameterTypeHandle,
87-
DeclaringMethodDescriptor,
8882
IsAnonymous);
8983

9084
public static bool operator ==(AnonymousParameterDescriptor left, AnonymousParameterDescriptor right) => left.Equals(right);

src/BionicCode.Utilities.Reflection/ConstructorListBuilder.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,14 @@ ConstructorList IConstructorListBuilder.Build()
8585

8686
internal static class ConstructorListBuilderExtensions
8787
{
88-
internal static ConstructorList ToConstructorList(this IEnumerable<ConstructorData> items, TypeData? declaringType) => items is null || items.IsEmpty()
89-
? ConstructorList.Empty
90-
: new ConstructorList(items, declaringType);
88+
internal static ConstructorList ToConstructorList(this IEnumerable<ConstructorData> items, TypeData? declaringType)
89+
{
90+
ArgumentNullExceptionAdvanced.ThrowIfNull(declaringType);
91+
92+
return items is null || items.IsEmpty()
93+
? ConstructorList.Empty
94+
: new ConstructorList(items, declaringType);
95+
}
9196

9297
internal static IConstructorListView ToConstructorListView(this IEnumerable<ConstructorData> items, TypeData declaringType)
9398
{

0 commit comments

Comments
 (0)