From 9c578e88f68d47a2e8d1c83fde43be76f0b440de Mon Sep 17 00:00:00 2001 From: Kenny Pflug Date: Sun, 20 Jul 2025 21:03:16 +0200 Subject: [PATCH 1/2] chore: add plan for issue 116 --- ...16-must-not-contain-for-immutable-array.md | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 Code/Plans/issue-116-must-not-contain-for-immutable-array.md diff --git a/Code/Plans/issue-116-must-not-contain-for-immutable-array.md b/Code/Plans/issue-116-must-not-contain-for-immutable-array.md new file mode 100644 index 0000000..81ce6f3 --- /dev/null +++ b/Code/Plans/issue-116-must-not-contain-for-immutable-array.md @@ -0,0 +1,20 @@ +# Issue 116 - MustNotContain 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 `MustNotContain` assertion for `ImmutableArray`. + +## Tasks for this issue + +- [ ] The production code should be placed in the Light.GuardClauses project. The file `Check.MustNotContain.cs` already exists in the root folder of the project - extend this existing file. +- [ ] In this file, create two extension method overloads called `MustNotContain` 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.ExistingItem` method which is located in `ExceptionFactory/Throw.ExistingItem.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 unwanted item. +- [ ] Create unit tests for both overloads. The corresponding tests should be placed in Light.GuardClauses.Tests project, in the existing file 'CollectionAssertions/MustNotContainTests.cs'. 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. +- The assertion should verify that the `ImmutableArray` does not contain the specified item. +- If you have any questions or suggestions, please ask me about them. From ac60b22f35e5fe66081989de577b02e5de710c78 Mon Sep 17 00:00:00 2001 From: Kenny Pflug Date: Sun, 20 Jul 2025 21:46:31 +0200 Subject: [PATCH 2/2] feat: add MustNotContain for ImmutableArray Signed-off-by: Kenny Pflug --- .../MustNotContainTests.cs | 67 ++++++++++++++++++- .../Check.MustNotContain.cs | 48 +++++++++++++ 2 files changed, 114 insertions(+), 1 deletion(-) diff --git a/Code/Light.GuardClauses.Tests/CollectionAssertions/MustNotContainTests.cs b/Code/Light.GuardClauses.Tests/CollectionAssertions/MustNotContainTests.cs index 70fe7ad..a4c49a4 100644 --- a/Code/Light.GuardClauses.Tests/CollectionAssertions/MustNotContainTests.cs +++ b/Code/Light.GuardClauses.Tests/CollectionAssertions/MustNotContainTests.cs @@ -1,5 +1,6 @@ -using System; +using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Collections.ObjectModel; using FluentAssertions; using Light.GuardClauses.Exceptions; @@ -75,4 +76,68 @@ public static void CallerArgumentExpression() act.Should().Throw() .WithParameterName(nameof(array)); } + + [Theory] + [InlineData(new[] { "Foo", "Bar" }, "Foo")] + [InlineData(new[] { "Baz", "Qux", "Quux" }, "Qux")] + [InlineData(new[] { "Corge", "Grault", null }, null)] + public static void ImmutableArrayItemExists(string[] items, string item) + { + var array = ImmutableArray.Create(items); + + Action act = () => array.MustNotContain(item, nameof(array)); + + var assertions = act.Should().Throw().Which; + assertions.Message.Should() + .Contain($"{nameof(array)} must not contain {item.ToStringOrNull()}, but it actually does."); + assertions.ParamName.Should().BeSameAs(nameof(array)); + } + + [Theory] + [InlineData(new[] { 100, 101, 102 }, 42)] + [InlineData(new[] { 11 }, -5000)] + public static void ImmutableArrayItemExistsNot(int[] items, int item) + { + var array = ImmutableArray.Create(items); + array.MustNotContain(item).Should().Equal(array); + } + + [Fact] + public static void ImmutableArrayEmptyDoesNotContainItem() + { + var emptyArray = ImmutableArray.Empty; + emptyArray.MustNotContain(42).Should().Equal(emptyArray); + } + + [Fact] + public static void ImmutableArrayCustomException() => + Test.CustomException( + ImmutableArray.Create("Foo"), + "Foo", + (array, value, exceptionFactory) => array.MustNotContain(value, exceptionFactory) + ); + + [Fact] + public static void ImmutableArrayNoCustomExceptionThrown() + { + var array = ImmutableArray.Create(1, 2); + array.MustNotContain(3, (_, _) => new ()).Should().Equal(array); + } + + [Fact] + public static void ImmutableArrayCustomMessage() => + Test.CustomMessage( + message => ImmutableArray.Create(42).MustNotContain(42, message: message) + ); + + [Fact] + public static void ImmutableArrayCallerArgumentExpression() + { + var array = ImmutableArray.Create(1, 2, 3); + + Action act = () => array.MustNotContain(3); + + act.Should().Throw() + .WithParameterName(nameof(array)); + } } \ No newline at end of file diff --git a/Code/Light.GuardClauses/Check.MustNotContain.cs b/Code/Light.GuardClauses/Check.MustNotContain.cs index ddadc32..4d5b2dc 100644 --- a/Code/Light.GuardClauses/Check.MustNotContain.cs +++ b/Code/Light.GuardClauses/Check.MustNotContain.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Runtime.CompilerServices; using JetBrains.Annotations; @@ -197,4 +198,51 @@ public static string MustNotContain( return parameter; } + + /// + /// Ensures that the does not contain the specified item, or otherwise throws an . + /// + /// The to be checked. + /// The item that must not be part of the . + /// The name of the parameter (optional). + /// The message that will be passed to the resulting exception (optional). + /// Thrown when contains . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ImmutableArray MustNotContain( + this ImmutableArray parameter, + T item, + [CallerArgumentExpression("parameter")] string? parameterName = null, + string? message = null + ) + { + if (parameter.Contains(item)) + { + Throw.ExistingItem(parameter, item, parameterName, message); + } + + return parameter; + } + + /// + /// Ensures that the does not contain the specified item, or otherwise throws your custom exception. + /// + /// The to be checked. + /// 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 . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("exceptionFactory:null => halt")] + public static ImmutableArray MustNotContain( + this ImmutableArray parameter, + T item, + Func, T, Exception> exceptionFactory + ) + { + if (parameter.Contains(item)) + { + Throw.CustomException(exceptionFactory, parameter, item); + } + + return parameter; + } }