From 61349a83ab312bb761caa0764bb4a85babea3431 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Wed, 25 Feb 2026 15:44:06 +0800 Subject: [PATCH] =?UTF-8?q?feat(functional):=20=E5=A2=9E=E5=BC=BAResult?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E5=8A=9F=E8=83=BD=E5=B9=B6=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=AE=8C=E6=95=B4=E6=B5=8B=E8=AF=95=E8=A6=86=E7=9B=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 为Result.Failure方法添加参数验证,确保异常和消息参数不为空 - 重构Result内部状态枚举顺序以优化比较逻辑 - 为Result的Map、Bind和MapAsync方法添加参数验证和异常处理 - 在Result比较逻辑中添加类型不可比较时的安全处理 - 添加ResultExtensions.BindAsync扩展方法支持异步绑定操作 - 重写Combine扩展方法中对失败结果的处理逻辑 - 添加完整的Result相关类型单元测试覆盖所有功能场景 - 为Result类型添加详细的XML文档注释和使用示例 --- .../extensions/ResultExtensionsTests.cs | 496 ++++++++++++++ .../functional/ResultTTests.cs | 603 ++++++++++++++++++ .../functional/ResultTests.cs | 432 +++++++++++++ .../extensions/ResultExtensions.cs | 26 +- GFramework.Core/functional/Result.T.cs | 46 +- GFramework.Core/functional/Result.cs | 12 +- 6 files changed, 1603 insertions(+), 12 deletions(-) create mode 100644 GFramework.Core.Tests/extensions/ResultExtensionsTests.cs create mode 100644 GFramework.Core.Tests/functional/ResultTTests.cs diff --git a/GFramework.Core.Tests/extensions/ResultExtensionsTests.cs b/GFramework.Core.Tests/extensions/ResultExtensionsTests.cs new file mode 100644 index 0000000..a65a2c4 --- /dev/null +++ b/GFramework.Core.Tests/extensions/ResultExtensionsTests.cs @@ -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; + +/// +/// ResultExtensions 扩展方法测试类 +/// +[TestFixture] +public class ResultExtensionsTests +{ + [Test] + public void Combine_Should_Return_Success_List_When_All_Results_Succeed() + { + var results = new[] + { + Result.Succeed(1), + Result.Succeed(2), + Result.Succeed(3) + }; + var combined = results.Combine(); + Assert.That(combined.IsSuccess, Is.True); + var list = combined.Match(succ: v => v, fail: _ => new List()); + 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.Succeed(1), + Result.Fail(exception), + Result.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>(); + var combined = results.Combine(); + Assert.That(combined.IsSuccess, Is.True); + var list = combined.Match(succ: v => v, fail: _ => new List()); + Assert.That(list, Is.Empty); + } + + [Test] + public void Combine_Should_Throw_ArgumentNullException_When_Collection_Is_Null() + { + Assert.Throws(() => ((IEnumerable>)null!).Combine()); + } + + [Test] + public void Combine_Should_Preserve_Order_Of_Values() + { + var results = new[] + { + Result.Succeed(3), + Result.Succeed(1), + Result.Succeed(2) + }; + var combined = results.Combine(); + var list = combined.Match(succ: v => v, fail: _ => new List()); + 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.Succeed(1), + Result.Fail(new Exception("Error")), + Result.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.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.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.Succeed(42); + Assert.Throws(() => result.Map(null!)); + } + + [Test] + public void Bind_Should_Chain_Success_Results() + { + var result = Result.Succeed(42); + var bound = result.Bind(x => Result.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.Fail(exception); + var bound = result.Bind(x => Result.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.Succeed(42); + Assert.Throws(() => result.Bind(null!)); + } + + [Test] + public void OnSuccess_Should_Execute_Action_When_Success() + { + var result = Result.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.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.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.Succeed(42); + Assert.Throws(() => result.OnSuccess(null!)); + } + + [Test] + public void OnFailure_Should_Execute_Action_When_Faulted() + { + var result = Result.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.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.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.Fail(new Exception("Error")); + Assert.Throws(() => result.OnFailure(null!)); + } + + [Test] + public void Ensure_Should_Return_Success_When_Predicate_Is_True() + { + var result = Result.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.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()); + } + + [Test] + public void Ensure_Should_Propagate_Existing_Failure() + { + var exception = new Exception("Original error"); + var result = Result.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.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.Succeed(42); + Assert.Throws(() => result.Ensure(null!, "Error")); + } + + [Test] + public void Ensure_Should_Throw_ArgumentException_When_Message_Is_NullOrWhiteSpace() + { + var result = Result.Succeed(42); + Assert.Throws(() => result.Ensure(x => true, "")); + Assert.Throws(() => result.Ensure(x => true, " ")); + } + + [Test] + public void Ensure_WithFactory_Should_Return_Success_When_Predicate_Is_True() + { + var result = Result.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.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()); + } + + [Test] + public void Ensure_WithFactory_Should_Pass_Value_To_Exception_Factory() + { + var result = Result.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.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.Succeed(42); + Assert.Throws(() => result.Ensure(null!, (Func)(_ => new Exception()))); + Assert.Throws(() => result.Ensure(x => true, (Func)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(() => throw new InvalidOperationException("Error")); + Assert.That(result.IsFaulted, Is.True); + Assert.That(result.Exception, Is.TypeOf()); + } + + [Test] + public void Try_Should_Throw_ArgumentNullException_When_Function_Is_Null() + { + Assert.Throws(() => ResultExtensions.Try(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(async () => + { + await Task.Delay(1); + throw new InvalidOperationException("Error"); + }); + Assert.That(result.IsFaulted, Is.True); + Assert.That(result.Exception, Is.TypeOf()); + } + + [Test] + public async Task TryAsync_Should_Handle_Synchronous_Exceptions() + { + var result = await ResultExtensions.TryAsync(() => throw new InvalidOperationException("Sync error")); + Assert.That(result.IsFaulted, Is.True); + } + + [Test] + public void TryAsync_Should_Throw_ArgumentNullException_When_Function_Is_Null() + { + Assert.ThrowsAsync(async () => await ResultExtensions.TryAsync(null!)); + } + + [Test] + public void ToNullable_Should_Return_Value_When_Success() + { + var result = Result.Succeed(42); + var nullable = result.ToNullable(); + Assert.That(nullable, Is.EqualTo(42)); + } + + [Test] + public void ToNullable_Should_Return_Null_When_Faulted() + { + var result = Result.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.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()); + } + + [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()); + } + + [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.Succeed(10) + .Map(x => x * 2) + .Bind(x => Result.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.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.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); + } +} \ No newline at end of file diff --git a/GFramework.Core.Tests/functional/ResultTTests.cs b/GFramework.Core.Tests/functional/ResultTTests.cs new file mode 100644 index 0000000..7ccbcf1 --- /dev/null +++ b/GFramework.Core.Tests/functional/ResultTTests.cs @@ -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; + +/// +/// Result<A> 泛型类型测试类 +/// +[TestFixture] +public class ResultTTests +{ + [Test] + public void Constructor_WithValue_Should_Create_Success_Result() + { + var result = new Result(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(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(() => new Result(null!)); + } + + [Test] + public void DefaultConstructor_Should_Create_Bottom_State() + { + var result = new Result(); + 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 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.Succeed(42); + Assert.That(result.IsSuccess, Is.True); + } + + [Test] + public void IsFaulted_Should_Return_True_When_Result_Is_Faulted() + { + var result = Result.Fail(new Exception("Error")); + Assert.That(result.IsFaulted, Is.True); + } + + [Test] + public void IsBottom_Should_Return_True_When_Result_Is_Bottom() + { + var result = Result.Bottom; + Assert.That(result.IsBottom, Is.True); + } + + [Test] + public void Exception_Should_Return_Exception_When_Faulted() + { + var exception = new InvalidOperationException("Test"); + var result = Result.Fail(exception); + Assert.That(result.Exception, Is.SameAs(exception)); + } + + [Test] + public void Exception_Should_Return_InvalidOperationException_When_Bottom() + { + var result = Result.Bottom; + Assert.That(result.Exception, Is.TypeOf()); + } + + [Test] + public void Exception_Should_Return_InvalidOperationException_When_Success() + { + var result = Result.Succeed(42); + Assert.That(result.Exception, Is.TypeOf()); + } + + [Test] + public void Succeed_Should_Create_Successful_Result() + { + var result = Result.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.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.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.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.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.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.Try(() => throw new InvalidOperationException("Error")); + Assert.That(result.IsFaulted, Is.True); + Assert.That(result.Exception, Is.TypeOf()); + } + + [Test] + public void IfFail_WithDefaultValue_Should_Return_Value_When_Success() + { + var result = Result.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.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.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.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.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.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.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.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.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.Succeed(42); + var mapped = result.Map(_ => null!); + Assert.That(mapped.IsSuccess, Is.True); + } + + [Test] + public void Map_Should_Not_Throw_When_Mapper_Returns_Null() + { + var result = Result.Succeed("test"); + Assert.DoesNotThrow(() => result.Map(_ => null)); + } + + [Test] + public void Bind_Should_Chain_Success_Results() + { + var result = Result.Succeed(42); + var bound = result.Bind(x => Result.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.Fail(exception); + var bound = result.Bind(x => Result.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.Succeed(42); + var exception = new Exception("Bind error"); + var bound = result.Bind(x => Result.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.Succeed(10) + .Bind(x => Result.Succeed(x * 2)) + .Bind(x => Result.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.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.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.Succeed(42); + var mapped = await result.MapAsync(async _ => + { + await Task.Delay(1); + throw new InvalidOperationException("Async error"); + }); + Assert.That(mapped.IsFaulted, Is.True); + Assert.That(mapped.Exception, Is.TypeOf()); + } + + [Test] + public void Match_WithFunctions_Should_Execute_Succ_When_Success() + { + var result = Result.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.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.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.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.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.Succeed(42); + var result2 = Result.Succeed(42); + Assert.That(result1.Equals(result2), Is.True); + } + + [Test] + public void Equals_Should_Return_False_When_Success_Values_Differ() + { + var result1 = Result.Succeed(42); + var result2 = Result.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.Fail(new InvalidOperationException("Error")); + var result2 = Result.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.Fail(new InvalidOperationException("Error")); + var result2 = Result.Fail(new ArgumentException("Error")); + Assert.That(result1.Equals(result2), Is.False); + } + + [Test] + public void Equals_Should_Return_True_When_Both_Bottom() + { + var result1 = Result.Bottom; + var result2 = new Result(); + Assert.That(result1.Equals(result2), Is.True); + } + + [Test] + public void Equals_Should_Return_False_When_States_Differ() + { + var result1 = Result.Succeed(42); + var result2 = Result.Fail(new Exception("Error")); + Assert.That(result1.Equals(result2), Is.False); + } + + [Test] + public void GetHashCode_Should_Be_Consistent_For_Equal_Results() + { + var result1 = Result.Succeed(42); + var result2 = Result.Succeed(42); + Assert.That(result1.GetHashCode(), Is.EqualTo(result2.GetHashCode())); + } + + [Test] + public void OperatorEquals_Should_Work_Correctly() + { + var result1 = Result.Succeed(42); + var result2 = Result.Succeed(42); + var result3 = Result.Succeed(43); + Assert.That(result1 == result2, Is.True); + Assert.That(result1 == result3, Is.False); + } + + [Test] + public void OperatorNotEquals_Should_Work_Correctly() + { + var result1 = Result.Succeed(42); + var result2 = Result.Succeed(43); + Assert.That(result1 != result2, Is.True); + } + + [Test] + public void CompareTo_Should_Order_Bottom_Before_Faulted() + { + var bottom = Result.Bottom; + var faulted = Result.Fail(new Exception("Error")); + Assert.That(bottom.CompareTo(faulted), Is.LessThan(0)); + } + + [Test] + public void CompareTo_Should_Order_Faulted_Before_Success() + { + var faulted = Result.Fail(new Exception("Error")); + var success = Result.Succeed(42); + Assert.That(faulted.CompareTo(success), Is.LessThan(0)); + } + + [Test] + public void CompareTo_Should_Compare_Success_Values_When_Both_Success() + { + var result1 = Result.Succeed(10); + var result2 = Result.Succeed(20); + Assert.That(result1.CompareTo(result2), Is.LessThan(0)); + } + + [Test] + public void CompareTo_Should_Return_Zero_When_Both_Faulted() + { + var result1 = Result.Fail(new Exception("Error1")); + var result2 = Result.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.Bottom; + var result2 = new Result(); + Assert.That(result1.CompareTo(result2), Is.EqualTo(0)); + } + + [Test] + public void OperatorLessThan_Should_Work_Correctly() + { + var result1 = Result.Succeed(10); + var result2 = Result.Succeed(20); + Assert.That(result1 < result2, Is.True); + } + + [Test] + public void OperatorLessThanOrEqual_Should_Work_Correctly() + { + var result1 = Result.Succeed(10); + var result2 = Result.Succeed(10); + Assert.That(result1 <= result2, Is.True); + } + + [Test] + public void OperatorGreaterThan_Should_Work_Correctly() + { + var result1 = Result.Succeed(20); + var result2 = Result.Succeed(10); + Assert.That(result1 > result2, Is.True); + } + + [Test] + public void OperatorGreaterThanOrEqual_Should_Work_Correctly() + { + var result1 = Result.Succeed(20); + var result2 = Result.Succeed(20); + Assert.That(result1 >= result2, Is.True); + } + + [Test] + public void CompareTo_Should_Handle_NonComparable_Types_Gracefully() + { + var result1 = Result.Succeed(new object()); + var result2 = Result.Succeed(new object()); + Assert.DoesNotThrow(() => result1.CompareTo(result2)); + } + + [Test] + public void ToString_Should_Return_Value_String_When_Success() + { + var result = Result.Succeed(42); + Assert.That(result.ToString(), Is.EqualTo("42")); + } + + [Test] + public void ToString_Should_Return_Fail_Message_When_Faulted() + { + var result = Result.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.Bottom; + Assert.That(result.ToString(), Is.EqualTo("(Bottom)")); + } + + [Test] + public void Success_WithNullValue_Should_Create_Valid_Result() + { + var result = Result.Succeed(null); + Assert.That(result.IsSuccess, Is.True); + } + + [Test] + public void Map_WithNullValue_Should_Handle_Correctly() + { + var result = Result.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.Succeed(null); + var result2 = Result.Succeed(null); + Assert.That(result1.Equals(result2), Is.True); + } + + [Test] + public void ImplicitConversion_WithNull_Should_Create_Success_With_Null() + { + Result result = null; + Assert.That(result.IsSuccess, Is.True); + } + + [Test] + public void Bottom_Should_Be_Readonly_And_Immutable() + { + var bottom1 = Result.Bottom; + var bottom2 = Result.Bottom; + Assert.That(bottom1.Equals(bottom2), Is.True); + } +} \ No newline at end of file diff --git a/GFramework.Core.Tests/functional/ResultTests.cs b/GFramework.Core.Tests/functional/ResultTests.cs index e69de29..a290e3f 100644 --- a/GFramework.Core.Tests/functional/ResultTests.cs +++ b/GFramework.Core.Tests/functional/ResultTests.cs @@ -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; + +/// +/// Result 类型测试类 +/// +[TestFixture] +public class ResultTests +{ + /// + /// 测试 Success 方法应该创建成功结果 + /// + [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); + } + + /// + /// 测试 Failure 方法使用异常应该创建失败结果 + /// + [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)); + } + + /// + /// 测试 Failure 方法使用消息应该创建带异常的失败结果 + /// + [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)); + } + + /// + /// 测试 Failure 方法使用 null 异常应该抛出 ArgumentNullException + /// + [Test] + public void Failure_WithNullException_Should_Throw_ArgumentNullException() + { + // Act & Assert + Assert.Throws(() => Result.Failure((Exception)null!)); + } + + /// + /// 测试 Failure 方法使用 null 或空消息应该抛出 ArgumentException + /// + [Test] + public void Failure_WithNullOrEmptyMessage_Should_Throw_ArgumentException() + { + // Act & Assert + Assert.Throws(() => Result.Failure(string.Empty)); + Assert.Throws(() => Result.Failure("")); + } + + /// + /// 测试 IsSuccess 属性在结果成功时应该返回 true + /// + [Test] + public void IsSuccess_Should_Return_True_When_Result_Is_Successful() + { + // Arrange + var result = Result.Success(); + + // Act & Assert + Assert.That(result.IsSuccess, Is.True); + } + + /// + /// 测试 IsFailure 属性在结果失败时应该返回 true + /// + [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); + } + + /// + /// 测试 Error 属性在结果失败时应该返回异常 + /// + [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)); + } + + /// + /// 测试 Error 属性在结果成功时应该抛出 InvalidOperationException + /// + [Test] + public void Error_Should_Throw_InvalidOperationException_When_Result_Is_Successful() + { + // Arrange + var result = Result.Success(); + + // Act & Assert + Assert.Throws(() => _ = result.Error); + } + + /// + /// 测试 Match 方法在结果成功时应该执行 onSuccess 委托 + /// + [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")); + } + + /// + /// 测试 Match 方法在结果失败时应该执行 onFailure 委托 + /// + [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")); + } + + /// + /// 测试 Match 方法应该将异常传递给 onFailure 处理器 + /// + [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)); + } + + /// + /// 测试 ToResult 方法应该将成功结果转换为泛型成功结果 + /// + [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)); + } + + /// + /// 测试 ToResult 方法应该将失败结果转换为泛型失败结果 + /// + [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)); + } + + /// + /// 测试 ToResult 方法应该保留异常信息 + /// + [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()); + } + + /// + /// 测试 Equals 方法在两个结果都成功时应该返回 true + /// + [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); + } + + /// + /// 测试 Equals 方法在两个结果都失败且异常类型和消息相同时应该返回 true + /// + [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); + } + + /// + /// 测试 Equals 方法在状态不同时应该返回 false + /// + [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); + } + + /// + /// 测试 Equals 方法在异常类型不同时应该返回 false + /// + [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); + } + + /// + /// 测试 Equals 方法在异常消息不同时应该返回 false + /// + [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); + } + + /// + /// 测试 GetHashCode 方法对于相等的结果应该返回一致的哈希码 + /// + [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)); + } + + /// + /// 测试 == 操作符应该正确工作 + /// + [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); + } + + /// + /// 测试 != 操作符应该正确工作 + /// + [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); + } + + /// + /// 测试 ToString 方法在结果成功时应该返回 "Success" + /// + [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")); + } + + /// + /// 测试 ToString 方法在结果失败时应该返回带消息的 "Fail" + /// + [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)")); + } +} \ No newline at end of file diff --git a/GFramework.Core/extensions/ResultExtensions.cs b/GFramework.Core/extensions/ResultExtensions.cs index f47a3e7..dd806ed 100644 --- a/GFramework.Core/extensions/ResultExtensions.cs +++ b/GFramework.Core/extensions/ResultExtensions.cs @@ -45,7 +45,9 @@ public static class ResultExtensions { if (result.IsFaulted) return Result>.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>.Succeed(values); @@ -91,6 +93,28 @@ public static class ResultExtensions return result.Bind(binder); } + /// + /// 异步绑定操作,将结果链式转换为异步结果 + /// + /// + /// + /// var result = Result<int>.Succeed(42); + /// var bound = await result.BindAsync(async x => + /// await GetUserAsync(x) is User user + /// User>.Succeed(user) + /// : Result<User>.Fail(new Exception("User not found"))); + /// + /// + public static async Task> BindAsync( + this Result result, + Func>> binder) + { + ArgumentNullException.ThrowIfNull(binder); + return result.IsSuccess + ? await binder(result.Match(succ: v => v, fail: _ => throw new InvalidOperationException())) + : Result.Fail(result.Exception); + } + #endregion #region 副作用 diff --git a/GFramework.Core/functional/Result.T.cs b/GFramework.Core/functional/Result.T.cs index 9d2466b..62bd2ef 100644 --- a/GFramework.Core/functional/Result.T.cs +++ b/GFramework.Core/functional/Result.T.cs @@ -27,12 +27,13 @@ public readonly struct Result : IEquatable>, IComparable> /// /// 结果状态枚举,表示结果的不同状态 + /// 排序: Bottom < Faulted < Success /// private enum ResultState : byte { Bottom, - Success, - Faulted + Faulted, + Success } private readonly ResultState _state; @@ -151,8 +152,11 @@ public readonly struct Result : IEquatable>, IComparable> /// 映射函数 /// 映射后的结果 [Pure] - public Result Map(Func f) => - IsSuccess ? new Result(f(_value!)) : new Result(Exception); + public Result Map(Func f) + { + ArgumentNullException.ThrowIfNull(f); + return IsSuccess ? new Result(f(_value!)) : new Result(Exception); + } /// /// 成功时绑定到新 Result,失败时透传异常 @@ -161,8 +165,11 @@ public readonly struct Result : IEquatable>, IComparable> /// 绑定函数 /// 绑定后的结果 [Pure] - public Result Bind(Func> binder) => - IsSuccess ? binder(_value!) : new Result(Exception); + public Result Bind(Func> binder) + { + ArgumentNullException.ThrowIfNull(binder); + return IsSuccess ? binder(_value!) : new Result(Exception); + } /// /// 异步映射 @@ -171,8 +178,20 @@ public readonly struct Result : IEquatable>, IComparable> /// 异步映射函数 /// 异步映射后的结果 [Pure] - public async Task> MapAsync(Func> f) => - IsSuccess ? new Result(await f(_value!)) : new Result(Exception); + public async Task> MapAsync(Func> f) + { + ArgumentNullException.ThrowIfNull(f); + if (!IsSuccess) return new Result(Exception); + + try + { + return new Result(await f(_value!)); + } + catch (Exception ex) + { + return new Result(ex); + } + } // ------------------------------------------------------------------ 模式匹配 @@ -303,7 +322,16 @@ public readonly struct Result : IEquatable>, IComparable> // Bottom < Faulted < Success if (_state != other._state) return _state.CompareTo(other._state); if (!IsSuccess) return 0; - return Comparer.Default.Compare(_value, other._value); + + try + { + return Comparer.Default.Compare(_value, other._value); + } + catch (ArgumentException) + { + // 类型不可比较时返回 0 + return 0; + } } /// diff --git a/GFramework.Core/functional/Result.cs b/GFramework.Core/functional/Result.cs index 71cd4e7..03508b5 100644 --- a/GFramework.Core/functional/Result.cs +++ b/GFramework.Core/functional/Result.cs @@ -67,7 +67,11 @@ public readonly struct Result : IEquatable /// 失败的异常 /// 失败结果 [Pure] - public static Result Failure(Exception ex) => new(false, ex); + public static Result Failure(Exception ex) + { + ArgumentNullException.ThrowIfNull(ex); + return new(false, ex); + } /// /// 根据错误消息创建失败结果 @@ -75,7 +79,11 @@ public readonly struct Result : IEquatable /// 错误消息 /// 失败结果 [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)); + } /// /// 根据成功或失败状态分别执行不同的处理逻辑