feat(functional): 增强Result类型功能并添加完整测试覆盖

- 为Result.Failure方法添加参数验证,确保异常和消息参数不为空
- 重构Result<T>内部状态枚举顺序以优化比较逻辑
- 为Result<T>的Map、Bind和MapAsync方法添加参数验证和异常处理
- 在Result<T>比较逻辑中添加类型不可比较时的安全处理
- 添加ResultExtensions.BindAsync扩展方法支持异步绑定操作
- 重写Combine扩展方法中对失败结果的处理逻辑
- 添加完整的Result相关类型单元测试覆盖所有功能场景
- 为Result类型添加详细的XML文档注释和使用示例
This commit is contained in:
GeWuYou 2026-02-25 15:44:06 +08:00 committed by gewuyou
parent 1cb7dfdb14
commit 61349a83ab
6 changed files with 1603 additions and 12 deletions

View File

@ -0,0 +1,496 @@
// Copyright (c) 2025 GeWuYou
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using GFramework.Core.Extensions;
using GFramework.Core.Functional;
using NUnit.Framework;
namespace GFramework.Core.Tests.Extensions;
/// <summary>
/// ResultExtensions 扩展方法测试类
/// </summary>
[TestFixture]
public class ResultExtensionsTests
{
[Test]
public void Combine_Should_Return_Success_List_When_All_Results_Succeed()
{
var results = new[]
{
Result<int>.Succeed(1),
Result<int>.Succeed(2),
Result<int>.Succeed(3)
};
var combined = results.Combine();
Assert.That(combined.IsSuccess, Is.True);
var list = combined.Match(succ: v => v, fail: _ => new List<int>());
Assert.That(list, Has.Count.EqualTo(3));
Assert.That(list, Is.EqualTo(new[] { 1, 2, 3 }));
}
[Test]
public void Combine_Should_Return_First_Failure_When_Any_Result_Fails()
{
var exception = new Exception("Error");
var results = new[]
{
Result<int>.Succeed(1),
Result<int>.Fail(exception),
Result<int>.Succeed(3)
};
var combined = results.Combine();
Assert.That(combined.IsFaulted, Is.True);
Assert.That(combined.Exception, Is.SameAs(exception));
}
[Test]
public void Combine_Should_Handle_Empty_Collection()
{
var results = Array.Empty<Result<int>>();
var combined = results.Combine();
Assert.That(combined.IsSuccess, Is.True);
var list = combined.Match(succ: v => v, fail: _ => new List<int>());
Assert.That(list, Is.Empty);
}
[Test]
public void Combine_Should_Throw_ArgumentNullException_When_Collection_Is_Null()
{
Assert.Throws<ArgumentNullException>(() => ((IEnumerable<Result<int>>)null!).Combine());
}
[Test]
public void Combine_Should_Preserve_Order_Of_Values()
{
var results = new[]
{
Result<int>.Succeed(3),
Result<int>.Succeed(1),
Result<int>.Succeed(2)
};
var combined = results.Combine();
var list = combined.Match(succ: v => v, fail: _ => new List<int>());
Assert.That(list, Is.EqualTo(new[] { 3, 1, 2 }));
}
[Test]
public void Combine_Should_Short_Circuit_On_First_Failure()
{
var callCount = 0;
var results = new[]
{
Result<int>.Succeed(1),
Result<int>.Fail(new Exception("Error")),
Result<int>.Try(() =>
{
callCount++;
return 3;
})
};
var combined = results.Combine();
Assert.That(combined.IsFaulted, Is.True);
}
[Test]
public void Map_Should_Transform_Success_Value()
{
var result = Result<int>.Succeed(42);
var mapped = result.Map(x => x.ToString());
Assert.That(mapped.IsSuccess, Is.True);
Assert.That(mapped.Match(succ: v => v, fail: _ => ""), Is.EqualTo("42"));
}
[Test]
public void Map_Should_Propagate_Failure()
{
var exception = new Exception("Error");
var result = Result<int>.Fail(exception);
var mapped = result.Map(x => x.ToString());
Assert.That(mapped.IsFaulted, Is.True);
Assert.That(mapped.Exception, Is.SameAs(exception));
}
[Test]
public void Map_Should_Throw_ArgumentNullException_When_Mapper_Is_Null()
{
var result = Result<int>.Succeed(42);
Assert.Throws<ArgumentNullException>(() => result.Map<string>(null!));
}
[Test]
public void Bind_Should_Chain_Success_Results()
{
var result = Result<int>.Succeed(42);
var bound = result.Bind(x => Result<string>.Succeed(x.ToString()));
Assert.That(bound.IsSuccess, Is.True);
Assert.That(bound.Match(succ: v => v, fail: _ => ""), Is.EqualTo("42"));
}
[Test]
public void Bind_Should_Propagate_Failure()
{
var exception = new Exception("Error");
var result = Result<int>.Fail(exception);
var bound = result.Bind(x => Result<string>.Succeed(x.ToString()));
Assert.That(bound.IsFaulted, Is.True);
Assert.That(bound.Exception, Is.SameAs(exception));
}
[Test]
public void Bind_Should_Throw_ArgumentNullException_When_Binder_Is_Null()
{
var result = Result<int>.Succeed(42);
Assert.Throws<ArgumentNullException>(() => result.Bind<string>(null!));
}
[Test]
public void OnSuccess_Should_Execute_Action_When_Success()
{
var result = Result<int>.Succeed(42);
var executed = false;
result.OnSuccess(_ => executed = true);
Assert.That(executed, Is.True);
}
[Test]
public void OnSuccess_Should_Not_Execute_Action_When_Faulted()
{
var result = Result<int>.Fail(new Exception("Error"));
var executed = false;
result.OnSuccess(_ => executed = true);
Assert.That(executed, Is.False);
}
[Test]
public void OnSuccess_Should_Return_Original_Result_For_Chaining()
{
var result = Result<int>.Succeed(42);
var returned = result.OnSuccess(_ => { });
Assert.That(returned, Is.EqualTo(result));
}
[Test]
public void OnSuccess_Should_Throw_ArgumentNullException_When_Action_Is_Null()
{
var result = Result<int>.Succeed(42);
Assert.Throws<ArgumentNullException>(() => result.OnSuccess(null!));
}
[Test]
public void OnFailure_Should_Execute_Action_When_Faulted()
{
var result = Result<int>.Fail(new Exception("Error"));
var executed = false;
result.OnFailure(_ => executed = true);
Assert.That(executed, Is.True);
}
[Test]
public void OnFailure_Should_Not_Execute_Action_When_Success()
{
var result = Result<int>.Succeed(42);
var executed = false;
result.OnFailure(_ => executed = true);
Assert.That(executed, Is.False);
}
[Test]
public void OnFailure_Should_Return_Original_Result_For_Chaining()
{
var result = Result<int>.Fail(new Exception("Error"));
var returned = result.OnFailure(_ => { });
Assert.That(returned, Is.EqualTo(result));
}
[Test]
public void OnFailure_Should_Throw_ArgumentNullException_When_Action_Is_Null()
{
var result = Result<int>.Fail(new Exception("Error"));
Assert.Throws<ArgumentNullException>(() => result.OnFailure(null!));
}
[Test]
public void Ensure_Should_Return_Success_When_Predicate_Is_True()
{
var result = Result<int>.Succeed(42);
var ensured = result.Ensure(x => x > 0, "Value must be positive");
Assert.That(ensured.IsSuccess, Is.True);
}
[Test]
public void Ensure_Should_Return_Failure_When_Predicate_Is_False()
{
var result = Result<int>.Succeed(-1);
var ensured = result.Ensure(x => x > 0, "Value must be positive");
Assert.That(ensured.IsFaulted, Is.True);
Assert.That(ensured.Exception, Is.TypeOf<ArgumentException>());
}
[Test]
public void Ensure_Should_Propagate_Existing_Failure()
{
var exception = new Exception("Original error");
var result = Result<int>.Fail(exception);
var ensured = result.Ensure(x => x > 0, "Value must be positive");
Assert.That(ensured.IsFaulted, Is.True);
Assert.That(ensured.Exception, Is.SameAs(exception));
}
[Test]
public void Ensure_Should_Create_ArgumentException_With_Message()
{
var result = Result<int>.Succeed(-1);
var ensured = result.Ensure(x => x > 0, "Value must be positive");
Assert.That(ensured.Exception.Message, Is.EqualTo("Value must be positive"));
}
[Test]
public void Ensure_Should_Throw_ArgumentNullException_When_Predicate_Is_Null()
{
var result = Result<int>.Succeed(42);
Assert.Throws<ArgumentNullException>(() => result.Ensure(null!, "Error"));
}
[Test]
public void Ensure_Should_Throw_ArgumentException_When_Message_Is_NullOrWhiteSpace()
{
var result = Result<int>.Succeed(42);
Assert.Throws<ArgumentException>(() => result.Ensure(x => true, ""));
Assert.Throws<ArgumentException>(() => result.Ensure(x => true, " "));
}
[Test]
public void Ensure_WithFactory_Should_Return_Success_When_Predicate_Is_True()
{
var result = Result<int>.Succeed(42);
var ensured = result.Ensure(x => x > 0, _ => new InvalidOperationException("Error"));
Assert.That(ensured.IsSuccess, Is.True);
}
[Test]
public void Ensure_WithFactory_Should_Return_Failure_With_Custom_Exception()
{
var result = Result<int>.Succeed(-1);
var ensured = result.Ensure(x => x > 0, _ => new InvalidOperationException("Custom error"));
Assert.That(ensured.IsFaulted, Is.True);
Assert.That(ensured.Exception, Is.TypeOf<InvalidOperationException>());
}
[Test]
public void Ensure_WithFactory_Should_Pass_Value_To_Exception_Factory()
{
var result = Result<int>.Succeed(-1);
var capturedValue = 0;
result.Ensure(x => x > 0, v =>
{
capturedValue = v;
return new Exception("Error");
});
Assert.That(capturedValue, Is.EqualTo(-1));
}
[Test]
public void Ensure_WithFactory_Should_Propagate_Existing_Failure()
{
var exception = new Exception("Original error");
var result = Result<int>.Fail(exception);
var ensured = result.Ensure(x => x > 0, _ => new InvalidOperationException("Error"));
Assert.That(ensured.Exception, Is.SameAs(exception));
}
[Test]
public void Ensure_WithFactory_Should_Throw_ArgumentNullException_When_Parameters_Are_Null()
{
var result = Result<int>.Succeed(42);
Assert.Throws<ArgumentNullException>(() => result.Ensure(null!, (Func<int, Exception>)(_ => new Exception())));
Assert.Throws<ArgumentNullException>(() => result.Ensure(x => true, (Func<int, Exception>)null!));
}
[Test]
public void Try_Should_Return_Success_When_Function_Succeeds()
{
var result = ResultExtensions.Try(() => 42);
Assert.That(result.IsSuccess, Is.True);
Assert.That(result.Match(succ: v => v, fail: _ => 0), Is.EqualTo(42));
}
[Test]
public void Try_Should_Return_Failure_When_Function_Throws()
{
var result = ResultExtensions.Try<int>(() => throw new InvalidOperationException("Error"));
Assert.That(result.IsFaulted, Is.True);
Assert.That(result.Exception, Is.TypeOf<InvalidOperationException>());
}
[Test]
public void Try_Should_Throw_ArgumentNullException_When_Function_Is_Null()
{
Assert.Throws<ArgumentNullException>(() => ResultExtensions.Try<int>(null!));
}
[Test]
public async Task TryAsync_Should_Return_Success_When_Async_Function_Succeeds()
{
var result = await ResultExtensions.TryAsync(async () =>
{
await Task.Delay(1);
return 42;
});
Assert.That(result.IsSuccess, Is.True);
Assert.That(result.Match(succ: v => v, fail: _ => 0), Is.EqualTo(42));
}
[Test]
public async Task TryAsync_Should_Return_Failure_When_Async_Function_Throws()
{
var result = await ResultExtensions.TryAsync<int>(async () =>
{
await Task.Delay(1);
throw new InvalidOperationException("Error");
});
Assert.That(result.IsFaulted, Is.True);
Assert.That(result.Exception, Is.TypeOf<InvalidOperationException>());
}
[Test]
public async Task TryAsync_Should_Handle_Synchronous_Exceptions()
{
var result = await ResultExtensions.TryAsync<int>(() => throw new InvalidOperationException("Sync error"));
Assert.That(result.IsFaulted, Is.True);
}
[Test]
public void TryAsync_Should_Throw_ArgumentNullException_When_Function_Is_Null()
{
Assert.ThrowsAsync<ArgumentNullException>(async () => await ResultExtensions.TryAsync<int>(null!));
}
[Test]
public void ToNullable_Should_Return_Value_When_Success()
{
var result = Result<int>.Succeed(42);
var nullable = result.ToNullable();
Assert.That(nullable, Is.EqualTo(42));
}
[Test]
public void ToNullable_Should_Return_Null_When_Faulted()
{
var result = Result<int>.Fail(new Exception("Error"));
var nullable = result.ToNullable();
Assert.That(nullable, Is.Null);
}
[Test]
public void ToNullable_Should_Work_With_Value_Types()
{
var result = Result<DateTime>.Succeed(new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc));
var nullable = result.ToNullable();
Assert.That(nullable, Is.Not.Null);
Assert.That(nullable!.Value.Year, Is.EqualTo(2025));
Assert.That(nullable!.Value.Kind, Is.EqualTo(DateTimeKind.Utc)); // 验证 DateTimeKind
}
[Test]
public void ToResult_Should_Return_Success_When_Value_Is_Not_Null()
{
string value = "test";
var result = value.ToResult();
Assert.That(result.IsSuccess, Is.True);
Assert.That(result.Match(succ: v => v, fail: _ => ""), Is.EqualTo("test"));
}
[Test]
public void ToResult_Should_Return_Failure_When_Value_Is_Null()
{
string? value = null;
var result = value.ToResult();
Assert.That(result.IsFaulted, Is.True);
Assert.That(result.Exception, Is.TypeOf<ArgumentNullException>());
}
[Test]
public void ToResult_Should_Use_Custom_Error_Message()
{
string? value = null;
var result = value.ToResult("Custom error");
Assert.That(result.Exception.Message, Does.Contain("Custom error"));
}
[Test]
public void ToResult_Should_Return_Success_When_Nullable_Has_Value()
{
int? value = 42;
var result = value.ToResult();
Assert.That(result.IsSuccess, Is.True);
Assert.That(result.Match(succ: v => v, fail: _ => 0), Is.EqualTo(42));
}
[Test]
public void ToResult_Should_Return_Failure_When_Nullable_Is_Null()
{
int? value = null;
var result = value.ToResult();
Assert.That(result.IsFaulted, Is.True);
Assert.That(result.Exception, Is.TypeOf<ArgumentNullException>());
}
[Test]
public void ToResult_Should_Use_Custom_Error_Message_For_Nullable()
{
int? value = null;
var result = value.ToResult("Custom nullable error");
Assert.That(result.Exception.Message, Does.Contain("Custom nullable error"));
}
[Test]
public void Should_Support_Complex_Chaining_With_Multiple_Operations()
{
var result = Result<int>.Succeed(10)
.Map(x => x * 2)
.Bind(x => Result<int>.Succeed(x + 5))
.Ensure(x => x > 20, "Value must be greater than 20")
.OnSuccess(x => Console.WriteLine($"Success: {x}"))
.OnFailure(ex => Console.WriteLine($"Error: {ex.Message}"));
Assert.That(result.IsSuccess, Is.True);
Assert.That(result.Match(succ: v => v, fail: _ => 0), Is.EqualTo(25));
}
[Test]
public void Should_Support_OnSuccess_And_OnFailure_Chaining()
{
var successCount = 0;
var failureCount = 0;
Result<int>.Succeed(42)
.OnSuccess(_ => successCount++)
.OnFailure(_ => failureCount++);
Assert.That(successCount, Is.EqualTo(1));
Assert.That(failureCount, Is.EqualTo(0));
}
[Test]
public void Should_Support_Ensure_Chaining_With_Multiple_Conditions()
{
var result = Result<int>.Succeed(50)
.Ensure(x => x > 0, "Must be positive")
.Ensure(x => x < 100, "Must be less than 100")
.Ensure(x => x % 2 == 0, "Must be even");
Assert.That(result.IsSuccess, Is.True);
}
}

View File

@ -0,0 +1,603 @@
// Copyright (c) 2025 GeWuYou
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using GFramework.Core.Functional;
using NUnit.Framework;
namespace GFramework.Core.Tests.Functional;
/// <summary>
/// Result&lt;A&gt; 泛型类型测试类
/// </summary>
[TestFixture]
public class ResultTTests
{
[Test]
public void Constructor_WithValue_Should_Create_Success_Result()
{
var result = new Result<int>(42);
Assert.That(result.IsSuccess, Is.True);
Assert.That(result.IsFaulted, Is.False);
Assert.That(result.IsBottom, Is.False);
}
[Test]
public void Constructor_WithException_Should_Create_Faulted_Result()
{
var exception = new InvalidOperationException("Test error");
var result = new Result<int>(exception);
Assert.That(result.IsFaulted, Is.True);
Assert.That(result.IsSuccess, Is.False);
Assert.That(result.Exception, Is.SameAs(exception));
}
[Test]
public void Constructor_WithNullException_Should_Throw_ArgumentNullException()
{
Assert.Throws<ArgumentNullException>(() => new Result<int>(null!));
}
[Test]
public void DefaultConstructor_Should_Create_Bottom_State()
{
var result = new Result<int>();
Assert.That(result.IsBottom, Is.True);
Assert.That(result.IsSuccess, Is.False);
Assert.That(result.IsFaulted, Is.False);
}
[Test]
public void ImplicitConversion_Should_Create_Success_Result()
{
Result<int> result = 42;
Assert.That(result.IsSuccess, Is.True);
Assert.That(result.Match(succ: v => v, fail: _ => 0), Is.EqualTo(42));
}
[Test]
public void IsSuccess_Should_Return_True_When_Result_Is_Successful()
{
var result = Result<int>.Succeed(42);
Assert.That(result.IsSuccess, Is.True);
}
[Test]
public void IsFaulted_Should_Return_True_When_Result_Is_Faulted()
{
var result = Result<int>.Fail(new Exception("Error"));
Assert.That(result.IsFaulted, Is.True);
}
[Test]
public void IsBottom_Should_Return_True_When_Result_Is_Bottom()
{
var result = Result<int>.Bottom;
Assert.That(result.IsBottom, Is.True);
}
[Test]
public void Exception_Should_Return_Exception_When_Faulted()
{
var exception = new InvalidOperationException("Test");
var result = Result<int>.Fail(exception);
Assert.That(result.Exception, Is.SameAs(exception));
}
[Test]
public void Exception_Should_Return_InvalidOperationException_When_Bottom()
{
var result = Result<int>.Bottom;
Assert.That(result.Exception, Is.TypeOf<InvalidOperationException>());
}
[Test]
public void Exception_Should_Return_InvalidOperationException_When_Success()
{
var result = Result<int>.Succeed(42);
Assert.That(result.Exception, Is.TypeOf<InvalidOperationException>());
}
[Test]
public void Succeed_Should_Create_Successful_Result()
{
var result = Result<int>.Succeed(42);
Assert.That(result.IsSuccess, Is.True);
Assert.That(result.Match(succ: v => v, fail: _ => 0), Is.EqualTo(42));
}
[Test]
public void Success_Should_Create_Successful_Result()
{
var result = Result<int>.Success(42);
Assert.That(result.IsSuccess, Is.True);
Assert.That(result.Match(succ: v => v, fail: _ => 0), Is.EqualTo(42));
}
[Test]
public void Fail_Should_Create_Faulted_Result()
{
var exception = new Exception("Error");
var result = Result<int>.Fail(exception);
Assert.That(result.IsFaulted, Is.True);
Assert.That(result.Exception, Is.SameAs(exception));
}
[Test]
public void Failure_WithException_Should_Create_Faulted_Result()
{
var exception = new Exception("Error");
var result = Result<int>.Failure(exception);
Assert.That(result.IsFaulted, Is.True);
Assert.That(result.Exception, Is.SameAs(exception));
}
[Test]
public void Failure_WithMessage_Should_Create_Faulted_Result()
{
var message = "Test error";
var result = Result<int>.Failure(message);
Assert.That(result.IsFaulted, Is.True);
Assert.That(result.Exception.Message, Is.EqualTo(message));
}
[Test]
public void Try_Should_Return_Success_When_Function_Succeeds()
{
var result = Result<int>.Try(() => 42);
Assert.That(result.IsSuccess, Is.True);
Assert.That(result.Match(succ: v => v, fail: _ => 0), Is.EqualTo(42));
}
[Test]
public void Try_Should_Return_Failure_When_Function_Throws()
{
var result = Result<int>.Try(() => throw new InvalidOperationException("Error"));
Assert.That(result.IsFaulted, Is.True);
Assert.That(result.Exception, Is.TypeOf<InvalidOperationException>());
}
[Test]
public void IfFail_WithDefaultValue_Should_Return_Value_When_Success()
{
var result = Result<int>.Succeed(42);
var value = result.IfFail(0);
Assert.That(value, Is.EqualTo(42));
}
[Test]
public void IfFail_WithDefaultValue_Should_Return_Default_When_Faulted()
{
var result = Result<int>.Fail(new Exception("Error"));
var value = result.IfFail(99);
Assert.That(value, Is.EqualTo(99));
}
[Test]
public void IfFail_WithFunction_Should_Return_Value_When_Success()
{
var result = Result<int>.Succeed(42);
var value = result.IfFail(_ => 0);
Assert.That(value, Is.EqualTo(42));
}
[Test]
public void IfFail_WithFunction_Should_Execute_Function_When_Faulted()
{
var exception = new Exception("Error");
var result = Result<int>.Fail(exception);
var executed = false;
var value = result.IfFail(ex =>
{
executed = true;
return 99;
});
Assert.That(executed, Is.True);
Assert.That(value, Is.EqualTo(99));
}
[Test]
public void IfFail_WithAction_Should_Execute_Action_When_Faulted()
{
var result = Result<int>.Fail(new Exception("Error"));
var executed = false;
result.IfFail(_ => executed = true);
Assert.That(executed, Is.True);
}
[Test]
public void IfSucc_Should_Execute_Action_When_Success()
{
var result = Result<int>.Succeed(42);
var executed = false;
result.IfSucc(_ => executed = true);
Assert.That(executed, Is.True);
}
[Test]
public void IfSucc_Should_Not_Execute_Action_When_Faulted()
{
var result = Result<int>.Fail(new Exception("Error"));
var executed = false;
result.IfSucc(_ => executed = true);
Assert.That(executed, Is.False);
}
[Test]
public void Map_Should_Transform_Value_When_Success()
{
var result = Result<int>.Succeed(42);
var mapped = result.Map(x => x.ToString());
Assert.That(mapped.IsSuccess, Is.True);
Assert.That(mapped.Match(succ: v => v, fail: _ => ""), Is.EqualTo("42"));
}
[Test]
public void Map_Should_Propagate_Exception_When_Faulted()
{
var exception = new Exception("Error");
var result = Result<int>.Fail(exception);
var mapped = result.Map(x => x.ToString());
Assert.That(mapped.IsFaulted, Is.True);
Assert.That(mapped.Exception, Is.SameAs(exception));
}
[Test]
public void Map_Should_Handle_Null_Result_From_Mapper()
{
var result = Result<int>.Succeed(42);
var mapped = result.Map<string>(_ => null!);
Assert.That(mapped.IsSuccess, Is.True);
}
[Test]
public void Map_Should_Not_Throw_When_Mapper_Returns_Null()
{
var result = Result<string>.Succeed("test");
Assert.DoesNotThrow(() => result.Map<string?>(_ => null));
}
[Test]
public void Bind_Should_Chain_Success_Results()
{
var result = Result<int>.Succeed(42);
var bound = result.Bind(x => Result<string>.Succeed(x.ToString()));
Assert.That(bound.IsSuccess, Is.True);
Assert.That(bound.Match(succ: v => v, fail: _ => ""), Is.EqualTo("42"));
}
[Test]
public void Bind_Should_Propagate_First_Failure()
{
var exception = new Exception("Error");
var result = Result<int>.Fail(exception);
var bound = result.Bind(x => Result<string>.Succeed(x.ToString()));
Assert.That(bound.IsFaulted, Is.True);
Assert.That(bound.Exception, Is.SameAs(exception));
}
[Test]
public void Bind_Should_Propagate_Second_Failure()
{
var result = Result<int>.Succeed(42);
var exception = new Exception("Bind error");
var bound = result.Bind(x => Result<string>.Fail(exception));
Assert.That(bound.IsFaulted, Is.True);
Assert.That(bound.Exception, Is.SameAs(exception));
}
[Test]
public void Bind_Should_Handle_Complex_Chaining()
{
var result = Result<int>.Succeed(10)
.Bind(x => Result<int>.Succeed(x * 2))
.Bind(x => Result<int>.Succeed(x + 5));
Assert.That(result.IsSuccess, Is.True);
Assert.That(result.Match(succ: v => v, fail: _ => 0), Is.EqualTo(25));
}
[Test]
public async Task MapAsync_Should_Transform_Value_When_Success()
{
var result = Result<int>.Succeed(42);
var mapped = await result.MapAsync(async x =>
{
await Task.Delay(1);
return x.ToString();
});
Assert.That(mapped.IsSuccess, Is.True);
Assert.That(mapped.Match(succ: v => v, fail: _ => ""), Is.EqualTo("42"));
}
[Test]
public async Task MapAsync_Should_Propagate_Exception_When_Faulted()
{
var exception = new Exception("Error");
var result = Result<int>.Fail(exception);
var mapped = await result.MapAsync(async x =>
{
await Task.Delay(1);
return x.ToString();
});
Assert.That(mapped.IsFaulted, Is.True);
Assert.That(mapped.Exception, Is.SameAs(exception));
}
[Test]
public async Task MapAsync_Should_Handle_Async_Exceptions()
{
var result = Result<int>.Succeed(42);
var mapped = await result.MapAsync<string>(async _ =>
{
await Task.Delay(1);
throw new InvalidOperationException("Async error");
});
Assert.That(mapped.IsFaulted, Is.True);
Assert.That(mapped.Exception, Is.TypeOf<InvalidOperationException>());
}
[Test]
public void Match_WithFunctions_Should_Execute_Succ_When_Success()
{
var result = Result<int>.Succeed(42);
var value = result.Match(succ: x => x * 2, fail: _ => 0);
Assert.That(value, Is.EqualTo(84));
}
[Test]
public void Match_WithFunctions_Should_Execute_Fail_When_Faulted()
{
var result = Result<int>.Fail(new Exception("Error"));
var value = result.Match(succ: x => x, fail: _ => 99);
Assert.That(value, Is.EqualTo(99));
}
[Test]
public void Match_WithActions_Should_Execute_Succ_When_Success()
{
var result = Result<int>.Succeed(42);
var executed = false;
result.Match(succ: _ => executed = true, fail: _ => { });
Assert.That(executed, Is.True);
}
[Test]
public void Match_WithActions_Should_Execute_Fail_When_Faulted()
{
var result = Result<int>.Fail(new Exception("Error"));
var executed = false;
result.Match(succ: _ => { }, fail: _ => executed = true);
Assert.That(executed, Is.True);
}
[Test]
public void Match_Should_Handle_Bottom_State_Correctly()
{
var result = Result<int>.Bottom;
var value = result.Match(succ: x => x, fail: _ => 99);
Assert.That(value, Is.EqualTo(99));
}
[Test]
public void Equals_Should_Return_True_When_Both_Success_With_Same_Value()
{
var result1 = Result<int>.Succeed(42);
var result2 = Result<int>.Succeed(42);
Assert.That(result1.Equals(result2), Is.True);
}
[Test]
public void Equals_Should_Return_False_When_Success_Values_Differ()
{
var result1 = Result<int>.Succeed(42);
var result2 = Result<int>.Succeed(43);
Assert.That(result1.Equals(result2), Is.False);
}
[Test]
public void Equals_Should_Return_True_When_Both_Faulted_With_Same_Exception()
{
var result1 = Result<int>.Fail(new InvalidOperationException("Error"));
var result2 = Result<int>.Fail(new InvalidOperationException("Error"));
Assert.That(result1.Equals(result2), Is.True);
}
[Test]
public void Equals_Should_Return_False_When_Exception_Types_Differ()
{
var result1 = Result<int>.Fail(new InvalidOperationException("Error"));
var result2 = Result<int>.Fail(new ArgumentException("Error"));
Assert.That(result1.Equals(result2), Is.False);
}
[Test]
public void Equals_Should_Return_True_When_Both_Bottom()
{
var result1 = Result<int>.Bottom;
var result2 = new Result<int>();
Assert.That(result1.Equals(result2), Is.True);
}
[Test]
public void Equals_Should_Return_False_When_States_Differ()
{
var result1 = Result<int>.Succeed(42);
var result2 = Result<int>.Fail(new Exception("Error"));
Assert.That(result1.Equals(result2), Is.False);
}
[Test]
public void GetHashCode_Should_Be_Consistent_For_Equal_Results()
{
var result1 = Result<int>.Succeed(42);
var result2 = Result<int>.Succeed(42);
Assert.That(result1.GetHashCode(), Is.EqualTo(result2.GetHashCode()));
}
[Test]
public void OperatorEquals_Should_Work_Correctly()
{
var result1 = Result<int>.Succeed(42);
var result2 = Result<int>.Succeed(42);
var result3 = Result<int>.Succeed(43);
Assert.That(result1 == result2, Is.True);
Assert.That(result1 == result3, Is.False);
}
[Test]
public void OperatorNotEquals_Should_Work_Correctly()
{
var result1 = Result<int>.Succeed(42);
var result2 = Result<int>.Succeed(43);
Assert.That(result1 != result2, Is.True);
}
[Test]
public void CompareTo_Should_Order_Bottom_Before_Faulted()
{
var bottom = Result<int>.Bottom;
var faulted = Result<int>.Fail(new Exception("Error"));
Assert.That(bottom.CompareTo(faulted), Is.LessThan(0));
}
[Test]
public void CompareTo_Should_Order_Faulted_Before_Success()
{
var faulted = Result<int>.Fail(new Exception("Error"));
var success = Result<int>.Succeed(42);
Assert.That(faulted.CompareTo(success), Is.LessThan(0));
}
[Test]
public void CompareTo_Should_Compare_Success_Values_When_Both_Success()
{
var result1 = Result<int>.Succeed(10);
var result2 = Result<int>.Succeed(20);
Assert.That(result1.CompareTo(result2), Is.LessThan(0));
}
[Test]
public void CompareTo_Should_Return_Zero_When_Both_Faulted()
{
var result1 = Result<int>.Fail(new Exception("Error1"));
var result2 = Result<int>.Fail(new Exception("Error2"));
Assert.That(result1.CompareTo(result2), Is.EqualTo(0));
}
[Test]
public void CompareTo_Should_Return_Zero_When_Both_Bottom()
{
var result1 = Result<int>.Bottom;
var result2 = new Result<int>();
Assert.That(result1.CompareTo(result2), Is.EqualTo(0));
}
[Test]
public void OperatorLessThan_Should_Work_Correctly()
{
var result1 = Result<int>.Succeed(10);
var result2 = Result<int>.Succeed(20);
Assert.That(result1 < result2, Is.True);
}
[Test]
public void OperatorLessThanOrEqual_Should_Work_Correctly()
{
var result1 = Result<int>.Succeed(10);
var result2 = Result<int>.Succeed(10);
Assert.That(result1 <= result2, Is.True);
}
[Test]
public void OperatorGreaterThan_Should_Work_Correctly()
{
var result1 = Result<int>.Succeed(20);
var result2 = Result<int>.Succeed(10);
Assert.That(result1 > result2, Is.True);
}
[Test]
public void OperatorGreaterThanOrEqual_Should_Work_Correctly()
{
var result1 = Result<int>.Succeed(20);
var result2 = Result<int>.Succeed(20);
Assert.That(result1 >= result2, Is.True);
}
[Test]
public void CompareTo_Should_Handle_NonComparable_Types_Gracefully()
{
var result1 = Result<object>.Succeed(new object());
var result2 = Result<object>.Succeed(new object());
Assert.DoesNotThrow(() => result1.CompareTo(result2));
}
[Test]
public void ToString_Should_Return_Value_String_When_Success()
{
var result = Result<int>.Succeed(42);
Assert.That(result.ToString(), Is.EqualTo("42"));
}
[Test]
public void ToString_Should_Return_Fail_Message_When_Faulted()
{
var result = Result<int>.Fail(new Exception("Test error"));
Assert.That(result.ToString(), Is.EqualTo("Fail(Test error)"));
}
[Test]
public void ToString_Should_Return_Bottom_When_Bottom()
{
var result = Result<int>.Bottom;
Assert.That(result.ToString(), Is.EqualTo("(Bottom)"));
}
[Test]
public void Success_WithNullValue_Should_Create_Valid_Result()
{
var result = Result<string?>.Succeed(null);
Assert.That(result.IsSuccess, Is.True);
}
[Test]
public void Map_WithNullValue_Should_Handle_Correctly()
{
var result = Result<string?>.Succeed(null);
var mapped = result.Map(x => x?.Length ?? 0);
Assert.That(mapped.IsSuccess, Is.True);
Assert.That(mapped.Match(succ: v => v, fail: _ => -1), Is.EqualTo(0));
}
[Test]
public void Equals_WithNullValues_Should_Work_Correctly()
{
var result1 = Result<string?>.Succeed(null);
var result2 = Result<string?>.Succeed(null);
Assert.That(result1.Equals(result2), Is.True);
}
[Test]
public void ImplicitConversion_WithNull_Should_Create_Success_With_Null()
{
Result<string?> result = null;
Assert.That(result.IsSuccess, Is.True);
}
[Test]
public void Bottom_Should_Be_Readonly_And_Immutable()
{
var bottom1 = Result<int>.Bottom;
var bottom2 = Result<int>.Bottom;
Assert.That(bottom1.Equals(bottom2), Is.True);
}
}

View File

@ -0,0 +1,432 @@
// Copyright (c) 2025 GeWuYou
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using GFramework.Core.Functional;
using NUnit.Framework;
namespace GFramework.Core.Tests.Functional;
/// <summary>
/// Result 类型测试类
/// </summary>
[TestFixture]
public class ResultTests
{
/// <summary>
/// 测试 Success 方法应该创建成功结果
/// </summary>
[Test]
public void Success_Should_Create_Successful_Result()
{
// Arrange & Act
var result = Result.Success();
// Assert
Assert.That(result.IsSuccess, Is.True);
Assert.That(result.IsFailure, Is.False);
}
/// <summary>
/// 测试 Failure 方法使用异常应该创建失败结果
/// </summary>
[Test]
public void Failure_WithException_Should_Create_Failed_Result()
{
// Arrange
var exception = new InvalidOperationException("Test error");
// Act
var result = Result.Failure(exception);
// Assert
Assert.That(result.IsFailure, Is.True);
Assert.That(result.IsSuccess, Is.False);
Assert.That(result.Error, Is.SameAs(exception));
}
/// <summary>
/// 测试 Failure 方法使用消息应该创建带异常的失败结果
/// </summary>
[Test]
public void Failure_WithMessage_Should_Create_Failed_Result_With_Exception()
{
// Arrange
var message = "Test error message";
// Act
var result = Result.Failure(message);
// Assert
Assert.That(result.IsFailure, Is.True);
Assert.That(result.Error.Message, Is.EqualTo(message));
}
/// <summary>
/// 测试 Failure 方法使用 null 异常应该抛出 ArgumentNullException
/// </summary>
[Test]
public void Failure_WithNullException_Should_Throw_ArgumentNullException()
{
// Act & Assert
Assert.Throws<ArgumentNullException>(() => Result.Failure((Exception)null!));
}
/// <summary>
/// 测试 Failure 方法使用 null 或空消息应该抛出 ArgumentException
/// </summary>
[Test]
public void Failure_WithNullOrEmptyMessage_Should_Throw_ArgumentException()
{
// Act & Assert
Assert.Throws<ArgumentException>(() => Result.Failure(string.Empty));
Assert.Throws<ArgumentException>(() => Result.Failure(""));
}
/// <summary>
/// 测试 IsSuccess 属性在结果成功时应该返回 true
/// </summary>
[Test]
public void IsSuccess_Should_Return_True_When_Result_Is_Successful()
{
// Arrange
var result = Result.Success();
// Act & Assert
Assert.That(result.IsSuccess, Is.True);
}
/// <summary>
/// 测试 IsFailure 属性在结果失败时应该返回 true
/// </summary>
[Test]
public void IsFailure_Should_Return_True_When_Result_Is_Failed()
{
// Arrange
var result = Result.Failure(new Exception("Error"));
// Acert
Assert.That(result.IsFailure, Is.True);
}
/// <summary>
/// 测试 Error 属性在结果失败时应该返回异常
/// </summary>
[Test]
public void Error_Should_Return_Exception_When_Result_Is_Failed()
{
// Arrange
var exception = new InvalidOperationException("Test");
var result = Result.Failure(exception);
// Act
var error = result.Error;
// Assert
Assert.That(error, Is.SameAs(exception));
}
/// <summary>
/// 测试 Error 属性在结果成功时应该抛出 InvalidOperationException
/// </summary>
[Test]
public void Error_Should_Throw_InvalidOperationException_When_Result_Is_Successful()
{
// Arrange
var result = Result.Success();
// Act & Assert
Assert.Throws<InvalidOperationException>(() => _ = result.Error);
}
/// <summary>
/// 测试 Match 方法在结果成功时应该执行 onSuccess 委托
/// </summary>
[Test]
public void Match_Should_Execute_OnSuccess_When_Result_Is_Successful()
{
// Arrange
var result = Result.Success();
var executed = false;
// Act
var value = result.Match(
onSuccess: () =>
{
executed = true;
return "success";
},
onFailure: _ => "failure"
);
// Assert
Assert.That(executed, Is.True);
Assert.That(value, Is.EqualTo("success"));
}
/// <summary>
/// 测试 Match 方法在结果失败时应该执行 onFailure 委托
/// </summary>
[Test]
public void Match_Should_Execute_OnFailure_WhenFailed()
{
// Arrange
var exception = new Exception("Error");
var result = Result.Failure(exception);
var executed = false;
// Act
var value = result.Match(
onSuccess: () => "success",
onFailure: _ =>
{
executed = true;
return "failure";
}
);
// Assert
Assert.That(executed, Is.True);
Assert.That(value, Is.EqualTo("failure"));
}
/// <summary>
/// 测试 Match 方法应该将异常传递给 onFailure 处理器
/// </summary>
[Test]
public void Match_Should_Pass_Exception_To_OnFailure_Handler()
{
// Arrange
var exception = new InvalidOperationException("Test error");
var result = Result.Failure(exception);
Exception? capturedEx = null;
// Act
result.Match(
onSuccess: () => 0,
onFailure: ex =>
{
capturedEx = ex;
return 1;
}
);
// Assert
Assert.That(capturedEx, Is.SameAs(exception));
}
/// <summary>
/// 测试 ToResult 方法应该将成功结果转换为泛型成功结果
/// </summary>
[Test]
public void ToResult_Should_Convert_Success_To_Generic_Success()
{
// Arrange
var result = Result.Success();
// Act
var genericResult = result.ToResult(42);
// Assert
Assert.That(genericResult.IsSuccess, Is.True);
Assert.That(genericResult.Match(succ: v => v, fail: _ => 0), Is.EqualTo(42));
}
/// <summary>
/// 测试 ToResult 方法应该将失败结果转换为泛型失败结果
/// </summary>
[Test]
public void ToResult_Should_Convert_Failure_To_Generic_Failure()
{
// Arrange
var exception = new Exception("Error");
var result = Result.Failure(exception);
// Act
var genericResult = result.ToResult(42);
// Assert
Assert.That(genericResult.IsFaulted, Is.True);
Assert.That(genericResult.Exception, Is.SameAs(exception));
}
/// <summary>
/// 测试 ToResult 方法应该保留异常信息
/// </summary>
[Test]
public void ToResult_Should_Preserve_Exception_Information()
{
// Arrange
var exception = new InvalidOperationException("Original error");
var result = Result.Failure(exception);
// Act
var genericResult = result.ToResult("value");
// Assert
Assert.That(genericResult.Exception.Message, Is.EqualTo("Original error"));
Assert.That(genericResult.Exception, Is.TypeOf<InvalidOperationException>());
}
/// <summary>
/// 测试 Equals 方法在两个结果都成功时应该返回 true
/// </summary>
[Test]
public void Equals_Should_Return_True_When_Both_Are_Successful()
{
// Arrange
var result1 = Result.Success();
var result2 = Result.Success();
// Act & Assert
Assert.That(result1.Equals(result2), Is.True);
Assert.That(result1 == result2, Is.True);
}
/// <summary>
/// 测试 Equals 方法在两个结果都失败且异常类型和消息相同时应该返回 true
/// </summary>
[Test]
public void Equals_Should_Return_True_When_Both_Failed_With_Same_Exception_Type_And_Message()
{
// Arrange
var result1 = Result.Failure(new InvalidOperationException("Error"));
var result2 = Result.Failure(new InvalidOperationException("Error"));
// Act & Assert
Assert.That(result1.Equals(result2), Is.True);
}
/// <summary>
/// 测试 Equals 方法在状态不同时应该返回 false
/// </summary>
[Test]
public void Equals_Should_Return_False_When_States_Differ()
{
// Arrange
var result1 = Result.Success();
var result2 = Result.Failure(new Exception("Error"));
// Act & Assert
Assert.That(result1.Equals(result2), Is.False);
Assert.That(result1 != result2, Is.True);
}
/// <summary>
/// 测试 Equals 方法在异常类型不同时应该返回 false
/// </summary>
[Test]
public void Equals_Should_Return_False_When_Exception_Types_Differ()
{
// Arrange
var result1 = Result.Failure(new InvalidOperationException("Error"));
var result2 = Result.Failure(new ArgumentException("Error"));
// Act & Assert
Assert.That(result1.Equals(result2), Is.False);
}
/// <summary>
/// 测试 Equals 方法在异常消息不同时应该返回 false
/// </summary>
[Test]
public void Equals_Should_Return_False_When_Exception_Messages_Differ()
{
// Arrange
var result1 = Result.Failure(new Exception("Error1"));
var result2 = Result.Failure(new Exception("Error2"));
// Act & Assert
Assert.That(result1.Equals(result2), Is.False);
}
/// <summary>
/// 测试 GetHashCode 方法对于相等的结果应该返回一致的哈希码
/// </summary>
[Test]
public void GetHashCode_Should_Be_Consistent_For_Equal_Results()
{
// Arrange
var result1 = Result.Success();
var result2 = Result.Success();
// Act
var hash1 = result1.GetHashCode();
var hash2 = result2.GetHashCode();
// Assert
Assert.That(hash1, Is.EqualTo(hash2));
}
/// <summary>
/// 测试 == 操作符应该正确工作
/// </summary>
[Test]
public void OperatorEquals_Should_Work_Correctly()
{
// Arrange
var result1 = Result.Success();
var result2 = Result.Success();
var result3 = Result.Failure(new Exception("Error"));
// Act & Assert
Assert.That(result1 == result2, Is.True);
Assert.That(result1 == result3, Is.False);
}
/// <summary>
/// 测试 != 操作符应该正确工作
/// </summary>
[Test]
public void OperatorNotEquals_Should_Work_Correctly()
{
// Arrange
var result1 = Result.Success();
var result2 = Result.Failure(new Exception("Error"));
// Act & Assert
Assert.That(result1 != result2, Is.True);
Assert.That(result1 != Result.Success(), Is.False);
}
/// <summary>
/// 测试 ToString 方法在结果成功时应该返回 "Success"
/// </summary>
[Test]
public void ToString_Should_Return_Success_When_Successful()
{
// Arrange
var result = Result.Success();
// Act
var str = result.ToString();
// Assert
Assert.That(str, Is.EqualTo("Success"));
}
/// <summary>
/// 测试 ToString 方法在结果失败时应该返回带消息的 "Fail"
/// </summary>
[Test]
public void ToString_Should_Return_Fail_With_Message_When_Failed()
{
// Arrange
var result = Result.Failure(new Exception("Test error"));
// Act
var str = result.ToString();
// Assert
Assert.That(str, Is.EqualTo("Fail(Test error)"));
}
}

View File

@ -45,7 +45,9 @@ public static class ResultExtensions
{
if (result.IsFaulted)
return Result<List<T>>.Fail(result.Exception);
values.Add(result.Match(succ: v => v, fail: _ => default!));
if (result.IsSuccess)
values.Add(result.Match(succ: v => v, fail: _ => throw new InvalidOperationException()));
}
return Result<List<T>>.Succeed(values);
@ -91,6 +93,28 @@ public static class ResultExtensions
return result.Bind(binder);
}
/// <summary>
/// 异步绑定操作,将结果链式转换为异步结果
/// </summary>
/// <example>
/// <code>
/// var result = Result&lt;int&gt;.Succeed(42);
/// var bound = await result.BindAsync(async x =>
/// await GetUserAsync(x) is User user
/// User&gt;.Succeed(user)
/// : Result&lt;User&gt;.Fail(new Exception("User not found")));
/// </code>
/// </example>
public static async Task<Result<TResult>> BindAsync<T, TResult>(
this Result<T> result,
Func<T, Task<Result<TResult>>> binder)
{
ArgumentNullException.ThrowIfNull(binder);
return result.IsSuccess
? await binder(result.Match(succ: v => v, fail: _ => throw new InvalidOperationException()))
: Result<TResult>.Fail(result.Exception);
}
#endregion
#region

View File

@ -27,12 +27,13 @@ public readonly struct Result<A> : IEquatable<Result<A>>, IComparable<Result<A>>
/// <summary>
/// 结果状态枚举,表示结果的不同状态
/// 排序: Bottom < Faulted < Success
/// </summary>
private enum ResultState : byte
{
Bottom,
Success,
Faulted
Faulted,
Success
}
private readonly ResultState _state;
@ -151,8 +152,11 @@ public readonly struct Result<A> : IEquatable<Result<A>>, IComparable<Result<A>>
/// <param name="f">映射函数</param>
/// <returns>映射后的结果</returns>
[Pure]
public Result<B> Map<B>(Func<A, B> f) =>
IsSuccess ? new Result<B>(f(_value!)) : new Result<B>(Exception);
public Result<B> Map<B>(Func<A, B> f)
{
ArgumentNullException.ThrowIfNull(f);
return IsSuccess ? new Result<B>(f(_value!)) : new Result<B>(Exception);
}
/// <summary>
/// 成功时绑定到新 Result失败时透传异常
@ -161,8 +165,11 @@ public readonly struct Result<A> : IEquatable<Result<A>>, IComparable<Result<A>>
/// <param name="binder">绑定函数</param>
/// <returns>绑定后的结果</returns>
[Pure]
public Result<B> Bind<B>(Func<A, Result<B>> binder) =>
IsSuccess ? binder(_value!) : new Result<B>(Exception);
public Result<B> Bind<B>(Func<A, Result<B>> binder)
{
ArgumentNullException.ThrowIfNull(binder);
return IsSuccess ? binder(_value!) : new Result<B>(Exception);
}
/// <summary>
/// 异步映射
@ -171,8 +178,20 @@ public readonly struct Result<A> : IEquatable<Result<A>>, IComparable<Result<A>>
/// <param name="f">异步映射函数</param>
/// <returns>异步映射后的结果</returns>
[Pure]
public async Task<Result<B>> MapAsync<B>(Func<A, Task<B>> f) =>
IsSuccess ? new Result<B>(await f(_value!)) : new Result<B>(Exception);
public async Task<Result<B>> MapAsync<B>(Func<A, Task<B>> f)
{
ArgumentNullException.ThrowIfNull(f);
if (!IsSuccess) return new Result<B>(Exception);
try
{
return new Result<B>(await f(_value!));
}
catch (Exception ex)
{
return new Result<B>(ex);
}
}
// ------------------------------------------------------------------ 模式匹配
@ -303,8 +322,17 @@ public readonly struct Result<A> : IEquatable<Result<A>>, IComparable<Result<A>>
// Bottom < Faulted < Success
if (_state != other._state) return _state.CompareTo(other._state);
if (!IsSuccess) return 0;
try
{
return Comparer<A>.Default.Compare(_value, other._value);
}
catch (ArgumentException)
{
// 类型不可比较时返回 0
return 0;
}
}
/// <summary>
/// 判断两个结果是否相等

View File

@ -67,7 +67,11 @@ public readonly struct Result : IEquatable<Result>
/// <param name="ex">失败的异常</param>
/// <returns>失败结果</returns>
[Pure]
public static Result Failure(Exception ex) => new(false, ex);
public static Result Failure(Exception ex)
{
ArgumentNullException.ThrowIfNull(ex);
return new(false, ex);
}
/// <summary>
/// 根据错误消息创建失败结果
@ -75,7 +79,11 @@ public readonly struct Result : IEquatable<Result>
/// <param name="message">错误消息</param>
/// <returns>失败结果</returns>
[Pure]
public static Result Failure(string message) => new(false, new Exception(message));
public static Result Failure(string message)
{
ArgumentException.ThrowIfNullOrWhiteSpace(message);
return new(false, new Exception(message));
}
/// <summary>
/// 根据成功或失败状态分别执行不同的处理逻辑