Skip to content

Commit b235afb

Browse files
authored
Merge pull request #123 from feO2x/issue-117-must-have-length-for-immutable-array
MustHaveLength for immutable array
2 parents 963b628 + ca43982 commit b235afb

File tree

4 files changed

+197
-0
lines changed

4 files changed

+197
-0
lines changed
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
using System;
2+
using System.Collections.Immutable;
3+
using FluentAssertions;
4+
using Light.GuardClauses.Exceptions;
5+
using Xunit;
6+
7+
namespace Light.GuardClauses.Tests.CollectionAssertions;
8+
9+
public static class MustHaveLengthTests
10+
{
11+
[Theory]
12+
[InlineData(3, 2)]
13+
[InlineData(5, 6)]
14+
[InlineData(0, 1)]
15+
[InlineData(10, 11)]
16+
public static void ImmutableArrayInvalidLength(int arrayLength, int expectedLength)
17+
{
18+
var array = ImmutableArray.CreateRange(new int[arrayLength]);
19+
20+
var act = () => array.MustHaveLength(expectedLength, nameof(array));
21+
22+
act.Should().Throw<InvalidCollectionCountException>()
23+
.And.Message.Should().Contain(
24+
$"{nameof(array)} must have length {expectedLength}, but it actually has length {arrayLength}."
25+
);
26+
}
27+
28+
[Theory]
29+
[InlineData(0)]
30+
[InlineData(1)]
31+
[InlineData(3)]
32+
[InlineData(6)]
33+
public static void ImmutableArrayValidLength(int arrayLength)
34+
{
35+
var array = ImmutableArray.CreateRange(new string[arrayLength]);
36+
37+
var result = array.MustHaveLength(arrayLength);
38+
39+
result.Should().Equal(array);
40+
}
41+
42+
[Fact]
43+
public static void ImmutableArrayCustomException()
44+
{
45+
var exception = new Exception();
46+
var array = ImmutableArray.Create("a", "b", "c");
47+
48+
var act = () => array.MustHaveLength(4, (_, _) => exception);
49+
50+
act.Should().Throw<Exception>().Which.Should().BeSameAs(exception);
51+
}
52+
53+
[Fact]
54+
public static void ImmutableArrayNoCustomException()
55+
{
56+
var array = ImmutableArray.Create(1, 2, 3, 4);
57+
58+
var result = array.MustHaveLength(4, (_, _) => null);
59+
60+
result.Should().Equal(array);
61+
}
62+
63+
[Fact]
64+
public static void ImmutableArrayCustomMessage()
65+
{
66+
var array = ImmutableArray<string>.Empty;
67+
68+
var act = () => array.MustHaveLength(1, message: "Custom error message");
69+
70+
act.Should().Throw<InvalidCollectionCountException>()
71+
.And.Message.Should().Contain("Custom error message");
72+
}
73+
74+
[Fact]
75+
public static void ImmutableArrayCallerArgumentExpression()
76+
{
77+
var myArray = ImmutableArray.Create("foo", "bar");
78+
79+
var act = () => myArray.MustHaveLength(10);
80+
81+
act.Should().Throw<InvalidCollectionCountException>()
82+
.WithParameterName("myArray");
83+
}
84+
85+
[Fact]
86+
public static void ImmutableArrayDefaultArray()
87+
{
88+
var defaultArray = default(ImmutableArray<int>);
89+
90+
var act = () => defaultArray.MustHaveLength(5, nameof(defaultArray));
91+
92+
act.Should().Throw<InvalidCollectionCountException>()
93+
.And.Message.Should().Contain($"{nameof(defaultArray)} must have length 5, but it actually has length 0.");
94+
}
95+
}

Code/Light.GuardClauses/Check.MustHaveLength.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Immutable;
23
using System.Runtime.CompilerServices;
34
using JetBrains.Annotations;
45
using Light.GuardClauses.ExceptionFactory;
@@ -145,4 +146,52 @@ ReadOnlySpanExceptionFactory<T, int> exceptionFactory
145146

146147
return parameter;
147148
}
149+
150+
/// <summary>
151+
/// Ensures that the immutable array has the specified length, or otherwise throws an <see cref="InvalidCollectionCountException" />.
152+
/// </summary>
153+
/// <param name="parameter">The immutable array to be checked.</param>
154+
/// <param name="length">The length that the immutable array must have.</param>
155+
/// <param name="parameterName">The name of the parameter (optional).</param>
156+
/// <param name="message">The message that will be passed to the resulting exception (optional).</param>
157+
/// <exception cref="InvalidCollectionCountException">Thrown when <paramref name="parameter" /> does not have the specified length.</exception>
158+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
159+
public static ImmutableArray<T> MustHaveLength<T>(
160+
this ImmutableArray<T> parameter,
161+
int length,
162+
[CallerArgumentExpression("parameter")] string? parameterName = null,
163+
string? message = null
164+
)
165+
{
166+
var actualLength = parameter.IsDefault ? 0 : parameter.Length;
167+
if (actualLength != length)
168+
{
169+
Throw.InvalidImmutableArrayLength(parameter, length, parameterName, message);
170+
}
171+
172+
return parameter;
173+
}
174+
175+
/// <summary>
176+
/// Ensures that the immutable array has the specified length, or otherwise throws your custom exception.
177+
/// </summary>
178+
/// <param name="parameter">The immutable array to be checked.</param>
179+
/// <param name="length">The length that the immutable array must have.</param>
180+
/// <param name="exceptionFactory">The delegate that creates your custom exception. <paramref name="parameter" /> and <paramref name="length" /> are passed to this delegate.</param>
181+
/// <exception cref="Exception">Your custom exception thrown when <paramref name="parameter" /> does not have the specified length.</exception>
182+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
183+
public static ImmutableArray<T> MustHaveLength<T>(
184+
this ImmutableArray<T> parameter,
185+
int length,
186+
Func<ImmutableArray<T>, int, Exception> exceptionFactory
187+
)
188+
{
189+
var actualLength = parameter.IsDefault ? 0 : parameter.Length;
190+
if (actualLength != length)
191+
{
192+
Throw.CustomException(exceptionFactory, parameter, length);
193+
}
194+
195+
return parameter;
196+
}
148197
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using System.Collections.Immutable;
2+
using System.Diagnostics.CodeAnalysis;
3+
using System.Runtime.CompilerServices;
4+
using JetBrains.Annotations;
5+
using Light.GuardClauses.Exceptions;
6+
7+
namespace Light.GuardClauses.ExceptionFactory;
8+
9+
public static partial class Throw
10+
{
11+
/// <summary>
12+
/// Throws the default <see cref="InvalidCollectionCountException" /> indicating that an immutable array has an invalid length,
13+
/// using the optional parameter name and message.
14+
/// </summary>
15+
[ContractAnnotation("=> halt")]
16+
[DoesNotReturn]
17+
public static void InvalidImmutableArrayLength<T>(
18+
ImmutableArray<T> parameter,
19+
int length,
20+
[CallerArgumentExpression("parameter")] string? parameterName = null,
21+
string? message = null
22+
)
23+
{
24+
var actualLength = parameter.IsDefault ? 0 : parameter.Length;
25+
throw new InvalidCollectionCountException(
26+
parameterName,
27+
message ??
28+
$"{parameterName ?? "The immutable array"} must have length {length}, but it actually has length {actualLength}."
29+
);
30+
}
31+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Issue 117 - MustHaveLength for ImmutableArray
2+
3+
## Context
4+
5+
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 `MustHaveLength` assertion for `ImmutableArray<T>`.
6+
7+
## Tasks for this issue
8+
9+
- [ ] The production code should be placed in the Light.GuardClauses project. The file `Check.MustHaveLength.cs` already exists in the root folder of the project - extend this existing file.
10+
- [ ] In this file, create several extension method overloads called `MustHaveLength` for `ImmutableArray<T>`. It should be placed in the class `Check` which is marked as `partial`.
11+
- [ ] 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.InvalidCollectionCount` method which is located in `ExceptionFactory/Throw.InvalidCollectionCount.cs`.
12+
- [ ] 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 expected length.
13+
- [ ] Use the `Length` property of `ImmutableArray<T>` instead of `Count` for performance and correctness.
14+
- [ ] Create unit tests for both overloads. The corresponding tests should be placed in Light.GuardClauses.Tests project. There is already a file 'StringAssertions/MustHaveLengthTests.cs' but you need to create a new file 'CollectionAssertions/MustHaveLengthTests.cs' for collection-related length tests. Please follow conventions of the existing tests (e.g. use FluentAssertions' `Should()` for assertions).
15+
16+
## Notes
17+
18+
- 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.
19+
- This assertion specifically targets `ImmutableArray<T>` to avoid boxing that would occur with generic `IEnumerable<T>` extensions.
20+
- Use the `Length` property instead of `Count` as this is the appropriate property for `ImmutableArray<T>`.
21+
- The assertion should verify that the `ImmutableArray<T>` has exactly the expected length.
22+
- If you have any questions or suggestions, please ask me about them.

0 commit comments

Comments
 (0)