diff --git a/CSharpEssentials.Results/Extensions/ResultExtensions.cs b/CSharpEssentials.Results/Extensions/ResultExtensions.cs
index e60e8af..3164971 100644
--- a/CSharpEssentials.Results/Extensions/ResultExtensions.cs
+++ b/CSharpEssentials.Results/Extensions/ResultExtensions.cs
@@ -17,6 +17,13 @@ public static partial class ResultExtensions
///
public static Result ToResult(this IEnumerable errors) => errors.ToArray();
+ ///
+ /// Converts an array of errors to a result directly without copying.
+ ///
+ ///
+ ///
+ public static Result ToResult(this Error[] errors) => Result.Failure(errors);
+
///
/// Converts a value to a result.
///
@@ -40,4 +47,12 @@ public static partial class ResultExtensions
///
///
public static Result ToResult(this IEnumerable errors) => errors.ToArray();
+
+ ///
+ /// Converts an array of errors to a result directly without copying.
+ ///
+ ///
+ ///
+ ///
+ public static Result ToResult(this Error[] errors) => Result.Failure(errors);
}
diff --git a/CSharpEssentials.Results/Modules/Result.Bind.cs b/CSharpEssentials.Results/Modules/Result.Bind.cs
index 15ee260..d60856e 100644
--- a/CSharpEssentials.Results/Modules/Result.Bind.cs
+++ b/CSharpEssentials.Results/Modules/Result.Bind.cs
@@ -13,7 +13,7 @@ public readonly partial record struct Result
public Result Bind(Func> func)
{
if (IsFailure)
- return Errors;
+ return Result.Failure(_errors);
return func();
}
@@ -25,7 +25,7 @@ public Result Bind(Func> func)
public Result Bind(Func func)
{
if (IsFailure)
- return Errors;
+ return this;
return func();
}
@@ -38,7 +38,7 @@ public Result Bind(Func func)
public Task> Bind(Func>> func)
{
if (IsFailure)
- return Errors.ToResult().AsTask();
+ return Result.Failure(_errors).AsTask();
return func();
}
@@ -50,7 +50,7 @@ public Task> Bind(Func>> func)
public Task Bind(Func> func)
{
if (IsFailure)
- return ((Result)Errors).AsTask();
+ return this.AsTask();
return func();
}
@@ -63,7 +63,7 @@ public Task Bind(Func> func)
public ValueTask> Bind(Func>> valueTask)
{
if (IsFailure)
- return Failure(Errors).AsValueTask();
+ return Result.Failure(_errors).AsValueTask();
return valueTask();
}
@@ -75,7 +75,7 @@ public ValueTask> Bind(Func>> valueTas
public ValueTask Bind(Func> valueTask)
{
if (IsFailure)
- return Failure(Errors).AsValueTask();
+ return this.AsValueTask();
return valueTask();
}
}
diff --git a/CSharpEssentials.Results/Modules/Result.Map.cs b/CSharpEssentials.Results/Modules/Result.Map.cs
index dace847..494b50e 100644
--- a/CSharpEssentials.Results/Modules/Result.Map.cs
+++ b/CSharpEssentials.Results/Modules/Result.Map.cs
@@ -12,7 +12,7 @@ public readonly partial record struct Result
public Result Map(Func map)
{
if (IsFailure)
- return Errors;
+ return Result.Failure(_errors);
return map();
}
@@ -25,7 +25,7 @@ public Result Map(Func map)
public Result Map(Func> map)
{
if (IsFailure)
- return Errors;
+ return Result.Failure(_errors);
return map();
}
}
@@ -58,7 +58,7 @@ public static async Task> MapAsync(this Task task, Fu
{
Result result = await task.WithCancellation(cancellationToken);
if (result.IsFailure)
- return result.Errors;
+ return Result.Failure(result.ErrorsOrEmptyArray);
return await map().WithCancellation(cancellationToken);
}
diff --git a/CSharpEssentials.Results/Modules/Result.MapError.cs b/CSharpEssentials.Results/Modules/Result.MapError.cs
index df4981e..5334b12 100644
--- a/CSharpEssentials.Results/Modules/Result.MapError.cs
+++ b/CSharpEssentials.Results/Modules/Result.MapError.cs
@@ -8,13 +8,13 @@ public Result MapError(Func errorMapper)
{
if (IsSuccess)
return this;
- return errorMapper(Errors);
+ return Result.Failure(errorMapper(Errors));
}
public Result MapError(Func errorMapper)
{
if (IsSuccess)
return this;
- return errorMapper(FirstError);
+ return Result.Failure(errorMapper(FirstError));
}
}
diff --git a/CSharpEssentials.Results/Modules/ResultT.Bind.cs b/CSharpEssentials.Results/Modules/ResultT.Bind.cs
index 498c605..da4854f 100644
--- a/CSharpEssentials.Results/Modules/ResultT.Bind.cs
+++ b/CSharpEssentials.Results/Modules/ResultT.Bind.cs
@@ -13,7 +13,7 @@ public readonly partial record struct Result
public Result Bind(Func> func)
{
if (IsFailure)
- return Errors;
+ return Result.Failure(_errors);
return func(Value);
}
@@ -25,7 +25,7 @@ public Result Bind(Func> func)
public Result Bind(Func func)
{
if (IsFailure)
- return Errors;
+ return Result.Failure(_errors);
return func(Value);
}
@@ -38,7 +38,7 @@ public Result Bind(Func func)
public Task> Bind(Func>> func)
{
if (IsFailure)
- return Errors.ToResult().AsTask();
+ return Result.Failure(_errors).AsTask();
return func(Value);
}
@@ -50,7 +50,7 @@ public Task> Bind(Func>> func)
public Task Bind(Func> func)
{
if (IsFailure)
- return ((Result)Errors).AsTask();
+ return Result.Failure(_errors).AsTask();
return func(Value);
}
@@ -63,7 +63,7 @@ public Task Bind(Func> func)
public ValueTask> Bind(Func>> valueTask)
{
if (IsFailure)
- return Errors.ToResult().AsValueTask();
+ return Result.Failure(_errors).AsValueTask();
return valueTask(Value);
}
@@ -76,7 +76,7 @@ public ValueTask> Bind(Func>>
public ValueTask Bind(Func> valueTask)
{
if (IsFailure)
- return ((Result)Errors).AsValueTask();
+ return Result.Failure(_errors).AsValueTask();
return valueTask(Value);
}
}
diff --git a/CSharpEssentials.Results/Modules/ResultT.Else.cs b/CSharpEssentials.Results/Modules/ResultT.Else.cs
index a20ad26..0cbcb7b 100644
--- a/CSharpEssentials.Results/Modules/ResultT.Else.cs
+++ b/CSharpEssentials.Results/Modules/ResultT.Else.cs
@@ -14,7 +14,7 @@ public Result Else(Func onFailure)
{
if (IsSuccess)
return Value;
- return onFailure(Errors).ToResult();
+ return Result.Failure(onFailure(Errors));
}
///
@@ -26,7 +26,7 @@ public Result Else(Func onFailure)
{
if (IsSuccess)
return Value;
- return onFailure(Errors).ToResult();
+ return Result.Failure(onFailure(Errors));
}
///
@@ -38,7 +38,7 @@ public Result Else(Error error)
{
if (IsSuccess)
return Value;
- return error.ToResult();
+ return Result.Failure(error);
}
///
@@ -90,7 +90,7 @@ public async Task> ElseAsync(Func> onFailure
if (IsSuccess)
return Value;
Error result = await onFailure(Errors).WithCancellation(cancellationToken);
- return result.ToResult();
+ return Result.Failure(result);
}
///
@@ -104,7 +104,7 @@ public async Task> ElseAsync(Func> onFailu
if (IsSuccess)
return Value;
Error[] result = await onFailure(Errors).WithCancellation(cancellationToken);
- return result.ToResult();
+ return Result.Failure(result);
}
///
@@ -118,7 +118,7 @@ public async Task> ElseAsync(Task error, CancellationToken
if (IsSuccess)
return Value;
Error result = await error.WithCancellation(cancellationToken);
- return result.ToResult();
+ return Result.Failure(result);
}
///
diff --git a/CSharpEssentials.Results/Modules/ResultT.Ensure.cs b/CSharpEssentials.Results/Modules/ResultT.Ensure.cs
index c873a99..72671c1 100644
--- a/CSharpEssentials.Results/Modules/ResultT.Ensure.cs
+++ b/CSharpEssentials.Results/Modules/ResultT.Ensure.cs
@@ -9,42 +9,42 @@ public Result Ensure(Func predicate, Error error)
{
if (IsFailure)
return this;
- return predicate(Value) ? this : error.ToResult();
+ return predicate(Value) ? this : Result.Failure(error);
}
public Result Ensure(Func predicate, Func errorFactory)
{
if (IsFailure)
return this;
- return predicate(Value) ? this : errorFactory(Value).ToResult();
+ return predicate(Value) ? this : Result.Failure(errorFactory(Value));
}
public Result EnsureNotNull(Error error)
{
if (IsFailure)
return this;
- return Value is not null ? this : error.ToResult();
+ return Value is not null ? this : Result.Failure(error);
}
public Result EnsureNotNull(Func errorFactory)
{
if (IsFailure)
return this;
- return Value is not null ? this : errorFactory(Value).ToResult();
+ return Value is not null ? this : Result.Failure(errorFactory(Value));
}
public async Task> EnsureAsync(Func> predicate, Error error, CancellationToken cancellationToken = default)
{
if (IsFailure)
return this;
- return await predicate(Value).WithCancellation(cancellationToken) ? this : error.ToResult();
+ return await predicate(Value).WithCancellation(cancellationToken) ? this : Result.Failure(error);
}
public async Task> EnsureAsync(Func> predicate, Func errorFactory, CancellationToken cancellationToken = default)
{
if (IsFailure)
return this;
- return await predicate(Value).WithCancellation(cancellationToken) ? this : errorFactory(Value).ToResult();
+ return await predicate(Value).WithCancellation(cancellationToken) ? this : Result.Failure(errorFactory(Value));
}
}
diff --git a/CSharpEssentials.Results/Modules/ResultT.Map.cs b/CSharpEssentials.Results/Modules/ResultT.Map.cs
index cdf59fc..122f577 100644
--- a/CSharpEssentials.Results/Modules/ResultT.Map.cs
+++ b/CSharpEssentials.Results/Modules/ResultT.Map.cs
@@ -12,7 +12,7 @@ public readonly partial record struct Result
public Result Map(Func map)
{
if (IsFailure)
- return Errors;
+ return Result.Failure(_errors);
return map(Value);
}
@@ -25,7 +25,7 @@ public Result Map(Func map)
public Result Map(Func> map)
{
if (IsFailure)
- return Errors;
+ return Result.Failure(_errors);
return map(Value);
}
}
@@ -60,7 +60,7 @@ public static async Task> MapAsync(this Task result = await task.WithCancellation(cancellationToken);
if (result.IsFailure)
- return result.Errors;
+ return Result.Failure(result.ErrorsOrEmptyArray);
return await map(result.Value).WithCancellation(cancellationToken);
}
diff --git a/CSharpEssentials.Results/Modules/ResultT.MapError.cs b/CSharpEssentials.Results/Modules/ResultT.MapError.cs
index eab2730..a523389 100644
--- a/CSharpEssentials.Results/Modules/ResultT.MapError.cs
+++ b/CSharpEssentials.Results/Modules/ResultT.MapError.cs
@@ -8,13 +8,13 @@ public Result MapError(Func errorMapper)
{
if (IsSuccess)
return this;
- return errorMapper(Errors).ToResult();
+ return Result.Failure(errorMapper(Errors));
}
public Result MapError(Func errorMapper)
{
if (IsSuccess)
return this;
- return errorMapper(FirstError).ToResult();
+ return Result.Failure(errorMapper(FirstError));
}
}
diff --git a/CSharpEssentials.Results/Modules/ResultT.Select.cs b/CSharpEssentials.Results/Modules/ResultT.Select.cs
index 43155e3..9d50115 100644
--- a/CSharpEssentials.Results/Modules/ResultT.Select.cs
+++ b/CSharpEssentials.Results/Modules/ResultT.Select.cs
@@ -96,7 +96,7 @@ public static async Task> SelectAsync(this Task result = await task.WithCancellation(cancellationToken);
if (result.IsFailure)
- return result.Errors;
+ return Result.Failure(result.ErrorsOrEmptyArray);
return await selector(result.Value).WithCancellation(cancellationToken);
}
diff --git a/CSharpEssentials.Results/Modules/ResultT.Then.cs b/CSharpEssentials.Results/Modules/ResultT.Then.cs
index bf07e85..f0c0fe2 100644
--- a/CSharpEssentials.Results/Modules/ResultT.Then.cs
+++ b/CSharpEssentials.Results/Modules/ResultT.Then.cs
@@ -13,7 +13,7 @@ public readonly partial record struct Result
public Result Then(Func> onSuccess)
{
if (IsFailure)
- return Errors.ToResult();
+ return Result.Failure(_errors);
return onSuccess(Value);
}
@@ -25,7 +25,7 @@ public Result Then(Func> onSuccess)
public Result ThenDo(Action action)
{
if (IsFailure)
- return Errors;
+ return this;
action(Value);
return this;
}
@@ -39,7 +39,7 @@ public Result ThenDo(Action action)
public Result Then(Func onSuccess)
{
if (IsFailure)
- return Errors.ToResult();
+ return Result.Failure(_errors);
return onSuccess(Value).ToResult();
}
@@ -53,7 +53,7 @@ public Result Then(Func onSuccess)
public async Task> ThenAsync(Func>> onSuccess, CancellationToken cancellationToken = default)
{
if (IsFailure)
- return Errors.ToResult();
+ return Result.Failure(_errors);
return await onSuccess(Value).WithCancellation(cancellationToken);
}
@@ -66,7 +66,7 @@ public async Task> ThenAsync(Func>> onSucces
public async Task> ThenDoAsync(Func action, CancellationToken cancellationToken = default)
{
if (IsFailure)
- return Errors;
+ return this;
await action(Value).WithCancellation(cancellationToken);
return this;
}
@@ -81,7 +81,7 @@ public async Task> ThenDoAsync(Func action, Cancell
public async Task> ThenAsync(Func> onSuccess, CancellationToken cancellationToken = default)
{
if (IsFailure)
- return Errors.ToResult();
+ return Result.Failure(_errors);
T? result = await onSuccess(Value).WithCancellation(cancellationToken);
return result.ToResult();
}
diff --git a/CSharpEssentials.Results/Modules/ResultT.ThenEnsure.cs b/CSharpEssentials.Results/Modules/ResultT.ThenEnsure.cs
index 9e2104c..86d3094 100644
--- a/CSharpEssentials.Results/Modules/ResultT.ThenEnsure.cs
+++ b/CSharpEssentials.Results/Modules/ResultT.ThenEnsure.cs
@@ -14,7 +14,7 @@ public Result ThenEnsure(Func validator)
if (IsFailure)
return this;
Result result = validator(Value);
- return result.IsSuccess ? this : result.Errors.ToResult();
+ return result.IsSuccess ? this : Result.Failure(result.ErrorsOrEmptyArray);
}
public async Task> ThenEnsureAsync(Func>> validator, CancellationToken cancellationToken = default)
@@ -31,7 +31,7 @@ public async Task> ThenEnsureAsync(Func> val
return this;
cancellationToken.ThrowIfCancellationRequested();
Result result = await validator(Value);
- return result.IsSuccess ? this : result.Errors.ToResult();
+ return result.IsSuccess ? this : Result.Failure(result.ErrorsOrEmptyArray);
}
}
diff --git a/CSharpEssentials.Results/ResultFactory.cs b/CSharpEssentials.Results/ResultFactory.cs
index 8951af5..456d36b 100644
--- a/CSharpEssentials.Results/ResultFactory.cs
+++ b/CSharpEssentials.Results/ResultFactory.cs
@@ -25,6 +25,13 @@ public readonly partial record struct Result
///
public static Result Failure(params IEnumerable errors) => From(errors);
+ ///
+ /// Creates a new failure result directly using the specified errors array without copying.
+ ///
+ ///
+ ///
+ public static Result Failure(Error[] errors) => new(true, ResultLogic.ValidateErrorsArray(errors));
+
///
/// Converts a collection of errors to a result.
///
@@ -49,7 +56,7 @@ public static Result And(params IEnumerable results)
foreach (IResultBase result in results)
{
if (result.IsFailure)
- return result.Errors;
+ return Failure(result.ErrorsOrEmptyArray);
}
return Success();
}
@@ -102,6 +109,13 @@ public static Result Or(params IEnumerable results)
///
public static Result Failure(params IEnumerable errors) => Result.From(errors);
///
+ /// Creates a new failure result directly using the specified errors array without copying.
+ ///
+ ///
+ ///
+ ///
+ public static Result Failure(Error[] errors) => Result.Failure(errors);
+ ///
/// Converts an error to a result.
///
///
@@ -122,7 +136,7 @@ public static Result Or(params IEnumerable results)
#else
public static implicit operator Result(Error error) => new([error]);
#endif
- public static implicit operator Result(Error[] errors) => new(errors);
+ public static implicit operator Result(Error[] errors) => Failure(errors);
public static implicit operator Result(List errors) => new(errors);
public static implicit operator Result(HashSet errors) => new(errors);
public static implicit operator Result(bool isSuccess) => isSuccess ? Success() : Error.False;
diff --git a/CSharpEssentials.Results/ResultLogic.cs b/CSharpEssentials.Results/ResultLogic.cs
index f4fe6f9..0d273fe 100644
--- a/CSharpEssentials.Results/ResultLogic.cs
+++ b/CSharpEssentials.Results/ResultLogic.cs
@@ -4,6 +4,21 @@ namespace CSharpEssentials.ResultPattern;
internal static class ResultLogic
{
+ internal static Error[] ValidateErrorsArray(Error[] errors)
+ {
+#if NET6_0_OR_GREATER
+ ArgumentNullException.ThrowIfNull(errors);
+#else
+ if (errors is null)
+ throw new ArgumentNullException(nameof(errors));
+#endif
+
+ if (errors.Length == 0)
+ throw CreateEmptyErrorArrayException();
+
+ return errors;
+ }
+
internal static bool CheckIfErrorsAreEqual(Error[] left, Error[] right)
{
if (ReferenceEquals(left, right))
diff --git a/CSharpEssentials.Results/ResultTFactory.cs b/CSharpEssentials.Results/ResultTFactory.cs
index c99dd29..0c73a65 100644
--- a/CSharpEssentials.Results/ResultTFactory.cs
+++ b/CSharpEssentials.Results/ResultTFactory.cs
@@ -26,6 +26,13 @@ public readonly partial record struct Result
///
public static Result Failure(params IEnumerable errors) => From(errors);
+ ///
+ /// Creates a new failure result directly using the specified errors array without copying.
+ ///
+ ///
+ ///
+ public static Result Failure(Error[] errors) => new(false, default, ResultLogic.ValidateErrorsArray(errors));
+
///
/// Converts a collection of errors to a result.
///
@@ -96,7 +103,7 @@ public static Result Or(params IEnumerable> results)
#else
public static implicit operator Result(Error error) => new([error]);
#endif
- public static implicit operator Result(Error[] errors) => new(errors);
+ public static implicit operator Result(Error[] errors) => Failure(errors);
public static implicit operator Result(List errors) => new(errors);
public static implicit operator Result(HashSet errors) => new(errors);
diff --git a/CSharpEssentials.Tests/Results/ResultFactoryTests.cs b/CSharpEssentials.Tests/Results/ResultFactoryTests.cs
index 938a485..33e3786 100644
--- a/CSharpEssentials.Tests/Results/ResultFactoryTests.cs
+++ b/CSharpEssentials.Tests/Results/ResultFactoryTests.cs
@@ -33,6 +33,22 @@ public void Failure_WithMultipleErrors_ShouldCreateFailureResult()
result.Errors.Should().HaveCount(2);
}
+ [Fact]
+ public void Failure_WithEmptyErrorsArray_ShouldThrow()
+ {
+ Action act = () => Result.Failure([]);
+
+ act.Should().Throw();
+ }
+
+ [Fact]
+ public void Failure_WithNullErrorsArray_ShouldThrow()
+ {
+ Action act = () => Result.Failure((Error[]?)null!);
+
+ act.Should().Throw();
+ }
+
[Fact]
public void From_WithErrors_ShouldCreateFailureResult()
{
@@ -71,6 +87,22 @@ public void FailureT_WithMultipleErrors_ShouldCreateFailureResult()
result.Errors.Should().HaveCount(2);
}
+ [Fact]
+ public void FailureT_WithEmptyErrorsArray_ShouldThrow()
+ {
+ Action act = () => Result.Failure([]);
+
+ act.Should().Throw();
+ }
+
+ [Fact]
+ public void FailureT_WithNullErrorsArray_ShouldThrow()
+ {
+ Action act = () => Result.Failure((Error[]?)null!);
+
+ act.Should().Throw();
+ }
+
[Fact]
public void FromT_WithError_ShouldCreateFailureResult()
{
@@ -90,4 +122,3 @@ public void FromT_WithErrors_ShouldCreateFailureResult()
result.Errors.Should().Contain(TestData.Errors.Failure);
}
}
-
diff --git a/CSharpEssentials.Tests/Results/ResultZeroAllocationTests.cs b/CSharpEssentials.Tests/Results/ResultZeroAllocationTests.cs
new file mode 100644
index 0000000..6b9b517
--- /dev/null
+++ b/CSharpEssentials.Tests/Results/ResultZeroAllocationTests.cs
@@ -0,0 +1,201 @@
+using Xunit;
+using CSharpEssentials.Errors;
+using CSharpEssentials.ResultPattern;
+
+namespace CSharpEssentials.Tests.Results;
+
+public class ResultZeroAllocationTests
+{
+ [Fact]
+ public void ResultFailure_WithErrorsArray_ShouldNotCopyArray()
+ {
+ // Arrange
+ Error[] errors = [Error.Validation("Code", "Message")];
+
+ // Act
+ Result result = Result.Failure(errors);
+
+ // Assert
+ Assert.True(result.IsFailure);
+ Assert.Same(errors, result.ErrorsOrEmptyArray);
+ }
+
+ [Fact]
+ public void ResultGenericFailure_WithErrorsArray_ShouldNotCopyArray()
+ {
+ // Arrange
+ Error[] errors = [Error.Validation("Code", "Message")];
+
+ // Act
+ Result result = Result.Failure(errors);
+
+ // Assert
+ Assert.True(result.IsFailure);
+ Assert.Same(errors, result.ErrorsOrEmptyArray);
+ }
+
+ [Fact]
+ public void ResultExtensionsToResult_WithErrorsArray_ShouldNotCopyArray()
+ {
+ // Arrange
+ Error[] errors = [Error.Validation("Code", "Message")];
+
+ // Act
+ Result result = errors.ToResult();
+ Result resultGeneric = errors.ToResult();
+
+ // Assert
+ Assert.True(result.IsFailure);
+ Assert.Same(errors, result.ErrorsOrEmptyArray);
+ Assert.True(resultGeneric.IsFailure);
+ Assert.Same(errors, resultGeneric.ErrorsOrEmptyArray);
+ }
+
+ [Fact]
+ public void ResultImplicitConversions_WithErrorsArray_ShouldNotCopyArray()
+ {
+ // Arrange
+ Error[] errors = [Error.Validation("Code", "Message")];
+
+ // Act
+ Result result = errors;
+ Result