From 9c9fe0f23a43bee0d43a3e0d18336e77c680ce26 Mon Sep 17 00:00:00 2001 From: Kenny Pflug Date: Mon, 21 Jul 2025 06:58:34 +0200 Subject: [PATCH 1/5] chore: add plan for issue 120 Signed-off-by: Kenny Pflug --- ...have-maximum-length-for-immutable-array.md | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 Code/Plans/issue-120-must-have-maximum-length-for-immutable-array.md diff --git a/Code/Plans/issue-120-must-have-maximum-length-for-immutable-array.md b/Code/Plans/issue-120-must-have-maximum-length-for-immutable-array.md new file mode 100644 index 0000000..a79a18b --- /dev/null +++ b/Code/Plans/issue-120-must-have-maximum-length-for-immutable-array.md @@ -0,0 +1,22 @@ +# Issue 120 - MustHaveMaximumLength for ImmutableArray + +## Context + +The .NET library Light.GuardClauses already has several assertions for collections. They often rely on `IEnumerable` or `IEnumerable`. However, these assertions would result in `ImmutableArray` being boxed - we want to avoid that by providing dedicated assertions for this type which avoids boxing. For this issue, we implement the `MustHaveMaximumLength` assertion for `ImmutableArray`. + +## Tasks for this issue + +- [ ] The production code should be placed in the Light.GuardClauses project. There is no existing `Check.MustHaveMaximumLength.cs` file, but there is a `Check.MustHaveMaximumCount.cs` file. Create a new file called `Check.MustHaveMaximumLength.cs` in the root folder of the project. +- [ ] In this file, create several extension method overloads called `MustHaveMaximumLength` for `ImmutableArray`. It should be placed in the class `Check` which is marked as `partial`. +- [ ] Each assertion in Light.GuardClauses has two overloads - the first one takes the optional `parameterName` and `message` arguments and throw the default exception. The actual exception is thrown in the `Throw` class - use the existing `Throw.InvalidMaximumCollectionCount` method which is located in `ExceptionFactory/Throw.InvalidMaximumCollectionCount.cs`. +- [ ] The other overload takes a delegate which allows the caller to provide their own custom exceptions. Use the existing `Throw.CustomException` method and pass the delegate, the erroneous `ImmutableArray` instance and the maximum length. +- [ ] Use the `Length` property of `ImmutableArray` instead of `Count` for performance and correctness. +- [ ] Create unit tests for both overloads. The corresponding tests should be placed in Light.GuardClauses.Tests project. There is an existing file 'CollectionAssertions/MustHaveMaximumCountTests.cs' but you need to create a new file 'CollectionAssertions/MustHaveMaximumLengthTests.cs' for length-related tests. Please follow conventions of the existing tests (e.g. use FluentAssertions' `Should()` for assertions). + +## Notes + +- There are already plenty of other assertions and tests in this library. All overloads are placed in the same file in the production code project. The test projects has top-level folders for different groups of assertions, like `CollectionAssertions`, `StringAssertions`, `DateTimeAssertions` and so on. Please take a look at them to follow a similar structure and code style. +- This assertion specifically targets `ImmutableArray` to avoid boxing that would occur with generic `IEnumerable` extensions. +- Use the `Length` property instead of `Count` as this is the appropriate property for `ImmutableArray`. +- The assertion should verify that the `ImmutableArray` does not exceed the specified maximum length. +- If you have any questions or suggestions, please ask me about them. From 90944bd3b3dd6ce4831fa28cccc54b935378c01b Mon Sep 17 00:00:00 2001 From: Kenny Pflug Date: Mon, 21 Jul 2025 07:08:11 +0200 Subject: [PATCH 2/5] feat: add MustHaveMaximumLength for ImmutableArray Signed-off-by: Kenny Pflug --- .../MustHaveMaximumLengthTests.cs | 90 +++++++++++++++++++ .../Check.MustHaveMaximumLength.cs | 56 ++++++++++++ 2 files changed, 146 insertions(+) create mode 100644 Code/Light.GuardClauses.Tests/CollectionAssertions/MustHaveMaximumLengthTests.cs create mode 100644 Code/Light.GuardClauses/Check.MustHaveMaximumLength.cs diff --git a/Code/Light.GuardClauses.Tests/CollectionAssertions/MustHaveMaximumLengthTests.cs b/Code/Light.GuardClauses.Tests/CollectionAssertions/MustHaveMaximumLengthTests.cs new file mode 100644 index 0000000..f530fdf --- /dev/null +++ b/Code/Light.GuardClauses.Tests/CollectionAssertions/MustHaveMaximumLengthTests.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Immutable; +using FluentAssertions; +using Light.GuardClauses.Exceptions; +using Xunit; + +namespace Light.GuardClauses.Tests.CollectionAssertions; + +public static class MustHaveMaximumLengthTests +{ + [Theory] + [InlineData(new[] { 1, 2, 3, 4 }, 3)] + [InlineData(new[] { 1, 2 }, 1)] + [InlineData(new[] { 500 }, 0)] + public static void ImmutableArrayMoreItems(int[] items, int length) + { + var immutableArray = items.ToImmutableArray(); + Action act = () => immutableArray.MustHaveMaximumLength(length, nameof(immutableArray)); + + var assertion = act.Should().Throw().Which; + assertion.Message.Should().Contain( + $"{nameof(immutableArray)} must have at most count {length}, but it actually has count {immutableArray.Length}." + ); + assertion.ParamName.Should().BeSameAs(nameof(immutableArray)); + } + + [Theory] + [InlineData(new[] { "Foo" }, 1)] + [InlineData(new[] { "Bar" }, 2)] + [InlineData(new[] { "Baz", "Qux", "Quux" }, 5)] + public static void ImmutableArrayLessOrEqualItems(string[] items, int length) + { + var immutableArray = items.ToImmutableArray(); + var result = immutableArray.MustHaveMaximumLength(length); + result.Should().Equal(immutableArray); + } + + [Fact] + public static void ImmutableArrayEmpty() + { + var emptyArray = ImmutableArray.Empty; + var result = emptyArray.MustHaveMaximumLength(5); + result.Should().Equal(emptyArray); + } + + [Theory] + [InlineData(new[] { 87, 89, 99 }, 1)] + [InlineData(new[] { 1, 2, 3 }, -30)] + public static void ImmutableArrayCustomException(int[] items, int maximumLength) + { + var immutableArray = items.ToImmutableArray(); + + Action act = () => immutableArray.MustHaveMaximumLength( + maximumLength, + (array, length) => new ($"Custom exception for array with length {array.Length} and max {length}") + ); + + act.Should().Throw() + .WithMessage($"Custom exception for array with length {immutableArray.Length} and max {maximumLength}"); + } + + [Fact] + public static void ImmutableArrayNoCustomExceptionThrown() + { + var immutableArray = new[] { "Foo", "Bar" }.ToImmutableArray(); + var result = immutableArray.MustHaveMaximumLength(2, (_, _) => new ()); + result.Should().Equal(immutableArray); + } + + [Fact] + public static void ImmutableArrayCustomMessage() + { + var immutableArray = new[] { 1, 2, 3 }.ToImmutableArray(); + + Test.CustomMessage( + message => immutableArray.MustHaveMaximumLength(2, message: message) + ); + } + + [Fact] + public static void ImmutableArrayCallerArgumentExpression() + { + var myImmutableArray = new[] { 1, 2, 3 }.ToImmutableArray(); + + var act = () => myImmutableArray.MustHaveMaximumLength(2); + + act.Should().Throw() + .WithParameterName(nameof(myImmutableArray)); + } +} diff --git a/Code/Light.GuardClauses/Check.MustHaveMaximumLength.cs b/Code/Light.GuardClauses/Check.MustHaveMaximumLength.cs new file mode 100644 index 0000000..1fa8227 --- /dev/null +++ b/Code/Light.GuardClauses/Check.MustHaveMaximumLength.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Immutable; +using System.Runtime.CompilerServices; +using Light.GuardClauses.ExceptionFactory; +using Light.GuardClauses.Exceptions; + +namespace Light.GuardClauses; + +public static partial class Check +{ + /// + /// Ensures that the has at most the specified length, or otherwise throws an . + /// + /// The to be checked. + /// The maximum length the should have. + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when has more than the specified length. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ImmutableArray MustHaveMaximumLength( + this ImmutableArray parameter, + int length, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (parameter.IsDefault && length < 0 || parameter.Length > length) + { + Throw.InvalidMaximumCollectionCount(parameter, length, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the has at most the specified length, or otherwise throws your custom exception. + /// + /// The to be checked. + /// The maximum length the should have. + /// The delegate that creates your custom exception. and are passed to this delegate. + /// Your custom exception thrown when has more than the specified length. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ImmutableArray MustHaveMaximumLength( + this ImmutableArray parameter, + int length, + Func, int, Exception> exceptionFactory + ) + { + if (parameter.IsDefault && length < 0 || parameter.Length > length) + { + Throw.CustomException(exceptionFactory, parameter, length); + } + + return parameter; + } +} From ef2b78edf0fd38508a5af462a6f7dcf18fb4eab3 Mon Sep 17 00:00:00 2001 From: Kenny Pflug Date: Mon, 21 Jul 2025 07:24:33 +0200 Subject: [PATCH 3/5] fix: add explicit IsDefault check in MustHaveLengthIn for immutable arrays Signed-off-by: Kenny Pflug --- .../MustHaveLengthInTests.cs | 43 +++++++++++++++++++ .../Check.MustHaveLengthIn.cs | 6 ++- .../Throw.ImmutableArrayLengthNotInRange.cs | 2 +- 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/Code/Light.GuardClauses.Tests/CollectionAssertions/MustHaveLengthInTests.cs b/Code/Light.GuardClauses.Tests/CollectionAssertions/MustHaveLengthInTests.cs index 2e2a984..4c1e15e 100644 --- a/Code/Light.GuardClauses.Tests/CollectionAssertions/MustHaveLengthInTests.cs +++ b/Code/Light.GuardClauses.Tests/CollectionAssertions/MustHaveLengthInTests.cs @@ -68,4 +68,47 @@ public static void CallerArgumentExpression() act.Should().Throw() .WithParameterName(nameof(testArray)); } + + [Fact] + public static void DefaultImmutableArrayInRange() + { + var defaultArray = default(ImmutableArray); + + var result = defaultArray.MustHaveLengthIn(Range.FromInclusive(0).ToInclusive(5)); + + result.IsDefault.Should().BeTrue(); + } + + [Fact] + public static void DefaultImmutableArrayNotInRange() + { + var defaultArray = default(ImmutableArray); + + var act = () => defaultArray.MustHaveLengthIn(Range.FromInclusive(1).ToInclusive(5), nameof(defaultArray)); + + act.Should().Throw() + .And.Message.Should().Contain("must have its length in between 1 (inclusive) and 5 (inclusive)") + .And.Contain("has no length because it is the default instance"); + } + + [Fact] + public static void DefaultImmutableArrayCustomException() + { + var defaultArray = default(ImmutableArray); + + Test.CustomException( + defaultArray, + Range.FromInclusive(1).ToInclusive(5), + (array, r, exceptionFactory) => array.MustHaveLengthIn(r, exceptionFactory) + ); + } + + [Fact] + public static void DefaultImmutableArrayCustomMessage() => + Test.CustomMessage( + message => default(ImmutableArray).MustHaveLengthIn( + Range.FromInclusive(1).ToInclusive(5), + message: message + ) + ); } diff --git a/Code/Light.GuardClauses/Check.MustHaveLengthIn.cs b/Code/Light.GuardClauses/Check.MustHaveLengthIn.cs index 5974e71..f0cb16e 100644 --- a/Code/Light.GuardClauses/Check.MustHaveLengthIn.cs +++ b/Code/Light.GuardClauses/Check.MustHaveLengthIn.cs @@ -75,7 +75,8 @@ public static ImmutableArray MustHaveLengthIn( string? message = null ) { - if (!range.IsValueWithinRange(parameter.Length)) + var length = parameter.IsDefault ? 0 : parameter.Length; + if (!range.IsValueWithinRange(length)) { Throw.ImmutableArrayLengthNotInRange(parameter, range, parameterName, message); } @@ -98,7 +99,8 @@ public static ImmutableArray MustHaveLengthIn( Func, Range, Exception> exceptionFactory ) { - if (!range.IsValueWithinRange(parameter.Length)) + var length = parameter.IsDefault ? 0 : parameter.Length; + if (!range.IsValueWithinRange(length)) { Throw.CustomException(exceptionFactory, parameter, range); } diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.ImmutableArrayLengthNotInRange.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.ImmutableArrayLengthNotInRange.cs index 2bcf749..c4643c1 100644 --- a/Code/Light.GuardClauses/ExceptionFactory/Throw.ImmutableArrayLengthNotInRange.cs +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.ImmutableArrayLengthNotInRange.cs @@ -23,6 +23,6 @@ public static void ImmutableArrayLengthNotInRange( throw new ArgumentOutOfRangeException( parameterName, message ?? - $"{parameterName ?? "The immutable array"} must have its length in between {range.CreateRangeDescriptionText("and")}, but it actually has length {parameter.Length}." + $"{parameterName ?? "The immutable array"} must have its length in between {range.CreateRangeDescriptionText("and")}, but it actually {(parameter.IsDefault ? "has no length because it is the default instance" : $"has length {parameter.Length}")}." ); } From f4c6da1cca6074ba9f88a4223a37ba6281c3f06c Mon Sep 17 00:00:00 2001 From: Kenny Pflug Date: Mon, 21 Jul 2025 07:59:28 +0200 Subject: [PATCH 4/5] fix: add IsDefault check into MustNotContain for ImmutableArray Signed-off-by: Kenny Pflug --- .../MustNotContainTests.cs | 47 +++++++++++++++++++ .../Check.MustNotContain.cs | 10 +++- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/Code/Light.GuardClauses.Tests/CollectionAssertions/MustNotContainTests.cs b/Code/Light.GuardClauses.Tests/CollectionAssertions/MustNotContainTests.cs index a4c49a4..3f4b817 100644 --- a/Code/Light.GuardClauses.Tests/CollectionAssertions/MustNotContainTests.cs +++ b/Code/Light.GuardClauses.Tests/CollectionAssertions/MustNotContainTests.cs @@ -140,4 +140,51 @@ public static void ImmutableArrayCallerArgumentExpression() act.Should().Throw() .WithParameterName(nameof(array)); } + + [Fact] + public static void ImmutableArrayDefaultInstanceDoesNotContainItem() + { + var defaultArray = default(ImmutableArray); + + // Default instance should not throw for any item since it cannot contain anything + var result = defaultArray.MustNotContain(42); + + result.IsDefault.Should().BeTrue(); + } + + [Fact] + public static void ImmutableArrayDefaultInstanceCustomException() + { + var defaultArray = default(ImmutableArray); + + // Default instance should not throw even with custom exception factory + var result = defaultArray.MustNotContain( + "test", + (_, _) => new InvalidOperationException("Should not be called") + ); + + result.IsDefault.Should().BeTrue(); + } + + [Fact] + public static void ImmutableArrayDefaultInstanceCustomMessage() + { + var defaultArray = default(ImmutableArray); + + // Default instance should not throw even with custom message + var result = defaultArray.MustNotContain(new object(), message: "Custom message"); + + result.IsDefault.Should().BeTrue(); + } + + [Fact] + public static void ImmutableArrayDefaultInstanceCallerArgumentExpression() + { + var defaultArray = default(ImmutableArray); + + // Default instance should not throw, so no exception to check parameter name + var result = defaultArray.MustNotContain('x'); + + result.IsDefault.Should().BeTrue(); + } } \ No newline at end of file diff --git a/Code/Light.GuardClauses/Check.MustNotContain.cs b/Code/Light.GuardClauses/Check.MustNotContain.cs index 4d5b2dc..150efb1 100644 --- a/Code/Light.GuardClauses/Check.MustNotContain.cs +++ b/Code/Light.GuardClauses/Check.MustNotContain.cs @@ -207,6 +207,9 @@ public static string MustNotContain( /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). /// Thrown when contains . + /// + /// The default instance of cannot contain any items, so this method will not throw for default instances. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ImmutableArray MustNotContain( this ImmutableArray parameter, @@ -215,7 +218,7 @@ public static ImmutableArray MustNotContain( string? message = null ) { - if (parameter.Contains(item)) + if (!parameter.IsDefault && parameter.Contains(item)) { Throw.ExistingItem(parameter, item, parameterName, message); } @@ -230,6 +233,9 @@ public static ImmutableArray MustNotContain( /// The item that must not be part of the . /// The delegate that creates your custom exception. and are passed to this delegate. /// Your custom exception thrown when contains . + /// + /// The default instance of cannot contain any items, so this method will not throw for default instances. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] [ContractAnnotation("exceptionFactory:null => halt")] public static ImmutableArray MustNotContain( @@ -238,7 +244,7 @@ public static ImmutableArray MustNotContain( Func, T, Exception> exceptionFactory ) { - if (parameter.Contains(item)) + if (!parameter.IsDefault && parameter.Contains(item)) { Throw.CustomException(exceptionFactory, parameter, item); } From b558999eb107a966eebde88040f28821ea3af7cf Mon Sep 17 00:00:00 2001 From: Kenny Pflug Date: Mon, 21 Jul 2025 08:30:54 +0200 Subject: [PATCH 5/5] fix: MustHaveMaximumLength for ImmutableArray if block is now correct, adjusted default exception message Signed-off-by: Kenny Pflug --- .../MustHaveMaximumLengthTests.cs | 52 ++++++++++++++++++- .../Check.MustHaveMaximumLength.cs | 10 ++-- ...hrow.InvalidMaximumImmutableArrayLength.cs | 30 +++++++++++ 3 files changed, 88 insertions(+), 4 deletions(-) create mode 100644 Code/Light.GuardClauses/ExceptionFactory/Throw.InvalidMaximumImmutableArrayLength.cs diff --git a/Code/Light.GuardClauses.Tests/CollectionAssertions/MustHaveMaximumLengthTests.cs b/Code/Light.GuardClauses.Tests/CollectionAssertions/MustHaveMaximumLengthTests.cs index f530fdf..7401eb1 100644 --- a/Code/Light.GuardClauses.Tests/CollectionAssertions/MustHaveMaximumLengthTests.cs +++ b/Code/Light.GuardClauses.Tests/CollectionAssertions/MustHaveMaximumLengthTests.cs @@ -19,7 +19,7 @@ public static void ImmutableArrayMoreItems(int[] items, int length) var assertion = act.Should().Throw().Which; assertion.Message.Should().Contain( - $"{nameof(immutableArray)} must have at most count {length}, but it actually has count {immutableArray.Length}." + $"{nameof(immutableArray)} must have at most a length of {length}, but it actually has a length of {immutableArray.Length}." ); assertion.ParamName.Should().BeSameAs(nameof(immutableArray)); } @@ -87,4 +87,54 @@ public static void ImmutableArrayCallerArgumentExpression() act.Should().Throw() .WithParameterName(nameof(myImmutableArray)); } + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(5)] + public static void + DefaultImmutableArrayInstanceShouldNotThrowWhenLengthIsGreaterThanOrEqualToZero(int validLength) => + default(ImmutableArray).MustHaveMaximumLength(validLength).IsDefault.Should().BeTrue(); + + [Theory] + [InlineData(-1)] + [InlineData(-5)] + [InlineData(-12)] + public static void DefaultImmutableArrayInstanceShouldNotThrowWhenLengthIsNegative(int negativeLength) + { + var act = () => default(ImmutableArray).MustHaveMaximumLength(negativeLength); + + act.Should().Throw() + .WithParameterName("default(ImmutableArray)") + .WithMessage( + $"default(ImmutableArray) must have at most a length of {negativeLength}, but it actually has no length because it is the default instance.*" + ); + } + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(5)] + public static void DefaultImmutableArrayInstanceCustomExceptionShouldNotThrow(int validLength) + { + var result = default(ImmutableArray).MustHaveMaximumLength(validLength, (_, _) => new Exception()); + result.IsDefault.Should().BeTrue(); + } + + [Theory] + [InlineData(-1)] + [InlineData(-5)] + [InlineData(-12)] + public static void DefaultImmutableArrayInstanceCustomExceptionShouldThrow(int negativeLength) + { + var act = () => default(ImmutableArray).MustHaveMaximumLength( + negativeLength, + (array, length) => new ArgumentException( + $"Custom: Array length {(array.IsDefault ? 0 : array.Length)} exceeds maximum {length}" + ) + ); + + act.Should().Throw() + .WithMessage("Custom: Array length 0 exceeds maximum *"); + } } diff --git a/Code/Light.GuardClauses/Check.MustHaveMaximumLength.cs b/Code/Light.GuardClauses/Check.MustHaveMaximumLength.cs index 1fa8227..7700ddd 100644 --- a/Code/Light.GuardClauses/Check.MustHaveMaximumLength.cs +++ b/Code/Light.GuardClauses/Check.MustHaveMaximumLength.cs @@ -16,6 +16,7 @@ public static partial class Check /// The name of the parameter (optional). /// The message that will be passed to the resulting exception (optional). /// Thrown when has more than the specified length. + /// The default instance of will be treated as having length 0. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ImmutableArray MustHaveMaximumLength( this ImmutableArray parameter, @@ -24,9 +25,10 @@ public static ImmutableArray MustHaveMaximumLength( string? message = null ) { - if (parameter.IsDefault && length < 0 || parameter.Length > length) + var parameterLength = parameter.IsDefault ? 0 : parameter.Length; + if (parameterLength > length) { - Throw.InvalidMaximumCollectionCount(parameter, length, parameterName, message); + Throw.InvalidMaximumImmutableArrayLength(parameter, length, parameterName, message); } return parameter; @@ -39,6 +41,7 @@ public static ImmutableArray MustHaveMaximumLength( /// The maximum length the should have. /// The delegate that creates your custom exception. and are passed to this delegate. /// Your custom exception thrown when has more than the specified length. + /// The default instance of will be treated as having length 0. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ImmutableArray MustHaveMaximumLength( this ImmutableArray parameter, @@ -46,7 +49,8 @@ public static ImmutableArray MustHaveMaximumLength( Func, int, Exception> exceptionFactory ) { - if (parameter.IsDefault && length < 0 || parameter.Length > length) + var parameterLength = parameter.IsDefault ? 0 : parameter.Length; + if (parameterLength > length) { Throw.CustomException(exceptionFactory, parameter, length); } diff --git a/Code/Light.GuardClauses/ExceptionFactory/Throw.InvalidMaximumImmutableArrayLength.cs b/Code/Light.GuardClauses/ExceptionFactory/Throw.InvalidMaximumImmutableArrayLength.cs new file mode 100644 index 0000000..467f9b2 --- /dev/null +++ b/Code/Light.GuardClauses/ExceptionFactory/Throw.InvalidMaximumImmutableArrayLength.cs @@ -0,0 +1,30 @@ +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using Light.GuardClauses.Exceptions; + +namespace Light.GuardClauses.ExceptionFactory; + +public static partial class Throw +{ + /// + /// Throws the default indicating that an has more than a + /// maximum number of items, using the optional parameter name and message. + /// + [ContractAnnotation("=> halt")] + [DoesNotReturn] + public static void InvalidMaximumImmutableArrayLength( + ImmutableArray parameter, + int length, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + throw new InvalidCollectionCountException( + parameterName, + message ?? + $"{parameterName ?? "The immutable array"} must have at most a length of {length}, but it actually {(parameter.IsDefault ? "has no length because it is the default instance" : $"has a length of {parameter.Length}")}." + ); + } +}