-
Notifications
You must be signed in to change notification settings - Fork 9
MustHaveMaximumLength for immutable array #126
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
feO2x
merged 5 commits into
dev
from
issue-120-must-have-maximum-length-for-immutable-array
Jul 21, 2025
Merged
Changes from 4 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
9c9fe0f
chore: add plan for issue 120
feO2x 90944bd
feat: add MustHaveMaximumLength for ImmutableArray<T>
feO2x ef2b78e
fix: add explicit IsDefault check in MustHaveLengthIn for immutable a…
feO2x f4c6da1
fix: add IsDefault check into MustNotContain for ImmutableArray<T>
feO2x b558999
fix: MustHaveMaximumLength for ImmutableArray<T> if block is now corr…
feO2x File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
90 changes: 90 additions & 0 deletions
90
Code/Light.GuardClauses.Tests/CollectionAssertions/MustHaveMaximumLengthTests.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<InvalidCollectionCountException>().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<int>.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<Exception>() | ||
| .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<InvalidCollectionCountException>( | ||
| 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<InvalidCollectionCountException>() | ||
| .WithParameterName(nameof(myImmutableArray)); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
| { | ||
| /// <summary> | ||
| /// Ensures that the <see cref="ImmutableArray{T}" /> has at most the specified length, or otherwise throws an <see cref="InvalidCollectionCountException" />. | ||
| /// </summary> | ||
| /// <param name="parameter">The <see cref="ImmutableArray{T}" /> to be checked.</param> | ||
| /// <param name="length">The maximum length the <see cref="ImmutableArray{T}" /> should have.</param> | ||
| /// <param name="parameterName">The name of the parameter (optional).</param> | ||
| /// <param name="message">The message that will be passed to the resulting exception (optional).</param> | ||
| /// <exception cref="InvalidCollectionCountException">Thrown when <paramref name="parameter" /> has more than the specified length.</exception> | ||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
| public static ImmutableArray<T> MustHaveMaximumLength<T>( | ||
| this ImmutableArray<T> 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; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Ensures that the <see cref="ImmutableArray{T}" /> has at most the specified length, or otherwise throws your custom exception. | ||
| /// </summary> | ||
| /// <param name="parameter">The <see cref="ImmutableArray{T}" /> to be checked.</param> | ||
| /// <param name="length">The maximum length the <see cref="ImmutableArray{T}" /> should have.</param> | ||
| /// <param name="exceptionFactory">The delegate that creates your custom exception. <paramref name="parameter" /> and <paramref name="length" /> are passed to this delegate.</param> | ||
| /// <exception cref="Exception">Your custom exception thrown when <paramref name="parameter" /> has more than the specified length.</exception> | ||
| [MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
| public static ImmutableArray<T> MustHaveMaximumLength<T>( | ||
| this ImmutableArray<T> parameter, | ||
| int length, | ||
| Func<ImmutableArray<T>, int, Exception> exceptionFactory | ||
| ) | ||
| { | ||
| if (parameter.IsDefault && length < 0 || parameter.Length > length) | ||
|
feO2x marked this conversation as resolved.
Outdated
|
||
| { | ||
| Throw.CustomException(exceptionFactory, parameter, length); | ||
| } | ||
|
|
||
| return parameter; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
22 changes: 22 additions & 0 deletions
22
Code/Plans/issue-120-must-have-maximum-length-for-immutable-array.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<T>` or `IEnumerable`. However, these assertions would result in `ImmutableArray<T>` 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<T>`. | ||
|
|
||
| ## 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<T>`. 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<T>` instance and the maximum length. | ||
| - [ ] Use the `Length` property of `ImmutableArray<T>` 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<T>` to avoid boxing that would occur with generic `IEnumerable<T>` extensions. | ||
| - Use the `Length` property instead of `Count` as this is the appropriate property for `ImmutableArray<T>`. | ||
| - The assertion should verify that the `ImmutableArray<T>` does not exceed the specified maximum length. | ||
| - If you have any questions or suggestions, please ask me about them. |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The condition logic is incorrect. It should throw when parameter.IsDefault AND length < 0, OR when parameter.Length > length. However, the current condition will throw for default instances when length < 0, but default instances should be allowed for any non-negative maximum length. The condition should be:
if ((parameter.IsDefault && length < 0) || (!parameter.IsDefault && parameter.Length > length))