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