From df0f00bd9ec121aaa6bd7e5643d0fd7443623ab4 Mon Sep 17 00:00:00 2001
From: GeWuYou <95328647+GeWuYou@users.noreply.github.com>
Date: Thu, 26 Feb 2026 10:06:28 +0800
Subject: [PATCH] =?UTF-8?q?refactor(core):=20=E9=87=8D=E6=9E=84=E6=A0=B8?=
=?UTF-8?q?=E5=BF=83=E5=BA=93=E4=BB=A3=E7=A0=81=E7=BB=93=E6=9E=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 将异步扩展方法移至新的 AsyncFunctionalExtensions 类中
- 删除重复的数值扩展、对象扩展和字符串扩展方法
- 添加 Option 函数式编程类型实现
- 重命名 ResultExtensions 文件路径
- 修复 ResultExtensions 中的错误处理逻辑
- 更新命名空间以符合新的包结构
---
GFramework.Core/extensions/AsyncExtensions.cs | 128 -------
.../extensions/NumericExtensions.cs | 30 --
.../extensions/ObjectExtensions.cs | 21 --
.../extensions/StringExtensions.cs | 34 +-
GFramework.Core/functional/Option.cs | 316 ++++++++++++++++++
.../async/AsyncFunctionalExtensions.cs | 110 ++++++
.../result}/ResultExtensions.cs | 16 +-
7 files changed, 434 insertions(+), 221 deletions(-)
create mode 100644 GFramework.Core/functional/Option.cs
create mode 100644 GFramework.Core/functional/async/AsyncFunctionalExtensions.cs
rename GFramework.Core/{extensions => functional/result}/ResultExtensions.cs (95%)
diff --git a/GFramework.Core/extensions/AsyncExtensions.cs b/GFramework.Core/extensions/AsyncExtensions.cs
index 0b4b890..2b640dd 100644
--- a/GFramework.Core/extensions/AsyncExtensions.cs
+++ b/GFramework.Core/extensions/AsyncExtensions.cs
@@ -1,5 +1,3 @@
-using GFramework.Core.Functional;
-
namespace GFramework.Core.extensions;
///
@@ -99,132 +97,6 @@ public static class AsyncExtensions
}
}
- ///
- /// 为任务工厂添加重试机制
- ///
- /// 任务结果类型
- /// 任务工厂函数
- /// 最大重试次数
- /// 重试间隔
- /// 判断是否应该重试的函数,默认对所有异常重试
- /// 当为 true 时直接抛出原始异常,否则包装为 AggregateException
- /// 任务结果
- /// 当 taskFactory 为 null 时抛出
- /// 当 maxRetries 小于 0 时抛出
- ///
- ///
- /// var result = await (() => UnreliableOperation())
- /// .WithRetry(maxRetries: 3, delay: TimeSpan.FromSeconds(1));
- ///
- ///
- public static async Task WithRetry(
- this Func> taskFactory,
- int maxRetries,
- TimeSpan delay,
- Func? shouldRetry = null,
- bool throwOriginal = false)
- {
- ArgumentNullException.ThrowIfNull(taskFactory);
-
- if (maxRetries < 0)
- throw new ArgumentOutOfRangeException(nameof(maxRetries), "最大重试次数不能为负数");
-
- shouldRetry ??= _ => true;
-
- for (var attempt = 0; attempt <= maxRetries; attempt++)
- {
- try
- {
- return await taskFactory();
- }
- catch (Exception ex)
- {
- // 若还有重试机会且允许重试,则等待后继续;否则统一包装为 AggregateException 抛出
- if (attempt < maxRetries && shouldRetry(ex))
- {
- await Task.Delay(delay);
- }
- else
- {
- if (throwOriginal)
- throw;
-
- throw new AggregateException($"操作在 {attempt} 次重试后仍然失败", ex);
- }
- }
- }
-
- // 理论上不可达,仅满足编译器要求
- throw new AggregateException($"操作在 {maxRetries} 次重试后仍然失败");
- }
-
- ///
- /// 安全执行异步操作,将异常包装为 Result 类型
- ///
- /// 任务结果类型
- /// 要执行的异步函数
- /// 包含结果或异常的 Result 对象
- /// 当 func 为 null 时抛出
- ///
- ///
- /// var result = await (() => RiskyOperation()).TryAsync();
- /// result.Match(
- /// value => Console.WriteLine($"成功: {value}"),
- /// error => Console.WriteLine($"失败: {error.Message}")
- /// );
- ///
- ///
- public static async Task> TryAsync(this Func> func)
- {
- ArgumentNullException.ThrowIfNull(func);
-
- try
- {
- var result = await func();
- return new Result(result);
- }
- catch (Exception ex)
- {
- return new Result(ex);
- }
- }
-
- ///
- /// 等待所有任务完成
- ///
- /// 任务集合
- /// 当 tasks 为 null 时抛出
- ///
- ///
- /// var tasks = new[] { Task1(), Task2(), Task3() };
- /// await tasks.WhenAll();
- ///
- ///
- public static Task WhenAll(this IEnumerable tasks)
- {
- ArgumentNullException.ThrowIfNull(tasks);
- return Task.WhenAll(tasks);
- }
-
- ///
- /// 等待所有任务完成并返回结果数组
- ///
- /// 任务结果类型
- /// 任务集合
- /// 所有任务的结果数组
- /// 当 tasks 为 null 时抛出
- ///
- ///
- /// var tasks = new[] { GetValue1(), GetValue2(), GetValue3() };
- /// var results = await tasks.WhenAll();
- ///
- ///
- public static Task WhenAll(this IEnumerable> tasks)
- {
- ArgumentNullException.ThrowIfNull(tasks);
- return Task.WhenAll(tasks);
- }
-
///
/// 为任务添加失败回退机制
///
diff --git a/GFramework.Core/extensions/NumericExtensions.cs b/GFramework.Core/extensions/NumericExtensions.cs
index f2080db..d268806 100644
--- a/GFramework.Core/extensions/NumericExtensions.cs
+++ b/GFramework.Core/extensions/NumericExtensions.cs
@@ -5,36 +5,6 @@ namespace GFramework.Core.extensions;
///
public static class NumericExtensions
{
- ///
- /// 将值限制在指定的范围内
- ///
- /// 实现 IComparable 的类型
- /// 要限制的值
- /// 最小值
- /// 最大值
- /// 限制后的值
- /// 当 value、min 或 max 为 null 时抛出
- /// 当 min 大于 max 时抛出
- ///
- ///
- /// var value = 150;
- /// var clamped = value.Clamp(0, 100); // 返回 100
- ///
- ///
- public static T Clamp(this T value, T min, T max) where T : IComparable
- {
- if (min.CompareTo(max) > 0)
- throw new ArgumentException($"最小值 ({min}) 不能大于最大值 ({max})");
-
- if (value.CompareTo(min) < 0)
- return min;
-
- if (value.CompareTo(max) > 0)
- return max;
-
- return value;
- }
-
///
/// 检查值是否在指定范围内
///
diff --git a/GFramework.Core/extensions/ObjectExtensions.cs b/GFramework.Core/extensions/ObjectExtensions.cs
index be3f672..9e2ee24 100644
--- a/GFramework.Core/extensions/ObjectExtensions.cs
+++ b/GFramework.Core/extensions/ObjectExtensions.cs
@@ -136,27 +136,6 @@ public static class ObjectExtensions
return obj as T;
}
- ///
- /// 对对象执行指定操作后返回对象本身,
- /// 用于构建流式(链式)调用。
- ///
- /// 对象类型
- /// 源对象
- /// 要执行的操作
- /// 原对象
- ///
- ///
- /// obj.As<MyRule>()
- /// ?.Do(r => r.Initialize())
- /// ?.Do(r => r.Execute());
- ///
- ///
- public static T Do(this T obj, Action action)
- {
- action(obj);
- return obj;
- }
-
///
/// 根据对象的运行时类型,依次匹配并执行对应的处理逻辑,
/// 只会执行第一个匹配成功的处理器。
diff --git a/GFramework.Core/extensions/StringExtensions.cs b/GFramework.Core/extensions/StringExtensions.cs
index 8d88428..a3beb4b 100644
--- a/GFramework.Core/extensions/StringExtensions.cs
+++ b/GFramework.Core/extensions/StringExtensions.cs
@@ -5,38 +5,6 @@ namespace GFramework.Core.extensions;
///
public static class StringExtensions
{
- ///
- /// 指示指定的字符串是 null 还是空字符串
- ///
- /// 要测试的字符串
- /// 如果 str 参数为 null 或空字符串 (""),则为 true;否则为 false
- ///
- ///
- /// string? text = null;
- /// if (text.IsNullOrEmpty()) { /* ... */ }
- ///
- ///
- public static bool IsNullOrEmpty(this string? str)
- {
- return string.IsNullOrEmpty(str);
- }
-
- ///
- /// 指示指定的字符串是 null、空还是仅由空白字符组成
- ///
- /// 要测试的字符串
- /// 如果 str 参数为 null、空字符串或仅包含空白字符,则为 true;否则为 false
- ///
- ///
- /// string? text = " ";
- /// if (text.IsNullOrWhiteSpace()) { /* ... */ }
- ///
- ///
- public static bool IsNullOrWhiteSpace(this string? str)
- {
- return string.IsNullOrWhiteSpace(str);
- }
-
///
/// 如果字符串为空,则返回 null;否则返回原字符串
///
@@ -103,4 +71,4 @@ public static class StringExtensions
return string.Join(separator, values);
}
-}
+}
\ No newline at end of file
diff --git a/GFramework.Core/functional/Option.cs b/GFramework.Core/functional/Option.cs
new file mode 100644
index 0000000..709c343
--- /dev/null
+++ b/GFramework.Core/functional/Option.cs
@@ -0,0 +1,316 @@
+// 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.
+
+namespace GFramework.Core.Functional;
+
+///
+/// 表示可能存在或不存在的值,用于替代 null 引用的函数式编程类型
+///
+/// 值的类型
+public readonly struct Option
+{
+ private readonly T _value;
+ private readonly bool _isSome;
+
+ ///
+ /// 私有构造函数,用于创建 Some 状态
+ ///
+ private Option(T value)
+ {
+ _value = value;
+ _isSome = true;
+ }
+
+ ///
+ /// 判断是否有值
+ ///
+ public bool IsSome => _isSome;
+
+ ///
+ /// 判断是否无值
+ ///
+ public bool IsNone => !_isSome;
+
+ #region 工厂方法
+
+ ///
+ /// 创建包含值的 Option
+ ///
+ /// 要包装的值
+ /// 包含值的 Option
+ /// 当 value 为 null 时抛出
+ ///
+ ///
+ /// var option = Option<int>.Some(42);
+ ///
+ ///
+ public static Option Some(T value)
+ {
+ ArgumentNullException.ThrowIfNull(value);
+ return new Option(value);
+ }
+
+ ///
+ /// 表示无值的 Option
+ ///
+ public static Option None => default;
+
+ #endregion
+
+ #region 取值
+
+ ///
+ /// 获取值,如果无值则返回默认值
+ ///
+ /// 无值时返回的默认值
+ /// Option 中的值或默认值
+ ///
+ ///
+ /// var value = option.GetOrElse(0); // 如果无值则返回 0
+ ///
+ ///
+ public T GetOrElse(T defaultValue) => _isSome ? _value : defaultValue;
+
+ ///
+ /// 获取值,如果无值则通过工厂函数生成默认值
+ ///
+ /// 生成默认值的工厂函数
+ /// Option 中的值或工厂函数生成的值
+ /// 当 factory 为 null 时抛出
+ ///
+ ///
+ /// var value = option.GetOrElse(() => ExpensiveDefault());
+ ///
+ ///
+ public T GetOrElse(Func factory)
+ {
+ ArgumentNullException.ThrowIfNull(factory);
+ return _isSome ? _value : factory();
+ }
+
+ #endregion
+
+ #region 变换
+
+ ///
+ /// 映射值到新类型,如果无值则返回 None
+ ///
+ /// 映射后的类型
+ /// 映射函数
+ /// 映射后的 Option
+ /// 当 mapper 为 null 时抛出
+ ///
+ ///
+ /// var option = Option<int>.Some(42);
+ /// var mapped = option.Map(x => x.ToString()); // Option<string>.Some("42")
+ ///
+ ///
+ public Option Map(Func mapper)
+ {
+ ArgumentNullException.ThrowIfNull(mapper);
+ return _isSome ? Option.Some(mapper(_value)) : Option.None;
+ }
+
+ ///
+ /// 单子绑定操作,将 Option 链式转换
+ ///
+ /// 绑定后的类型
+ /// 绑定函数
+ /// 绑定后的 Option
+ /// 当 binder 为 null 时抛出
+ ///
+ ///
+ /// var option = Option<string>.Some("42");
+ /// var bound = option.Bind(s => int.TryParse(s, out var i)
+ /// ? Option<int>.Some(i)
+ /// : Option<int>.None);
+ ///
+ ///
+ public Option Bind(Func> binder)
+ {
+ ArgumentNullException.ThrowIfNull(binder);
+ return _isSome ? binder(_value) : Option.None;
+ }
+
+ #endregion
+
+ #region 过滤
+
+ ///
+ /// 根据条件过滤值,不满足条件则返回 None
+ ///
+ /// 过滤条件
+ /// 满足条件的 Option 或 None
+ /// 当 predicate 为 null 时抛出
+ ///
+ ///
+ /// var option = Option<int>.Some(42);
+ /// var filtered = option.Filter(x => x > 0); // Option<int>.Some(42)
+ /// var filtered2 = option.Filter(x => x < 0); // Option<int>.None
+ ///
+ ///
+ public Option Filter(Func predicate)
+ {
+ ArgumentNullException.ThrowIfNull(predicate);
+ return _isSome && predicate(_value) ? this : None;
+ }
+
+ #endregion
+
+ #region 模式匹配
+
+ ///
+ /// 模式匹配,根据是否有值执行不同的函数
+ ///
+ /// 返回值类型
+ /// 有值时执行的函数
+ /// 无值时执行的函数
+ /// 匹配结果
+ /// 当 some 或 none 为 null 时抛出
+ ///
+ ///
+ /// var result = option.Match(
+ /// some: value => $"Value: {value}",
+ /// none: () => "No value"
+ /// );
+ ///
+ ///
+ public TResult Match(Func some, Func none)
+ {
+ ArgumentNullException.ThrowIfNull(some);
+ ArgumentNullException.ThrowIfNull(none);
+ return _isSome ? some(_value) : none();
+ }
+
+ ///
+ /// 模式匹配(副作用版本),根据是否有值执行不同的操作
+ ///
+ /// 有值时执行的操作
+ /// 无值时执行的操作
+ /// 当 some 或 none 为 null 时抛出
+ ///
+ ///
+ /// option.Match(
+ /// some: value => Console.WriteLine($"Value: {value}"),
+ /// none: () => Console.WriteLine("No value")
+ /// );
+ ///
+ ///
+ public void Match(Action some, Action none)
+ {
+ ArgumentNullException.ThrowIfNull(some);
+ ArgumentNullException.ThrowIfNull(none);
+ if (_isSome)
+ some(_value);
+ else
+ none();
+ }
+
+ #endregion
+
+ #region 转换
+
+ ///
+ /// 转换为 Result 类型
+ ///
+ /// 无值时的错误消息
+ /// Result 类型
+ ///
+ ///
+ /// var result = option.ToResult("Value not found");
+ ///
+ ///
+ public Result ToResult(string errorMessage = "Value is None")
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(errorMessage);
+ return _isSome
+ ? Result.Succeed(_value)
+ : Result.Fail(new InvalidOperationException(errorMessage));
+ }
+
+ ///
+ /// 转换为可枚举集合(有值时包含一个元素,无值时为空集合)
+ ///
+ /// 可枚举集合
+ ///
+ ///
+ /// var items = option.ToEnumerable(); // 有值时: [value], 无值时: []
+ ///
+ ///
+ public IEnumerable ToEnumerable()
+ {
+ if (_isSome)
+ yield return _value;
+ }
+
+ #endregion
+
+ #region 隐式转换
+
+ ///
+ /// 从值隐式转换为 Option
+ ///
+ public static implicit operator Option(T value) =>
+ value is not null ? Some(value) : None;
+
+ #endregion
+
+ #region 相等性和比较
+
+ ///
+ /// 判断两个 Option 是否相等
+ ///
+ public bool Equals(Option other)
+ {
+ if (_isSome != other._isSome)
+ return false;
+
+ return !_isSome || EqualityComparer.Default.Equals(_value, other._value);
+ }
+
+ ///
+ /// 判断与对象是否相等
+ ///
+ public override bool Equals(object? obj) =>
+ obj is Option other && Equals(other);
+
+ ///
+ /// 获取哈希码
+ ///
+ public override int GetHashCode() =>
+ _isSome ? EqualityComparer.Default.GetHashCode(_value!) : 0;
+
+ ///
+ /// 相等运算符
+ ///
+ public static bool operator ==(Option left, Option right) =>
+ left.Equals(right);
+
+ ///
+ /// 不等运算符
+ ///
+ public static bool operator !=(Option left, Option right) =>
+ !left.Equals(right);
+
+ #endregion
+
+ #region 字符串表示
+
+ ///
+ /// 获取字符串表示
+ ///
+ public override string ToString() =>
+ _isSome ? $"Some({_value})" : "None";
+
+ #endregion
+}
\ No newline at end of file
diff --git a/GFramework.Core/functional/async/AsyncFunctionalExtensions.cs b/GFramework.Core/functional/async/AsyncFunctionalExtensions.cs
new file mode 100644
index 0000000..dc722b3
--- /dev/null
+++ b/GFramework.Core/functional/async/AsyncFunctionalExtensions.cs
@@ -0,0 +1,110 @@
+// 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.
+
+namespace GFramework.Core.Functional.Async;
+
+///
+/// 异步函数式编程扩展方法
+///
+public static class AsyncFunctionalExtensions
+{
+ ///
+ /// 为任务工厂添加重试机制
+ ///
+ /// 任务结果类型
+ /// 任务工厂函数
+ /// 最大重试次数
+ /// 重试间隔
+ /// 判断是否应该重试的函数,默认对所有异常重试
+ /// 当为 true 时直接抛出原始异常,否则包装为 AggregateException
+ /// 任务结果
+ /// 当 taskFactory 为 null 时抛出
+ /// 当 maxRetries 小于 0 时抛出
+ ///
+ ///
+ /// var result = await (() => UnreliableOperation())
+ /// .WithRetry(maxRetries: 3, delay: TimeSpan.FromSeconds(1));
+ ///
+ ///
+ public static async Task WithRetry(
+ this Func> taskFactory,
+ int maxRetries,
+ TimeSpan delay,
+ Func? shouldRetry = null,
+ bool throwOriginal = false)
+ {
+ ArgumentNullException.ThrowIfNull(taskFactory);
+
+ if (maxRetries < 0)
+ throw new ArgumentOutOfRangeException(nameof(maxRetries), "最大重试次数不能为负数");
+
+ shouldRetry ??= _ => true;
+
+ for (var attempt = 0; attempt <= maxRetries; attempt++)
+ {
+ try
+ {
+ return await taskFactory();
+ }
+ catch (Exception ex)
+ {
+ // 若还有重试机会且允许重试,则等待后继续;否则统一包装为 AggregateException 抛出
+ if (attempt < maxRetries && shouldRetry(ex))
+ {
+ await Task.Delay(delay);
+ }
+ else
+ {
+ if (throwOriginal)
+ throw;
+
+ throw new AggregateException($"操作在 {attempt} 次重试后仍然失败", ex);
+ }
+ }
+ }
+
+ // 理论上不可达,仅满足编译器要求
+ throw new AggregateException($"操作在 {maxRetries} 次重试后仍然失败");
+ }
+
+ ///
+ /// 安全执行异步操作,将异常包装为 Result 类型
+ ///
+ /// 任务结果类型
+ /// 要执行的异步函数
+ /// 包含结果或异常的 Result 对象
+ /// 当 func 为 null 时抛出
+ ///
+ ///
+ /// var result = await (() => RiskyOperation()).TryAsync();
+ /// result.Match(
+ /// value => Console.WriteLine($"成功: {value}"),
+ /// error => Console.WriteLine($"失败: {error.Message}")
+ /// );
+ ///
+ ///
+ public static async Task> TryAsync(this Func> func)
+ {
+ ArgumentNullException.ThrowIfNull(func);
+
+ try
+ {
+ var result = await func();
+ return new Result(result);
+ }
+ catch (Exception ex)
+ {
+ return new Result(ex);
+ }
+ }
+}
\ No newline at end of file
diff --git a/GFramework.Core/extensions/ResultExtensions.cs b/GFramework.Core/functional/result/ResultExtensions.cs
similarity index 95%
rename from GFramework.Core/extensions/ResultExtensions.cs
rename to GFramework.Core/functional/result/ResultExtensions.cs
index 0565a96..ffc588b 100644
--- a/GFramework.Core/extensions/ResultExtensions.cs
+++ b/GFramework.Core/functional/result/ResultExtensions.cs
@@ -2,18 +2,19 @@
// 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;
+namespace GFramework.Core.functional.result;
///
/// Result 类型的扩展方法,提供函数式编程支持
@@ -46,11 +47,8 @@ public static class ResultExtensions
if (result.IsFaulted)
return Result>.Fail(result.Exception);
- if (result.IsBottom)
- return Result>.Fail(new InvalidOperationException("Cannot combine Bottom results"));
-
if (result.IsSuccess)
- result.IfSucc(values.Add);
+ values.Add(result.Match(succ: v => v, fail: _ => throw new InvalidOperationException()));
}
return Result>.Succeed(values);
@@ -265,7 +263,7 @@ public static class ResultExtensions
string errorMessage = "Value is null") where T : class =>
value is not null
? Result.Succeed(value)
- : Result.Fail(new ArgumentNullException(nameof(value), errorMessage));
+ : Result.Fail(new ArgumentNullException(errorMessage));
///
/// 将可空值类型转换为 Result
@@ -275,7 +273,7 @@ public static class ResultExtensions
string errorMessage = "Value is null") where T : struct =>
value.HasValue
? Result.Succeed(value.Value)
- : Result.Fail(new ArgumentNullException(nameof(value), errorMessage));
+ : Result.Fail(new ArgumentNullException(errorMessage));
#endregion
}
\ No newline at end of file