From fd3a9ae9e0b0645e0c4e26d40806ac6c6aa6ea86 Mon Sep 17 00:00:00 2001
From: GeWuYou <95328647+GeWuYou@users.noreply.github.com>
Date: Sat, 31 Jan 2026 21:25:27 +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=89=A9=E5=B1=95=E5=92=8C?=
=?UTF-8?q?Option=E7=B1=BB=E5=9E=8B=E6=94=AF=E6=8C=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 在FunctionExtensions中新增Map扩展方法用于对象映射
- 在PipeExtensions中新增On扩展方法用于值到函数的应用
- 移除Tap方法及相关测试以优化管道操作
- 新增NullableExtensions实现可空类型到Option的转换
- 新增Option结构体提供安全的可选值处理
- 新增OptionExtensions提供Map、Bind、Filter、Match等函数式操作
- 新增OptionValueExtensions提供GetOrElse和OrElse值提取方法
- 调整全局引用添加Concurrent集合支持
- 扩展IResetApplyAbleSettings接口添加Data属性
- 更新Godot设置类实现Data属性返回设置数据模型
---
.../functional/pipe/PipeExtensionsTests.cs | 66 +++-----------
.../functions/FunctionExtensions.cs | 19 +++-
.../functional/pipe/PipeExtensions.cs | 37 ++++----
.../functional/types/NullableExtensions.cs | 44 +++++++++
GFramework.Core/functional/types/Option.cs | 78 ++++++++++++++++
.../functional/types/OptionExtensions.cs | 89 +++++++++++++++++++
.../functional/types/OptionValueExtensions.cs | 56 ++++++++++++
.../setting/IResetApplyAbleSettings.cs | 9 +-
GFramework.Game/GlobalUsings.cs | 1 +
GFramework.Game/setting/SettingsModel.cs | 3 +-
.../setting/GodotAudioSettings.cs | 2 +
.../setting/GodotGraphicsSettings.cs | 2 +
.../setting/GodotLocalizationSettings.cs | 2 +
13 files changed, 327 insertions(+), 81 deletions(-)
create mode 100644 GFramework.Core/functional/types/NullableExtensions.cs
create mode 100644 GFramework.Core/functional/types/Option.cs
create mode 100644 GFramework.Core/functional/types/OptionExtensions.cs
create mode 100644 GFramework.Core/functional/types/OptionValueExtensions.cs
diff --git a/GFramework.Core.Tests/functional/pipe/PipeExtensionsTests.cs b/GFramework.Core.Tests/functional/pipe/PipeExtensionsTests.cs
index 434cba7..bdaa0ce 100644
--- a/GFramework.Core.Tests/functional/pipe/PipeExtensionsTests.cs
+++ b/GFramework.Core.Tests/functional/pipe/PipeExtensionsTests.cs
@@ -10,8 +10,6 @@ namespace GFramework.Core.Tests.functional.pipe;
[TestFixture]
public class PipeExtensionsTests
{
- #region Pipe Tests
-
///
/// 测试Pipe方法 - 验证值能够正确传递给函数并返回结果
///
@@ -20,18 +18,14 @@ public class PipeExtensionsTests
{
// Arrange
var value = 5;
-
+
// Act
var result = value.Pipe(x => x * 2);
-
+
// Assert
Assert.That(result, Is.EqualTo(10));
}
- #endregion
-
- #region Then Tests
-
///
/// 测试Then方法 - 验证两个函数能够正确组合执行
///
@@ -44,7 +38,7 @@ public class PipeExtensionsTests
// Act
var composed = addTwo.Then((Func)MultiplyByThree);
var result = composed(5);
-
+
// Assert
Assert.That(result, Is.EqualTo(21)); // (5+2)*3 = 21
return;
@@ -52,10 +46,6 @@ public class PipeExtensionsTests
int MultiplyByThree(int x) => x * 3;
}
- #endregion
-
- #region After Tests
-
///
/// 测试After方法 - 验证反向函数组合的正确性
///
@@ -68,7 +58,7 @@ public class PipeExtensionsTests
// Act
var composed = multiplyByThree.After((Func)AddTwo);
var result = composed(5);
-
+
// Assert
Assert.That(result, Is.EqualTo(21)); // (5+2)*3 = 21
return;
@@ -76,32 +66,6 @@ public class PipeExtensionsTests
int AddTwo(int x) => x + 2;
}
- #endregion
-
- #region Tap Tests
-
- ///
- /// 测试Tap方法 - 验证副作用操作执行后返回原值
- ///
- [Test]
- public void Tap_Should_Execute_Action_And_Return_Original_Value()
- {
- // Arrange
- var value = 42;
- var capturedValue = 0;
-
- // Act
- var result = value.Tap(x => capturedValue = x);
-
- // Assert
- Assert.That(result, Is.EqualTo(42));
- Assert.That(capturedValue, Is.EqualTo(42));
- }
-
- #endregion
-
- #region Apply Tests
-
///
/// 测试Apply方法 - 验证函数能够正确应用到参数上
///
@@ -110,18 +74,14 @@ public class PipeExtensionsTests
{
// Arrange
Func multiplyByTwo = x => x * 2;
-
+
// Act
var result = multiplyByTwo.Apply(5);
-
+
// Assert
Assert.That(result, Is.EqualTo(10));
}
- #endregion
-
- #region Also Tests
-
///
/// 测试Also方法 - 验证执行操作后返回原值功能
///
@@ -131,19 +91,15 @@ public class PipeExtensionsTests
// Arrange
var value = 42;
var capturedValue = 0;
-
+
// Act
var result = value.Also(x => capturedValue = x);
-
+
// Assert
Assert.That(result, Is.EqualTo(42));
Assert.That(capturedValue, Is.EqualTo(42));
}
- #endregion
-
- #region Let Tests
-
///
/// 测试Let方法 - 验证值转换功能
///
@@ -152,13 +108,11 @@ public class PipeExtensionsTests
{
// Arrange
var value = 5;
-
+
// Act
var result = value.Let(x => x * 2);
-
+
// Assert
Assert.That(result, Is.EqualTo(10));
}
-
- #endregion
}
\ No newline at end of file
diff --git a/GFramework.Core/functional/functions/FunctionExtensions.cs b/GFramework.Core/functional/functions/FunctionExtensions.cs
index b2c04e1..ff2d1c1 100644
--- a/GFramework.Core/functional/functions/FunctionExtensions.cs
+++ b/GFramework.Core/functional/functions/FunctionExtensions.cs
@@ -10,6 +10,7 @@
// 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.functions;
///
@@ -54,7 +55,7 @@ public static class FunctionExtensions
this Func func,
T1 firstArg)
=> x => func(firstArg, x);
-
+
///
/// Repeat:重复执行函数n次
///
@@ -74,6 +75,7 @@ public static class FunctionExtensions
{
result = func(result);
}
+
return result;
}
@@ -122,4 +124,17 @@ public static class FunctionExtensions
return result;
};
}
-}
+
+ ///
+ /// Map:对单个对象应用函数
+ ///
+ /// 源对象类型
+ /// 映射后的类型
+ /// 要映射的源对象
+ /// 转换函数
+ /// 映射后的对象
+ public static TResult Map(
+ this TSource source,
+ Func selector)
+ => selector(source);
+}
\ No newline at end of file
diff --git a/GFramework.Core/functional/pipe/PipeExtensions.cs b/GFramework.Core/functional/pipe/PipeExtensions.cs
index 595789e..8127b48 100644
--- a/GFramework.Core/functional/pipe/PipeExtensions.cs
+++ b/GFramework.Core/functional/pipe/PipeExtensions.cs
@@ -10,6 +10,7 @@
// 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.pipe;
///
@@ -43,7 +44,7 @@ public static class PipeExtensions
this Func first,
Func second)
=> x => second(first(x));
-
+
///
/// Compose:反向组合(f2.After(f1))
///
@@ -57,21 +58,6 @@ public static class PipeExtensions
this Func second,
Func first)
=> x => second(first(x));
-
- ///
- /// Tap:执行副作用操作但返回原值(用于调试、日志等)
- ///
- /// 输入值的类型
- /// 要执行操作的输入值
- /// 要执行的副作用操作
- /// 原始输入值
- public static TSource Tap(
- this TSource value,
- Action action)
- {
- action(value);
- return value;
- }
///
/// Apply:将函数应用于值(柯里化辅助)
@@ -85,7 +71,20 @@ public static class PipeExtensions
this Func func,
TSource value)
=> func(value);
-
+
+ ///
+ /// On:将值应用于函数(与Apply功能相同,但参数顺序相反)
+ ///
+ /// 输入值的类型
+ /// 函数返回结果的类型
+ /// 要传递给函数的输入值
+ /// 要应用的函数
+ /// 函数执行后的结果
+ public static TResult On(
+ this TSource value,
+ Func func)
+ => func(value);
+
///
/// Also:执行操作并返回原值
///
@@ -113,6 +112,4 @@ public static class PipeExtensions
this TSource value,
Func transform)
=> transform(value);
-
-
-}
+}
\ No newline at end of file
diff --git a/GFramework.Core/functional/types/NullableExtensions.cs b/GFramework.Core/functional/types/NullableExtensions.cs
new file mode 100644
index 0000000..e0ac87f
--- /dev/null
+++ b/GFramework.Core/functional/types/NullableExtensions.cs
@@ -0,0 +1,44 @@
+// Copyright (c) 2026 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.types;
+
+///
+/// 提供Nullable类型转换为Option类型的扩展方法
+///
+public static class NullableExtensions
+{
+ ///
+ /// 将可空引用类型转换为Option类型
+ ///
+ /// 引用类型
+ /// 可空的引用类型值
+ /// 如果值为null则返回None,否则返回包含该值的Some
+ public static Option ToOption(this T? value)
+ where T : class
+ => value is null
+ ? Option.None()
+ : Option.Some(value);
+
+ ///
+ /// 将可空值类型转换为Option类型
+ ///
+ /// 值类型
+ /// 可空的值类型值
+ /// 如果值有值则返回包含该值的Some,否则返回None
+ public static Option ToOption(this T? value)
+ where T : struct
+ => value.HasValue
+ ? Option.Some(value.Value)
+ : Option.None();
+}
\ No newline at end of file
diff --git a/GFramework.Core/functional/types/Option.cs b/GFramework.Core/functional/types/Option.cs
new file mode 100644
index 0000000..6586a01
--- /dev/null
+++ b/GFramework.Core/functional/types/Option.cs
@@ -0,0 +1,78 @@
+// Copyright (c) 2026 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.types;
+
+///
+/// 表示一个可能存在也可能不存在的值
+///
+public readonly struct Option
+{
+ private readonly T _value;
+
+ ///
+ /// 获取当前Option是否包含值
+ ///
+ public bool IsSome { get; }
+
+ ///
+ /// 获取当前Option是否为空值
+ ///
+ public bool IsNone => !IsSome;
+
+ ///
+ /// 使用指定值创建Option实例
+ ///
+ /// 要包装的值
+ private Option(T value)
+ {
+ _value = value;
+ IsSome = true;
+ }
+
+ ///
+ /// 创建空的Option实例
+ ///
+ /// 占位参数,用于区分构造函数重载
+ private Option(bool _)
+ {
+ _value = default!;
+ IsSome = false;
+ }
+
+ ///
+ /// 创建包含指定值的Option实例
+ ///
+ /// 要包装的值,不能为null
+ /// 包含指定值的Option实例
+ /// 当value为null时抛出
+ public static Option Some(T value)
+ {
+ return value is null ? throw new ArgumentNullException(nameof(value)) : new Option(value);
+ }
+
+ ///
+ /// 创建空的Option实例
+ ///
+ /// 空的Option实例
+ public static Option None() => new(false);
+
+ ///
+ /// 获取Option中包含的值
+ ///
+ /// 当Option为空时抛出
+ public T Value =>
+ IsSome
+ ? _value
+ : throw new InvalidOperationException("Option has no value");
+}
\ No newline at end of file
diff --git a/GFramework.Core/functional/types/OptionExtensions.cs b/GFramework.Core/functional/types/OptionExtensions.cs
new file mode 100644
index 0000000..ef82b52
--- /dev/null
+++ b/GFramework.Core/functional/types/OptionExtensions.cs
@@ -0,0 +1,89 @@
+// Copyright (c) 2026 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.types;
+
+///
+/// 提供Option类型的功能扩展方法,支持映射、绑定、过滤和匹配操作
+///
+public static class OptionExtensions
+{
+ ///
+ /// 将Option中的值通过指定的映射函数转换为另一种类型的Option
+ ///
+ /// 源Option中值的类型
+ /// 映射后结果的类型
+ /// 要进行映射操作的Option实例
+ /// 用于将源值转换为目标值的映射函数
+ /// 包含映射后值的新Option实例,如果原Option为空则返回None
+ public static Option Map(
+ this Option option,
+ Func mapper)
+ {
+ return option.IsSome
+ ? Option.Some(mapper(option.Value))
+ : Option.None();
+ }
+
+ ///
+ /// 将Option中的值通过指定的绑定函数转换为另一个Option,实现Option的扁平化映射
+ ///
+ /// 源Option中值的类型
+ /// 绑定后Option中值的类型
+ /// 要进行绑定操作的Option实例
+ /// 用于将源值转换为新Option的绑定函数
+ /// 绑定函数返回的Option实例,如果原Option为空则返回None
+ public static Option Bind(
+ this Option option,
+ Func> binder)
+ {
+ return option.IsSome
+ ? binder(option.Value)
+ : Option.None();
+ }
+
+ ///
+ /// 根据指定的谓词函数过滤Option中的值
+ ///
+ /// Option中值的类型
+ /// 要进行过滤操作的Option实例
+ /// 用于判断值是否满足条件的谓词函数
+ /// 如果Option有值且满足谓词条件则返回原Option,否则返回None
+ public static Option Filter(
+ this Option option,
+ Func predicate)
+ {
+ return option.IsSome && predicate(option.Value)
+ ? option
+ : Option.None();
+ }
+
+ ///
+ /// 对Option进行模式匹配,根据Option的状态执行不同的函数
+ ///
+ /// Option中值的类型
+ /// 匹配结果的类型
+ /// 要进行匹配操作的Option实例
+ /// 当Option包含值时执行的函数
+ /// 当Option为空时执行的函数
+ /// 根据Option状态执行相应函数后的结果
+ public static TResult Match(
+ this Option option,
+ Func some,
+ Func none)
+ {
+ return option.IsSome
+ ? some(option.Value)
+ : none();
+ }
+}
\ No newline at end of file
diff --git a/GFramework.Core/functional/types/OptionValueExtensions.cs b/GFramework.Core/functional/types/OptionValueExtensions.cs
new file mode 100644
index 0000000..b0f2786
--- /dev/null
+++ b/GFramework.Core/functional/types/OptionValueExtensions.cs
@@ -0,0 +1,56 @@
+// Copyright (c) 2026 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.types;
+
+///
+/// 提供Option类型值的操作扩展方法
+///
+public static class OptionValueExtensions
+{
+ ///
+ /// 获取Option中的值,如果Option为空则返回默认值
+ ///
+ /// Option中存储的值的类型
+ /// 要获取值的Option对象
+ /// 当Option为空时返回的默认值
+ /// Option中的值或默认值
+ public static T GetOrElse(
+ this Option option,
+ T defaultValue)
+ => option.IsSome ? option.Value : defaultValue;
+
+ ///
+ /// 获取Option中的值,如果Option为空则通过工厂函数生成默认值
+ ///
+ /// Option中存储的值的类型
+ /// 要获取值的Option对象
+ /// 当Option为空时用于生成默认值的工厂函数
+ /// Option中的值或通过工厂函数生成的值
+ public static T GetOrElse(
+ this Option option,
+ Func defaultFactory)
+ => option.IsSome ? option.Value : defaultFactory();
+
+ ///
+ /// 获取当前Option,如果当前Option为空则返回备用Option
+ ///
+ /// Option中存储的值的类型
+ /// 当前Option对象
+ /// 当当前Option为空时返回的备用Option
+ /// 当前Option或备用Option
+ public static Option OrElse(
+ this Option option,
+ Option fallback)
+ => option.IsSome ? option : fallback;
+}
\ No newline at end of file
diff --git a/GFramework.Game.Abstractions/setting/IResetApplyAbleSettings.cs b/GFramework.Game.Abstractions/setting/IResetApplyAbleSettings.cs
index 8cc4e18..61797ca 100644
--- a/GFramework.Game.Abstractions/setting/IResetApplyAbleSettings.cs
+++ b/GFramework.Game.Abstractions/setting/IResetApplyAbleSettings.cs
@@ -17,4 +17,11 @@ namespace GFramework.Game.Abstractions.setting;
/// 定义一个可重置且可应用设置的接口
/// 该接口继承自IResettable和IApplyAbleSettings接口,组合了重置功能和应用设置功能
///
-public interface IResetApplyAbleSettings : IResettable, IApplyAbleSettings;
+public interface IResetApplyAbleSettings : IResettable, IApplyAbleSettings
+{
+ ///
+ /// 获取设置数据对象
+ ///
+ /// ISettingsData类型的设置数据
+ ISettingsData Data { get; }
+}
\ No newline at end of file
diff --git a/GFramework.Game/GlobalUsings.cs b/GFramework.Game/GlobalUsings.cs
index 4d27181..74db065 100644
--- a/GFramework.Game/GlobalUsings.cs
+++ b/GFramework.Game/GlobalUsings.cs
@@ -13,6 +13,7 @@
global using System;
global using System.Collections.Generic;
+global using System.Collections.Concurrent;
global using System.Linq;
global using System.Threading;
global using System.Threading.Tasks;
\ No newline at end of file
diff --git a/GFramework.Game/setting/SettingsModel.cs b/GFramework.Game/setting/SettingsModel.cs
index 6a0a324..a88b1ab 100644
--- a/GFramework.Game/setting/SettingsModel.cs
+++ b/GFramework.Game/setting/SettingsModel.cs
@@ -1,5 +1,4 @@
-using System.Collections.Concurrent;
-using GFramework.Core.Abstractions.logging;
+using GFramework.Core.Abstractions.logging;
using GFramework.Core.extensions;
using GFramework.Core.logging;
using GFramework.Core.model;
diff --git a/GFramework.Godot/setting/GodotAudioSettings.cs b/GFramework.Godot/setting/GodotAudioSettings.cs
index 92c9b61..f8756c4 100644
--- a/GFramework.Godot/setting/GodotAudioSettings.cs
+++ b/GFramework.Godot/setting/GodotAudioSettings.cs
@@ -34,6 +34,8 @@ public class GodotAudioSettings(ISettingsModel model, AudioBusMap audioBusMap)
model.GetData().Reset();
}
+ public ISettingsData Data { get; } = model.GetData();
+
///
/// 设置指定音频总线的音量
///
diff --git a/GFramework.Godot/setting/GodotGraphicsSettings.cs b/GFramework.Godot/setting/GodotGraphicsSettings.cs
index 9112218..10ec22b 100644
--- a/GFramework.Godot/setting/GodotGraphicsSettings.cs
+++ b/GFramework.Godot/setting/GodotGraphicsSettings.cs
@@ -50,4 +50,6 @@ public class GodotGraphicsSettings(ISettingsModel model) : IResetApplyAbleSettin
{
model.GetData().Reset();
}
+
+ public ISettingsData Data { get; } = model.GetData();
}
\ No newline at end of file
diff --git a/GFramework.Godot/setting/GodotLocalizationSettings.cs b/GFramework.Godot/setting/GodotLocalizationSettings.cs
index c68405b..36fe4bb 100644
--- a/GFramework.Godot/setting/GodotLocalizationSettings.cs
+++ b/GFramework.Godot/setting/GodotLocalizationSettings.cs
@@ -47,4 +47,6 @@ public class GodotLocalizationSettings(ISettingsModel model, LocalizationMap loc
{
model.GetData().Reset();
}
+
+ public ISettingsData Data { get; } = model.GetData();
}
\ No newline at end of file