From 1cb7dfdb1401f26cb624eda90d5e9df72781ae6e Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Wed, 25 Feb 2026 15:09:51 +0800 Subject: [PATCH] =?UTF-8?q?feat(functional):=20=E6=B7=BB=E5=8A=A0=E5=87=BD?= =?UTF-8?q?=E6=95=B0=E5=BC=8F=E7=BC=96=E7=A8=8B=E6=94=AF=E6=8C=81=E5=92=8C?= =?UTF-8?q?Result=E7=B1=BB=E5=9E=8B=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 实现了Result和Result结构体,提供函数式错误处理 - 添加了AsyncExtensions中的函数式编程命名空间引用 - 在FunctionExtensions中添加函数式编程相关引用 - 从项目文件中移除LanguageExt.Core依赖包 - 重构GlobalUsings.cs移除不必要的全局引用 - 添加ResultExtensions扩展方法支持函数式操作 - 实现Map、Bind、Match等核心函数式编程方法 - 添加异步操作支持和安全执行委托功能 --- .../functional/ResultTests.cs | 0 GFramework.Core/GFramework.Core.csproj | 1 - GFramework.Core/GlobalUsings.cs | 5 +- GFramework.Core/extensions/AsyncExtensions.cs | 2 + .../extensions/ResultExtensions.cs | 254 ++++++++++++ GFramework.Core/functional/Result.T.cs | 376 ++++++++++++++++++ GFramework.Core/functional/Result.cs | 160 ++++++++ .../functions/FunctionExtensions.cs | 1 + 8 files changed, 794 insertions(+), 5 deletions(-) create mode 100644 GFramework.Core.Tests/functional/ResultTests.cs create mode 100644 GFramework.Core/extensions/ResultExtensions.cs create mode 100644 GFramework.Core/functional/Result.T.cs create mode 100644 GFramework.Core/functional/Result.cs diff --git a/GFramework.Core.Tests/functional/ResultTests.cs b/GFramework.Core.Tests/functional/ResultTests.cs new file mode 100644 index 0000000..e69de29 diff --git a/GFramework.Core/GFramework.Core.csproj b/GFramework.Core/GFramework.Core.csproj index 5433802..05baa84 100644 --- a/GFramework.Core/GFramework.Core.csproj +++ b/GFramework.Core/GFramework.Core.csproj @@ -11,7 +11,6 @@ - diff --git a/GFramework.Core/GlobalUsings.cs b/GFramework.Core/GlobalUsings.cs index 05d852c..4d27181 100644 --- a/GFramework.Core/GlobalUsings.cs +++ b/GFramework.Core/GlobalUsings.cs @@ -15,7 +15,4 @@ global using System; global using System.Collections.Generic; global using System.Linq; global using System.Threading; -global using System.Threading.Tasks; -global using LanguageExt.Common; -global using LanguageExt.Effects; -global using LanguageExt.Pretty; \ No newline at end of file +global using System.Threading.Tasks; \ No newline at end of file diff --git a/GFramework.Core/extensions/AsyncExtensions.cs b/GFramework.Core/extensions/AsyncExtensions.cs index 2905b66..0b4b890 100644 --- a/GFramework.Core/extensions/AsyncExtensions.cs +++ b/GFramework.Core/extensions/AsyncExtensions.cs @@ -1,3 +1,5 @@ +using GFramework.Core.Functional; + namespace GFramework.Core.extensions; /// diff --git a/GFramework.Core/extensions/ResultExtensions.cs b/GFramework.Core/extensions/ResultExtensions.cs new file mode 100644 index 0000000..f47a3e7 --- /dev/null +++ b/GFramework.Core/extensions/ResultExtensions.cs @@ -0,0 +1,254 @@ +// 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; + +namespace GFramework.Core.Extensions; + +/// +/// Result 类型的扩展方法,提供函数式编程支持 +/// +public static class ResultExtensions +{ + #region 聚合 + + /// + /// 将多个结果合并,全部成功则返回值列表,遇到第一个失败即短路返回 + /// + /// + /// + /// var combined = new[] + /// { + /// Result<int>.Succeed(1), + /// Result<int>.Succeed(2), + /// Result<int>.Succeed(3) + /// }.Combine(); // Result<List<int>> [1, 2, 3] + /// + /// + public static Result> Combine( + this IEnumerable> results) + { + ArgumentNullException.ThrowIfNull(results); + + var values = new List(); + foreach (var result in results) + { + if (result.IsFaulted) + return Result>.Fail(result.Exception); + values.Add(result.Match(succ: v => v, fail: _ => default!)); + } + + return Result>.Succeed(values); + } + + #endregion + + #region 变换 + + /// + /// 映射成功值到新类型 + /// + /// + /// + /// var result = Result<int>.Succeed(42); + /// var mapped = result.Map(x => x.ToString()); // Result<string> "42" + /// + /// + public static Result Map( + this Result result, + Func mapper) + { + ArgumentNullException.ThrowIfNull(mapper); + return result.Map(mapper); + } + + /// + /// 绑定操作,将结果链式转换 + /// + /// + /// + /// var result = Result<int>.Succeed(42); + /// var bound = result.Bind(x => x > 0 + /// ? Result<string>.Succeed(x.ToString()) + /// : Result<string>.Fail(new ArgumentException("Value must be positive"))); + /// + /// + public static Result Bind( + this Result result, + Func> binder) + { + ArgumentNullException.ThrowIfNull(binder); + return result.Bind(binder); + } + + #endregion + + #region 副作用 + + /// + /// 成功时执行副作用,返回原始结果(可链式调用) + /// + /// + /// + /// Result<int>.Succeed(42) + /// .OnSuccess(x => Console.WriteLine($"Value: {x}")) + /// .OnFailure(ex => Console.WriteLine($"Error: {ex.Message}")); + /// + /// + public static Result OnSuccess( + this Result result, + Action action) + { + ArgumentNullException.ThrowIfNull(action); + result.IfSucc(action); + return result; + } + + /// + /// 失败时执行副作用,返回原始结果(可链式调用) + /// + public static Result OnFailure( + this Result result, + Action action) + { + ArgumentNullException.ThrowIfNull(action); + result.IfFail(action); + return result; + } + + #endregion + + #region 验证 + + /// + /// 确保成功值满足条件,否则转换为失败 + /// + /// + /// + /// var result = Result<int>.Succeed(42) + /// .Ensure(x => x > 0, "Value must be positive") + /// .Ensure(x => x < 100, "Value must be less than 100"); + /// + /// + public static Result Ensure( + this Result result, + Func predicate, + string errorMessage) + { + ArgumentNullException.ThrowIfNull(predicate); + ArgumentException.ThrowIfNullOrWhiteSpace(errorMessage); + + if (result.IsFaulted) return result; + + return result.Match( + succ: value => predicate(value) + ? result + : Result.Fail(new ArgumentException(errorMessage)), + fail: _ => result + ); + } + + /// + /// Ensure 的异常重载,可以传入更具体的异常类型 + /// + public static Result Ensure( + this Result result, + Func predicate, + Func exceptionFactory) + { + ArgumentNullException.ThrowIfNull(predicate); + ArgumentNullException.ThrowIfNull(exceptionFactory); + + if (result.IsFaulted) return result; + + return result.Match( + succ: value => predicate(value) + ? result + : Result.Fail(exceptionFactory(value)), + fail: _ => result + ); + } + + #endregion + + #region 安全执行 + + /// + /// 安全执行同步委托,自动捕获异常为 Result + /// + /// + /// + /// var result = ResultExtensions.Try(() => int.Parse("42")); + /// + /// + public static Result Try(Func func) + { + ArgumentNullException.ThrowIfNull(func); + return Result.Try(func); + } + + /// + /// 安全执行异步委托,自动捕获异常为 Result + /// + /// + /// + /// var result = await ResultExtensions.TryAsync(async () => await GetDataAsync()); + /// + /// + public static async Task> TryAsync(Func> func) + { + ArgumentNullException.ThrowIfNull(func); + try + { + return Result.Succeed(await func()); + } + catch (Exception ex) + { + return Result.Fail(ex); + } + } + + #endregion + + #region 类型转换 + + /// + /// 成功时返回值,失败时返回 null(值类型) + /// + public static T? ToNullable(this Result result) where T : struct => + result.IsSuccess + ? result.Match(succ: v => (T?)v, fail: _ => null) + : null; + + /// + /// 将可空引用类型转换为 Result + /// + public static Result ToResult( + this T? value, + string errorMessage = "Value is null") where T : class => + value is not null + ? Result.Succeed(value) + : Result.Fail(new ArgumentNullException(errorMessage)); + + /// + /// 将可空值类型转换为 Result + /// + public static Result ToResult( + this T? value, + string errorMessage = "Value is null") where T : struct => + value.HasValue + ? Result.Succeed(value.Value) + : Result.Fail(new ArgumentNullException(errorMessage)); + + #endregion +} \ No newline at end of file diff --git a/GFramework.Core/functional/Result.T.cs b/GFramework.Core/functional/Result.T.cs new file mode 100644 index 0000000..9d2466b --- /dev/null +++ b/GFramework.Core/functional/Result.T.cs @@ -0,0 +1,376 @@ +// 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 System.Diagnostics.Contracts; + +namespace GFramework.Core.Functional; + +/// +/// 表示一个操作的结果,可能是成功值或异常 +/// +public readonly struct Result : IEquatable>, IComparable> +{ + private readonly A? _value; + private readonly Exception? _exception; + + // ------------------------------------------------------------------ 状态枚举 + + /// + /// 结果状态枚举,表示结果的不同状态 + /// + private enum ResultState : byte + { + Bottom, + Success, + Faulted + } + + private readonly ResultState _state; + + // ------------------------------------------------------------------ 静态默认值 + + /// + /// 表示未初始化的 Bottom 状态 + /// + public static readonly Result Bottom = default; + + // ------------------------------------------------------------------ 构造器 + + /// + /// 构造成功结果 + /// + /// 成功的值 + public Result(A value) + { + _state = ResultState.Success; + _value = value; + _exception = null; + } + + /// + /// 构造失败结果 + /// + /// 失败的异常 + public Result(Exception exception) + { + _state = ResultState.Faulted; + _exception = exception ?? throw new ArgumentNullException(nameof(exception)); + _value = default; + } + + // ------------------------------------------------------------------ 隐式转换 + + /// + /// 隐式将值转换为成功结果 + /// + /// 要转换的值 + /// 成功结果 + [Pure] + public static implicit operator Result(A value) => new(value); + + // ------------------------------------------------------------------ 状态属性 + + /// + /// 判断结果是否为成功状态 + /// + [Pure] + public bool IsSuccess => _state == ResultState.Success; + + /// + /// 判断结果是否为失败状态 + /// + [Pure] + public bool IsFaulted => _state == ResultState.Faulted; + + + /// + /// 判断结果是否为未初始化的 Bottom 状态 + /// + [Pure] + public bool IsBottom => _state == ResultState.Bottom; + + /// + /// 获取内部异常,若为 Bottom 状态则抛出 InvalidOperationException + /// + [Pure] + public Exception Exception => _exception + ?? new InvalidOperationException("Result is in Bottom state."); + + // ------------------------------------------------------------------ 取值 + + /// + /// 若成功则返回值,若失败则返回默认值 + /// + /// 失败时返回的默认值 + /// 成功时的值或默认值 + [Pure] + public A IfFail(A defaultValue) => IsSuccess ? _value! : defaultValue; + + /// + /// 若成功则返回值,若失败则通过委托处理异常 + /// + /// 处理异常的委托 + /// 成功时的值或委托处理后的结果 + [Pure] + public A IfFail(Func f) => IsSuccess ? _value! : f(Exception); + + /// + /// 若失败则执行副作用 + /// + /// 处理异常的副作用委托 + public void IfFail(Action f) + { + if (IsFaulted) f(Exception); + } + + /// + /// 若成功则执行副作用 + /// + /// 处理成功值的副作用委托 + public void IfSucc(Action f) + { + if (IsSuccess) f(_value!); + } + + // ------------------------------------------------------------------ 变换 + + /// + /// 成功时映射值,失败时透传异常 + /// + /// 映射后的类型 + /// 映射函数 + /// 映射后的结果 + [Pure] + public Result Map(Func f) => + IsSuccess ? new Result(f(_value!)) : new Result(Exception); + + /// + /// 成功时绑定到新 Result,失败时透传异常 + /// + /// 绑定后的类型 + /// 绑定函数 + /// 绑定后的结果 + [Pure] + public Result Bind(Func> binder) => + IsSuccess ? binder(_value!) : new Result(Exception); + + /// + /// 异步映射 + /// + /// 映射后的类型 + /// 异步映射函数 + /// 异步映射后的结果 + [Pure] + public async Task> MapAsync(Func> f) => + IsSuccess ? new Result(await f(_value!)) : new Result(Exception); + + // ------------------------------------------------------------------ 模式匹配 + + /// + /// 对成功/失败两种情况分别处理并返回值 + /// + /// 返回值类型 + /// 处理成功值的函数 + /// 处理异常的函数 + /// 处理后的结果 + [Pure] + public R Match(Func succ, Func fail) => + IsSuccess ? succ(_value!) : fail(Exception); + + /// + /// 对成功/失败两种情况分别执行副作用 + /// + /// 处理成功值的副作用委托 + /// 处理异常的副作用委托 + public void Match(Action succ, Action fail) + { + if (IsSuccess) succ(_value!); + else fail(Exception); + } + + // ------------------------------------------------------------------ 静态工厂(语义更清晰) + + /// + /// 创建成功结果 + /// + /// 成功的值 + /// 成功结果 + [Pure] + public static Result Succeed(A value) => new(value); + + /// + /// 创建成功结果(别名) + /// + /// 成功的值 + /// 成功结果 + [Pure] + public static Result Success(A value) => new(value); + + /// + /// 创建失败结果 + /// + /// 失败的异常 + /// 失败结果 + [Pure] + public static Result Fail(Exception ex) => new(ex); + + /// + /// 创建失败结果(别名) + /// + /// 失败的异常 + /// 失败结果 + [Pure] + public static Result Failure(Exception ex) => new(ex); + + /// + /// 根据错误消息创建失败结果 + /// + /// 错误消息 + /// 失败结果 + [Pure] + public static Result Failure(string message) => new(new Exception(message)); + + /// + /// 安全执行委托,自动捕获异常 + /// + /// 要执行的委托 + /// 执行结果 + public static Result Try(Func f) + { + try + { + return new Result(f()); + } + catch (Exception ex) + { + return new Result(ex); + } + } + + // ------------------------------------------------------------------ 相等 / 比较 + + /// + /// 判断两个结果是否相等 + /// + /// 另一个结果 + /// 若相等返回 true,否则返回 false + [Pure] + public bool Equals(Result other) + { + if (_state != other._state) return false; + if (IsSuccess) return EqualityComparer.Default.Equals(_value, other._value); + if (IsFaulted) + return Exception.GetType() == other.Exception.GetType() + && Exception.Message == other.Exception.Message; + return true; // both Bottom + } + + /// + /// 判断对象是否与当前结果相等 + /// + /// 要比较的对象 + /// 若相等返回 true,否则返回 false + [Pure] + public override bool Equals(object? obj) => obj is Result other && Equals(other); + + /// + /// 获取结果的哈希码 + /// + /// 哈希码 + [Pure] + public override int GetHashCode() => IsSuccess + ? HashCode.Combine(0, _value) + : HashCode.Combine(1, Exception.GetType(), Exception.Message); + + /// + /// 比较两个结果的大小 + /// + /// 另一个结果 + /// 比较结果 + [Pure] + public int CompareTo(Result other) + { + // Bottom < Faulted < Success + if (_state != other._state) return _state.CompareTo(other._state); + if (!IsSuccess) return 0; + return Comparer.Default.Compare(_value, other._value); + } + + /// + /// 判断两个结果是否相等 + /// + /// 第一个结果 + /// 第二个结果 + /// 若相等返回 true,否则返回 false + [Pure] + public static bool operator ==(Result a, Result b) => a.Equals(b); + + /// + /// 判断两个结果是否不相等 + /// + /// 第一个结果 + /// 第二个结果 + /// 若不相等返回 true,否则返回 false + [Pure] + public static bool operator !=(Result a, Result b) => !a.Equals(b); + + /// + /// 判断第一个结果是否小于第二个结果 + /// + /// 第一个结果 + /// 第二个结果 + /// 若小于返回 true,否则返回 false + [Pure] + public static bool operator <(Result a, Result b) => a.CompareTo(b) < 0; + + /// + /// 判断第一个结果是否小于等于第二个结果 + /// + /// 第一个结果 + /// 第二个结果 + /// 若小于等于返回 true,否则返回 false + [Pure] + public static bool operator <=(Result a, Result b) => a.CompareTo(b) <= 0; + + /// + /// 判断第一个结果是否大于第二个结果 + /// + /// 第一个结果 + /// 第二个结果 + /// 若大于返回 true,否则返回 false + [Pure] + public static bool operator >(Result a, Result b) => a.CompareTo(b) > 0; + + /// + /// 判断第一个结果是否大于等于第二个结果 + /// + /// 第一个结果 + /// 第二个结果 + /// 若大于等于返回 true,否则返回 false + [Pure] + public static bool operator >=(Result a, Result b) => a.CompareTo(b) >= 0; + + // ------------------------------------------------------------------ 调试 + + /// + /// 返回结果的字符串表示 + /// + /// 结果的字符串表示 + [Pure] + public override string ToString() => _state switch + { + ResultState.Success => _value?.ToString() ?? "(null)", + ResultState.Faulted => $"Fail({Exception.Message})", + _ => "(Bottom)" + }; +} \ No newline at end of file diff --git a/GFramework.Core/functional/Result.cs b/GFramework.Core/functional/Result.cs new file mode 100644 index 0000000..71cd4e7 --- /dev/null +++ b/GFramework.Core/functional/Result.cs @@ -0,0 +1,160 @@ +// 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 System.Diagnostics.Contracts; + +namespace GFramework.Core.Functional; + +/// +/// 表示一个无值的操作结果,仅包含成功或失败状态 +/// +public readonly struct Result : IEquatable +{ + private readonly Exception? _exception; + private readonly bool _isSuccess; + + /// + /// 私有构造函数,用于创建 Result 实例 + /// + /// 是否为成功状态 + /// 失败时的异常信息 + private Result(bool isSuccess, Exception? exception) + { + _isSuccess = isSuccess; + _exception = exception; + } + + /// + /// 判断结果是否为成功状态 + /// + [Pure] + public bool IsSuccess => _isSuccess; + + /// + /// 判断结果是否为失败状态 + /// + [Pure] + public bool IsFailure => !_isSuccess; + + /// + /// 获取失败时的异常信息,若为成功状态则抛出 InvalidOperationException + /// + [Pure] + public Exception Error => IsFailure + ? _exception! + : throw new InvalidOperationException("Cannot access Error on a successful Result."); + + /// + /// 创建成功结果 + /// + /// 成功结果 + [Pure] + public static Result Success() => new(true, null); + + /// + /// 创建失败结果 + /// + /// 失败的异常 + /// 失败结果 + [Pure] + public static Result Failure(Exception ex) => new(false, ex); + + /// + /// 根据错误消息创建失败结果 + /// + /// 错误消息 + /// 失败结果 + [Pure] + public static Result Failure(string message) => new(false, new Exception(message)); + + /// + /// 根据成功或失败状态分别执行不同的处理逻辑 + /// + /// 返回值类型 + /// 成功时执行的函数 + /// 失败时执行的函数 + /// 处理后的结果 + public R Match(Func onSuccess, Func onFailure) => + IsSuccess ? onSuccess() : onFailure(_exception!); + + /// + /// 将当前无值的 Result 提升为带值的 Result + /// + /// 值的类型 + /// 成功时关联的值 + /// 带值的 Result + [Pure] + public Result ToResult(A value) => + IsSuccess ? Result.Success(value) : Result.Failure(_exception!); + + /// + /// 判断当前 Result 是否与另一个 Result 相等 + /// + /// 另一个 Result + /// 若相等返回 true,否则返回 false + [Pure] + public bool Equals(Result other) + { + // 比较状态和异常信息 + return _isSuccess == other._isSuccess && + (!IsFailure || (_exception?.GetType() == other._exception?.GetType() && + _exception?.Message == other._exception?.Message)); + } + + /// + /// 判断当前对象是否与另一个对象相等 + /// + /// 另一个对象 + /// 若相等返回 true,否则返回 false + [Pure] + public override bool Equals(object? obj) => obj is Result other && Equals(other); + + /// + /// 获取当前 Result 的哈希码 + /// + /// 哈希码 + [Pure] + public override int GetHashCode() + { + // 根据状态和异常信息生成哈希码 + return IsSuccess + ? HashCode.Combine(true) + : HashCode.Combine(false, _exception?.GetType(), _exception?.Message); + } + + /// + /// 判断两个 Result 是否相等 + /// + /// 第一个 Result + /// 第二个 Result + /// 若相等返回 true,否则返回 false + [Pure] + public static bool operator ==(Result a, Result b) => a.Equals(b); + + /// + /// 判断两个 Result 是否不相等 + /// + /// 第一个 Result + /// 第二个 Result + /// 若不相等返回 true,否则返回 false + [Pure] + public static bool operator !=(Result a, Result b) => !a.Equals(b); + + /// + /// 返回当前 Result 的字符串表示 + /// + /// Result 的字符串表示 + [Pure] + public override string ToString() => + IsSuccess ? "Success" : $"Fail({_exception?.Message ?? "Unknown"})"; +} \ No newline at end of file diff --git a/GFramework.Core/functional/functions/FunctionExtensions.cs b/GFramework.Core/functional/functions/FunctionExtensions.cs index c9b38d4..ce1bd1f 100644 --- a/GFramework.Core/functional/functions/FunctionExtensions.cs +++ b/GFramework.Core/functional/functions/FunctionExtensions.cs @@ -12,6 +12,7 @@ // limitations under the License. using System.Collections.Concurrent; +using GFramework.Core.Functional; namespace GFramework.Core.functional.functions;