diff --git a/GFramework.Core.Tests/Architectures/IMixedTestSystem.cs b/GFramework.Core.Tests/Architectures/IMixedTestSystem.cs
new file mode 100644
index 00000000..e777e361
--- /dev/null
+++ b/GFramework.Core.Tests/Architectures/IMixedTestSystem.cs
@@ -0,0 +1,10 @@
+using GFramework.Core.Abstractions.Systems;
+
+namespace GFramework.Core.Tests.Architectures;
+
+///
+/// 定义用于混合优先级排序测试的系统契约。
+///
+public interface IMixedTestSystem : ISystem
+{
+}
diff --git a/GFramework.Core.Tests/Architectures/IPriorityTestModel.cs b/GFramework.Core.Tests/Architectures/IPriorityTestModel.cs
new file mode 100644
index 00000000..2bda2258
--- /dev/null
+++ b/GFramework.Core.Tests/Architectures/IPriorityTestModel.cs
@@ -0,0 +1,10 @@
+using GFramework.Core.Abstractions.Model;
+
+namespace GFramework.Core.Tests.Architectures;
+
+///
+/// 定义用于优先级排序测试的模型契约。
+///
+public interface IPriorityTestModel : IModel
+{
+}
diff --git a/GFramework.Core.Tests/Architectures/IPriorityTestSystem.cs b/GFramework.Core.Tests/Architectures/IPriorityTestSystem.cs
new file mode 100644
index 00000000..9ea25cde
--- /dev/null
+++ b/GFramework.Core.Tests/Architectures/IPriorityTestSystem.cs
@@ -0,0 +1,10 @@
+using GFramework.Core.Abstractions.Systems;
+
+namespace GFramework.Core.Tests.Architectures;
+
+///
+/// 定义用于优先级排序测试的系统契约。
+///
+public interface IPriorityTestSystem : ISystem
+{
+}
diff --git a/GFramework.Core.Tests/Architectures/IPriorityTestUtility.cs b/GFramework.Core.Tests/Architectures/IPriorityTestUtility.cs
new file mode 100644
index 00000000..8f499dc0
--- /dev/null
+++ b/GFramework.Core.Tests/Architectures/IPriorityTestUtility.cs
@@ -0,0 +1,10 @@
+using GFramework.Core.Abstractions.Utility;
+
+namespace GFramework.Core.Tests.Architectures;
+
+///
+/// 定义用于优先级排序测试的工具契约。
+///
+public interface IPriorityTestUtility : IUtility
+{
+}
diff --git a/GFramework.Core.Tests/Architectures/MixedTestSystemNegativePriority.cs b/GFramework.Core.Tests/Architectures/MixedTestSystemNegativePriority.cs
new file mode 100644
index 00000000..fa59af18
--- /dev/null
+++ b/GFramework.Core.Tests/Architectures/MixedTestSystemNegativePriority.cs
@@ -0,0 +1,21 @@
+using GFramework.Core.Abstractions.Bases;
+
+namespace GFramework.Core.Tests.Architectures;
+
+///
+/// 表示显式声明负优先级的混合测试系统。
+///
+public class MixedTestSystemNegativePriority : AbstractSystem, IMixedTestSystem, IPrioritized
+{
+ ///
+ /// 获取当前测试系统的排序优先级。
+ ///
+ public int Priority => -10;
+
+ ///
+ /// 保持空初始化,以便测试仅覆盖优先级排序行为。
+ ///
+ protected override void OnInit()
+ {
+ }
+}
diff --git a/GFramework.Core.Tests/Architectures/MixedTestSystemWithPriority.cs b/GFramework.Core.Tests/Architectures/MixedTestSystemWithPriority.cs
new file mode 100644
index 00000000..c938ef37
--- /dev/null
+++ b/GFramework.Core.Tests/Architectures/MixedTestSystemWithPriority.cs
@@ -0,0 +1,21 @@
+using GFramework.Core.Abstractions.Bases;
+
+namespace GFramework.Core.Tests.Architectures;
+
+///
+/// 表示显式声明正优先级的混合测试系统。
+///
+public class MixedTestSystemWithPriority : AbstractSystem, IMixedTestSystem, IPrioritized
+{
+ ///
+ /// 获取当前测试系统的排序优先级。
+ ///
+ public int Priority => 10;
+
+ ///
+ /// 保持空初始化,以便测试仅覆盖优先级排序行为。
+ ///
+ protected override void OnInit()
+ {
+ }
+}
diff --git a/GFramework.Core.Tests/Architectures/MixedTestSystemWithoutPriority.cs b/GFramework.Core.Tests/Architectures/MixedTestSystemWithoutPriority.cs
new file mode 100644
index 00000000..ba418c39
--- /dev/null
+++ b/GFramework.Core.Tests/Architectures/MixedTestSystemWithoutPriority.cs
@@ -0,0 +1,16 @@
+using GFramework.Core.Abstractions.Bases;
+
+namespace GFramework.Core.Tests.Architectures;
+
+///
+/// 表示未声明优先级、依赖默认排序值的混合测试系统。
+///
+public class MixedTestSystemWithoutPriority : AbstractSystem, IMixedTestSystem
+{
+ ///
+ /// 保持空初始化,以便测试仅覆盖优先级排序行为。
+ ///
+ protected override void OnInit()
+ {
+ }
+}
diff --git a/GFramework.Core.Tests/Architectures/PriorityServiceTests.cs b/GFramework.Core.Tests/Architectures/PriorityServiceTests.cs
index 0499a4ab..2d53611c 100644
--- a/GFramework.Core.Tests/Architectures/PriorityServiceTests.cs
+++ b/GFramework.Core.Tests/Architectures/PriorityServiceTests.cs
@@ -1,12 +1,7 @@
using System.Reflection;
-using GFramework.Core.Abstractions.Bases;
using GFramework.Core.Abstractions.Logging;
-using GFramework.Core.Abstractions.Model;
-using GFramework.Core.Abstractions.Systems;
-using GFramework.Core.Abstractions.Utility;
using GFramework.Core.Ioc;
using GFramework.Core.Logging;
-using GFramework.Core.Model;
namespace GFramework.Core.Tests.Architectures;
@@ -120,129 +115,3 @@ public class PriorityServiceTests
Assert.That(systems[2], Is.InstanceOf()); // 10
}
}
-
-#region Test Interfaces
-
-public interface IPriorityTestSystem : ISystem
-{
-}
-
-public interface IPriorityTestModel : IModel
-{
-}
-
-public interface IPriorityTestUtility : IUtility
-{
-}
-
-public interface IMixedTestSystem : ISystem
-{
-}
-
-#endregion
-
-#region Test Systems
-
-public class PriorityTestSystemA : AbstractSystem, IPriorityTestSystem, IPrioritized
-{
- public int Priority => 10;
-
- protected override void OnInit()
- {
- }
-}
-
-public class PriorityTestSystemB : AbstractSystem, IPriorityTestSystem, IPrioritized
-{
- public int Priority => 20;
-
- protected override void OnInit()
- {
- }
-}
-
-public class PriorityTestSystemC : AbstractSystem, IPriorityTestSystem, IPrioritized
-{
- public int Priority => 30;
-
- protected override void OnInit()
- {
- }
-}
-
-public class MixedTestSystemWithPriority : AbstractSystem, IMixedTestSystem, IPrioritized
-{
- public int Priority => 10;
-
- protected override void OnInit()
- {
- }
-}
-
-public class MixedTestSystemWithoutPriority : AbstractSystem, IMixedTestSystem
-{
- protected override void OnInit()
- {
- }
-}
-
-public class MixedTestSystemNegativePriority : AbstractSystem, IMixedTestSystem, IPrioritized
-{
- public int Priority => -10;
-
- protected override void OnInit()
- {
- }
-}
-
-#endregion
-
-#region Test Models
-
-public class PriorityTestModelA : AbstractModel, IPriorityTestModel, IPrioritized
-{
- public int Priority => 10;
-
- protected override void OnInit()
- {
- }
-}
-
-public class PriorityTestModelB : AbstractModel, IPriorityTestModel, IPrioritized
-{
- public int Priority => 20;
-
- protected override void OnInit()
- {
- }
-}
-
-public class PriorityTestModelC : AbstractModel, IPriorityTestModel, IPrioritized
-{
- public int Priority => 30;
-
- protected override void OnInit()
- {
- }
-}
-
-#endregion
-
-#region Test Utilities
-
-public class PriorityTestUtilityA : IPriorityTestUtility, IPrioritized
-{
- public int Priority => 10;
-}
-
-public class PriorityTestUtilityB : IPriorityTestUtility, IPrioritized
-{
- public int Priority => 20;
-}
-
-public class PriorityTestUtilityC : IPriorityTestUtility, IPrioritized
-{
- public int Priority => 30;
-}
-
-#endregion
diff --git a/GFramework.Core.Tests/Architectures/PriorityTestModelA.cs b/GFramework.Core.Tests/Architectures/PriorityTestModelA.cs
new file mode 100644
index 00000000..dd1b2838
--- /dev/null
+++ b/GFramework.Core.Tests/Architectures/PriorityTestModelA.cs
@@ -0,0 +1,22 @@
+using GFramework.Core.Abstractions.Bases;
+using GFramework.Core.Model;
+
+namespace GFramework.Core.Tests.Architectures;
+
+///
+/// 表示优先级为 10 的测试模型。
+///
+public class PriorityTestModelA : AbstractModel, IPriorityTestModel, IPrioritized
+{
+ ///
+ /// 获取当前测试模型的排序优先级。
+ ///
+ public int Priority => 10;
+
+ ///
+ /// 保持空初始化,以便测试仅覆盖优先级排序行为。
+ ///
+ protected override void OnInit()
+ {
+ }
+}
diff --git a/GFramework.Core.Tests/Architectures/PriorityTestModelB.cs b/GFramework.Core.Tests/Architectures/PriorityTestModelB.cs
new file mode 100644
index 00000000..62560b44
--- /dev/null
+++ b/GFramework.Core.Tests/Architectures/PriorityTestModelB.cs
@@ -0,0 +1,22 @@
+using GFramework.Core.Abstractions.Bases;
+using GFramework.Core.Model;
+
+namespace GFramework.Core.Tests.Architectures;
+
+///
+/// 表示优先级为 20 的测试模型。
+///
+public class PriorityTestModelB : AbstractModel, IPriorityTestModel, IPrioritized
+{
+ ///
+ /// 获取当前测试模型的排序优先级。
+ ///
+ public int Priority => 20;
+
+ ///
+ /// 保持空初始化,以便测试仅覆盖优先级排序行为。
+ ///
+ protected override void OnInit()
+ {
+ }
+}
diff --git a/GFramework.Core.Tests/Architectures/PriorityTestModelC.cs b/GFramework.Core.Tests/Architectures/PriorityTestModelC.cs
new file mode 100644
index 00000000..2991cc1f
--- /dev/null
+++ b/GFramework.Core.Tests/Architectures/PriorityTestModelC.cs
@@ -0,0 +1,22 @@
+using GFramework.Core.Abstractions.Bases;
+using GFramework.Core.Model;
+
+namespace GFramework.Core.Tests.Architectures;
+
+///
+/// 表示优先级为 30 的测试模型。
+///
+public class PriorityTestModelC : AbstractModel, IPriorityTestModel, IPrioritized
+{
+ ///
+ /// 获取当前测试模型的排序优先级。
+ ///
+ public int Priority => 30;
+
+ ///
+ /// 保持空初始化,以便测试仅覆盖优先级排序行为。
+ ///
+ protected override void OnInit()
+ {
+ }
+}
diff --git a/GFramework.Core.Tests/Architectures/PriorityTestSystemA.cs b/GFramework.Core.Tests/Architectures/PriorityTestSystemA.cs
new file mode 100644
index 00000000..3c7baaed
--- /dev/null
+++ b/GFramework.Core.Tests/Architectures/PriorityTestSystemA.cs
@@ -0,0 +1,21 @@
+using GFramework.Core.Abstractions.Bases;
+
+namespace GFramework.Core.Tests.Architectures;
+
+///
+/// 表示优先级为 10 的测试系统。
+///
+public class PriorityTestSystemA : AbstractSystem, IPriorityTestSystem, IPrioritized
+{
+ ///
+ /// 获取当前测试系统的排序优先级。
+ ///
+ public int Priority => 10;
+
+ ///
+ /// 保持空初始化,以便测试仅覆盖优先级排序行为。
+ ///
+ protected override void OnInit()
+ {
+ }
+}
diff --git a/GFramework.Core.Tests/Architectures/PriorityTestSystemB.cs b/GFramework.Core.Tests/Architectures/PriorityTestSystemB.cs
new file mode 100644
index 00000000..5dbe0fa2
--- /dev/null
+++ b/GFramework.Core.Tests/Architectures/PriorityTestSystemB.cs
@@ -0,0 +1,21 @@
+using GFramework.Core.Abstractions.Bases;
+
+namespace GFramework.Core.Tests.Architectures;
+
+///
+/// 表示优先级为 20 的测试系统。
+///
+public class PriorityTestSystemB : AbstractSystem, IPriorityTestSystem, IPrioritized
+{
+ ///
+ /// 获取当前测试系统的排序优先级。
+ ///
+ public int Priority => 20;
+
+ ///
+ /// 保持空初始化,以便测试仅覆盖优先级排序行为。
+ ///
+ protected override void OnInit()
+ {
+ }
+}
diff --git a/GFramework.Core.Tests/Architectures/PriorityTestSystemC.cs b/GFramework.Core.Tests/Architectures/PriorityTestSystemC.cs
new file mode 100644
index 00000000..5033db63
--- /dev/null
+++ b/GFramework.Core.Tests/Architectures/PriorityTestSystemC.cs
@@ -0,0 +1,21 @@
+using GFramework.Core.Abstractions.Bases;
+
+namespace GFramework.Core.Tests.Architectures;
+
+///
+/// 表示优先级为 30 的测试系统。
+///
+public class PriorityTestSystemC : AbstractSystem, IPriorityTestSystem, IPrioritized
+{
+ ///
+ /// 获取当前测试系统的排序优先级。
+ ///
+ public int Priority => 30;
+
+ ///
+ /// 保持空初始化,以便测试仅覆盖优先级排序行为。
+ ///
+ protected override void OnInit()
+ {
+ }
+}
diff --git a/GFramework.Core.Tests/Architectures/PriorityTestUtilityA.cs b/GFramework.Core.Tests/Architectures/PriorityTestUtilityA.cs
new file mode 100644
index 00000000..52c91a96
--- /dev/null
+++ b/GFramework.Core.Tests/Architectures/PriorityTestUtilityA.cs
@@ -0,0 +1,14 @@
+using GFramework.Core.Abstractions.Bases;
+
+namespace GFramework.Core.Tests.Architectures;
+
+///
+/// 表示优先级为 10 的测试工具。
+///
+public class PriorityTestUtilityA : IPriorityTestUtility, IPrioritized
+{
+ ///
+ /// 获取当前测试工具的排序优先级。
+ ///
+ public int Priority => 10;
+}
diff --git a/GFramework.Core.Tests/Architectures/PriorityTestUtilityB.cs b/GFramework.Core.Tests/Architectures/PriorityTestUtilityB.cs
new file mode 100644
index 00000000..3d36ad10
--- /dev/null
+++ b/GFramework.Core.Tests/Architectures/PriorityTestUtilityB.cs
@@ -0,0 +1,14 @@
+using GFramework.Core.Abstractions.Bases;
+
+namespace GFramework.Core.Tests.Architectures;
+
+///
+/// 表示优先级为 20 的测试工具。
+///
+public class PriorityTestUtilityB : IPriorityTestUtility, IPrioritized
+{
+ ///
+ /// 获取当前测试工具的排序优先级。
+ ///
+ public int Priority => 20;
+}
diff --git a/GFramework.Core.Tests/Architectures/PriorityTestUtilityC.cs b/GFramework.Core.Tests/Architectures/PriorityTestUtilityC.cs
new file mode 100644
index 00000000..6c164079
--- /dev/null
+++ b/GFramework.Core.Tests/Architectures/PriorityTestUtilityC.cs
@@ -0,0 +1,14 @@
+using GFramework.Core.Abstractions.Bases;
+
+namespace GFramework.Core.Tests.Architectures;
+
+///
+/// 表示优先级为 30 的测试工具。
+///
+public class PriorityTestUtilityC : IPriorityTestUtility, IPrioritized
+{
+ ///
+ /// 获取当前测试工具的排序优先级。
+ ///
+ public int Priority => 30;
+}
diff --git a/GFramework.Core.Tests/Ioc/AliasAwareService.cs b/GFramework.Core.Tests/Ioc/AliasAwareService.cs
new file mode 100644
index 00000000..65de6dd1
--- /dev/null
+++ b/GFramework.Core.Tests/Ioc/AliasAwareService.cs
@@ -0,0 +1,8 @@
+namespace GFramework.Core.Tests.Ioc;
+
+///
+/// 同时实现多个别名接口的测试服务。
+///
+public sealed class AliasAwareService : IPrimaryAliasService, ISecondaryAliasService
+{
+}
diff --git a/GFramework.Core.Tests/Ioc/IMixedService.cs b/GFramework.Core.Tests/Ioc/IMixedService.cs
new file mode 100644
index 00000000..33c791f5
--- /dev/null
+++ b/GFramework.Core.Tests/Ioc/IMixedService.cs
@@ -0,0 +1,12 @@
+namespace GFramework.Core.Tests.Ioc;
+
+///
+/// 混合服务接口(用于测试优先级和非优先级混合)
+///
+public interface IMixedService
+{
+ ///
+ /// 获取或设置服务名称。
+ ///
+ string? Name { get; set; }
+}
diff --git a/GFramework.Core.Tests/Ioc/IPrimaryAliasService.cs b/GFramework.Core.Tests/Ioc/IPrimaryAliasService.cs
new file mode 100644
index 00000000..c8937b90
--- /dev/null
+++ b/GFramework.Core.Tests/Ioc/IPrimaryAliasService.cs
@@ -0,0 +1,6 @@
+namespace GFramework.Core.Tests.Ioc;
+
+///
+/// 主服务别名接口。
+///
+public interface IPrimaryAliasService : ISharedAliasService;
diff --git a/GFramework.Core.Tests/Ioc/IPrioritizedService.cs b/GFramework.Core.Tests/Ioc/IPrioritizedService.cs
new file mode 100644
index 00000000..4e52fe7a
--- /dev/null
+++ b/GFramework.Core.Tests/Ioc/IPrioritizedService.cs
@@ -0,0 +1,10 @@
+using GFramework.Core.Abstractions.Bases;
+
+namespace GFramework.Core.Tests.Ioc;
+
+///
+/// 优先级服务接口
+///
+public interface IPrioritizedService : IPrioritized, IMixedService
+{
+}
diff --git a/GFramework.Core.Tests/Ioc/ISecondaryAliasService.cs b/GFramework.Core.Tests/Ioc/ISecondaryAliasService.cs
new file mode 100644
index 00000000..215cbeef
--- /dev/null
+++ b/GFramework.Core.Tests/Ioc/ISecondaryAliasService.cs
@@ -0,0 +1,6 @@
+namespace GFramework.Core.Tests.Ioc;
+
+///
+/// 次级兼容别名接口。
+///
+public interface ISecondaryAliasService : ISharedAliasService;
diff --git a/GFramework.Core.Tests/Ioc/IService.cs b/GFramework.Core.Tests/Ioc/IService.cs
new file mode 100644
index 00000000..b9559fef
--- /dev/null
+++ b/GFramework.Core.Tests/Ioc/IService.cs
@@ -0,0 +1,6 @@
+namespace GFramework.Core.Tests.Ioc;
+
+///
+/// 服务接口定义
+///
+public interface IService;
diff --git a/GFramework.Core.Tests/Ioc/ISharedAliasService.cs b/GFramework.Core.Tests/Ioc/ISharedAliasService.cs
new file mode 100644
index 00000000..e12425bc
--- /dev/null
+++ b/GFramework.Core.Tests/Ioc/ISharedAliasService.cs
@@ -0,0 +1,6 @@
+namespace GFramework.Core.Tests.Ioc;
+
+///
+/// 用于验证未冻结查询路径中的服务别名去重行为。
+///
+public interface ISharedAliasService;
diff --git a/GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs b/GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs
index 19c59dcb..13fb782e 100644
--- a/GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs
+++ b/GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs
@@ -1,5 +1,4 @@
using System.Reflection;
-using GFramework.Core.Abstractions.Bases;
using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Ioc;
using GFramework.Core.Logging;
@@ -734,74 +733,3 @@ public class MicrosoftDiContainerTests
Assert.That(((IPrioritizedService)services[1]).Priority, Is.EqualTo(30));
}
}
-
-///
-/// 服务接口定义
-///
-public interface IService;
-
-///
-/// 测试服务类,实现 IService 接口
-///
-public sealed class TestService : IService
-{
- ///
- /// 获取或设置优先级
- ///
- public int Priority { get; set; }
-}
-
-///
-/// 优先级服务接口
-///
-public interface IPrioritizedService : IPrioritized
-{
- string? Name { get; set; }
-}
-
-///
-/// 混合服务接口(用于测试优先级和非优先级混合)
-///
-public interface IMixedService
-{
- string? Name { get; set; }
-}
-
-///
-/// 用于验证未冻结查询路径中的服务别名去重行为。
-///
-public interface ISharedAliasService;
-
-///
-/// 主服务别名接口。
-///
-public interface IPrimaryAliasService : ISharedAliasService;
-
-///
-/// 次级兼容别名接口。
-///
-public interface ISecondaryAliasService : ISharedAliasService;
-
-///
-/// 同时实现多个别名接口的测试服务。
-///
-public sealed class AliasAwareService : IPrimaryAliasService, ISecondaryAliasService
-{
-}
-
-///
-/// 实现优先级的服务
-///
-public sealed class PrioritizedService : IPrioritizedService, IMixedService
-{
- public int Priority { get; set; }
- public string? Name { get; set; }
-}
-
-///
-/// 不实现优先级的服务
-///
-public sealed class NonPrioritizedService : IMixedService
-{
- public string? Name { get; set; }
-}
diff --git a/GFramework.Core.Tests/Ioc/NonPrioritizedService.cs b/GFramework.Core.Tests/Ioc/NonPrioritizedService.cs
new file mode 100644
index 00000000..7f4f87d9
--- /dev/null
+++ b/GFramework.Core.Tests/Ioc/NonPrioritizedService.cs
@@ -0,0 +1,12 @@
+namespace GFramework.Core.Tests.Ioc;
+
+///
+/// 不实现优先级的服务
+///
+public sealed class NonPrioritizedService : IMixedService
+{
+ ///
+ /// 获取或设置服务名称
+ ///
+ public string? Name { get; set; }
+}
diff --git a/GFramework.Core.Tests/Ioc/PrioritizedService.cs b/GFramework.Core.Tests/Ioc/PrioritizedService.cs
new file mode 100644
index 00000000..1c20cb94
--- /dev/null
+++ b/GFramework.Core.Tests/Ioc/PrioritizedService.cs
@@ -0,0 +1,17 @@
+namespace GFramework.Core.Tests.Ioc;
+
+///
+/// 实现优先级的服务
+///
+public sealed class PrioritizedService : IPrioritizedService
+{
+ ///
+ /// 获取或设置优先级
+ ///
+ public int Priority { get; set; }
+
+ ///
+ /// 获取或设置服务名称
+ ///
+ public string? Name { get; set; }
+}
diff --git a/GFramework.Core.Tests/Ioc/TestService.cs b/GFramework.Core.Tests/Ioc/TestService.cs
new file mode 100644
index 00000000..21dc1faa
--- /dev/null
+++ b/GFramework.Core.Tests/Ioc/TestService.cs
@@ -0,0 +1,12 @@
+namespace GFramework.Core.Tests.Ioc;
+
+///
+/// 测试服务类,实现 IService 接口
+///
+public sealed class TestService : IService
+{
+ ///
+ /// 获取或设置优先级
+ ///
+ public int Priority { get; set; }
+}
diff --git a/GFramework.Core.Tests/Query/AbstractAsyncQueryTests.cs b/GFramework.Core.Tests/Query/AbstractAsyncQueryTests.cs
index a7312baa..305c5f0d 100644
--- a/GFramework.Core.Tests/Query/AbstractAsyncQueryTests.cs
+++ b/GFramework.Core.Tests/Query/AbstractAsyncQueryTests.cs
@@ -6,7 +6,6 @@ using GFramework.Core.Environment;
using GFramework.Core.Events;
using GFramework.Core.Ioc;
using GFramework.Core.Query;
-using GFramework.Cqrs.Abstractions.Cqrs.Query;
namespace GFramework.Core.Tests.Query;
@@ -236,179 +235,3 @@ public class AbstractAsyncQueryTests
Assert.That(result2, Is.EqualTo(40));
}
}
-
-///
-/// 测试用异步查询输入类V2
-///
-public sealed class TestAsyncQueryInputV2 : IQueryInput
-{
- ///
- /// 获取或设置值
- ///
- public int Value { get; init; }
-}
-
-///
-/// 整数类型测试异步查询类V4,继承AbstractAsyncQuery
-///
-public sealed class TestAsyncQueryV4 : AbstractAsyncQuery
-{
- ///
- /// 初始化TestAsyncQueryV4的新实例
- ///
- /// 查询输入参数
- public TestAsyncQueryV4(TestAsyncQueryInputV2 input) : base(input)
- {
- }
-
- ///
- /// 获取查询是否已执行
- ///
- public bool Executed { get; private set; }
-
- ///
- /// 执行异步查询操作的具体实现
- ///
- /// 查询输入参数
- /// 查询结果,将输入值乘以2
- protected override Task OnDoAsync(TestAsyncQueryInputV2 input)
- {
- Executed = true;
- return Task.FromResult(input.Value * 2);
- }
-}
-
-///
-/// 字符串类型测试异步查询类V4,继承AbstractAsyncQuery
-///
-public sealed class TestAsyncStringQueryV4 : AbstractAsyncQuery
-{
- ///
- /// 初始化TestAsyncStringQueryV4的新实例
- ///
- /// 查询输入参数
- public TestAsyncStringQueryV4(TestAsyncQueryInputV2 input) : base(input)
- {
- }
-
- ///
- /// 获取查询是否已执行
- ///
- public bool Executed { get; private set; }
-
- ///
- /// 执行异步查询操作的具体实现
- ///
- /// 查询输入参数
- /// 格式化的字符串结果
- protected override Task OnDoAsync(TestAsyncQueryInputV2 input)
- {
- Executed = true;
- return Task.FromResult($"Value: {input.Value * 2}");
- }
-}
-
-///
-/// 复杂对象类型测试异步查询类V4,继承AbstractAsyncQuery
-///
-public sealed class TestAsyncComplexQueryV4 : AbstractAsyncQuery
-{
- ///
- /// 初始化TestAsyncComplexQueryV4的新实例
- ///
- /// 查询输入参数
- public TestAsyncComplexQueryV4(TestAsyncQueryInputV2 input) : base(input)
- {
- }
-
- ///
- /// 获取查询是否已执行
- ///
- public bool Executed { get; private set; }
-
- ///
- /// 执行异步查询操作的具体实现
- ///
- /// 查询输入参数
- /// 复杂对象查询结果
- protected override Task OnDoAsync(TestAsyncQueryInputV2 input)
- {
- Executed = true;
- var result = new TestAsyncQueryResultV2
- {
- Value = input.Value * 2,
- DoubleValue = input.Value * 3
- };
- return Task.FromResult(result);
- }
-}
-
-///
-/// 测试用异步查询类(抛出异常)
-///
-public sealed class TestAsyncQueryWithExceptionV4 : AbstractAsyncQuery
-{
- ///
- /// 初始化TestAsyncQueryWithExceptionV4的新实例
- ///
- /// 查询输入参数
- public TestAsyncQueryWithExceptionV4(TestAsyncQueryInputV2 input) : base(input)
- {
- }
-
- ///
- /// 执行异步查询操作并抛出异常
- ///
- /// 查询输入参数
- /// 总是抛出异常
- protected override Task OnDoAsync(TestAsyncQueryInputV2 input)
- {
- throw new InvalidOperationException("Test exception");
- }
-}
-
-///
-/// 测试用异步查询子类V4,继承AbstractAsyncQuery
-///
-public sealed class TestAsyncQueryChildV4 : AbstractAsyncQuery
-{
- ///
- /// 初始化TestAsyncQueryChildV4的新实例
- ///
- /// 查询输入参数
- public TestAsyncQueryChildV4(TestAsyncQueryInputV2 input) : base(input)
- {
- }
-
- ///
- /// 获取查询是否已执行
- ///
- public bool Executed { get; private set; }
-
- ///
- /// 执行异步查询操作的具体实现(子类实现,乘以3)
- ///
- /// 查询输入参数
- /// 查询结果,将输入值乘以3
- protected override Task OnDoAsync(TestAsyncQueryInputV2 input)
- {
- Executed = true;
- return Task.FromResult(input.Value * 3);
- }
-}
-
-///
-/// 测试用复杂查询结果类V2
-///
-public sealed class TestAsyncQueryResultV2
-{
- ///
- /// 获取或设置值
- ///
- public int Value { get; init; }
-
- ///
- /// 获取或设置双倍值
- ///
- public int DoubleValue { get; init; }
-}
diff --git a/GFramework.Core.Tests/Query/TestAsyncComplexQueryV4.cs b/GFramework.Core.Tests/Query/TestAsyncComplexQueryV4.cs
new file mode 100644
index 00000000..a5661efb
--- /dev/null
+++ b/GFramework.Core.Tests/Query/TestAsyncComplexQueryV4.cs
@@ -0,0 +1,38 @@
+using GFramework.Core.Query;
+
+namespace GFramework.Core.Tests.Query;
+
+///
+/// 复杂对象类型测试异步查询类V4,继承AbstractAsyncQuery
+///
+public sealed class TestAsyncComplexQueryV4 : AbstractAsyncQuery
+{
+ ///
+ /// 初始化TestAsyncComplexQueryV4的新实例
+ ///
+ /// 查询输入参数
+ public TestAsyncComplexQueryV4(TestAsyncQueryInputV2 input) : base(input)
+ {
+ }
+
+ ///
+ /// 获取查询是否已执行
+ ///
+ public bool Executed { get; private set; }
+
+ ///
+ /// 执行异步查询操作的具体实现
+ ///
+ /// 查询输入参数
+ /// 复杂对象查询结果
+ protected override Task OnDoAsync(TestAsyncQueryInputV2 input)
+ {
+ Executed = true;
+ var result = new TestAsyncQueryResultV2
+ {
+ Value = input.Value * 2,
+ DoubleValue = input.Value * 3
+ };
+ return Task.FromResult(result);
+ }
+}
diff --git a/GFramework.Core.Tests/Query/TestAsyncQueryChildV4.cs b/GFramework.Core.Tests/Query/TestAsyncQueryChildV4.cs
new file mode 100644
index 00000000..f931a52f
--- /dev/null
+++ b/GFramework.Core.Tests/Query/TestAsyncQueryChildV4.cs
@@ -0,0 +1,33 @@
+using GFramework.Core.Query;
+
+namespace GFramework.Core.Tests.Query;
+
+///
+/// 测试用异步查询子类V4,继承AbstractAsyncQuery
+///
+public sealed class TestAsyncQueryChildV4 : AbstractAsyncQuery
+{
+ ///
+ /// 初始化TestAsyncQueryChildV4的新实例
+ ///
+ /// 查询输入参数
+ public TestAsyncQueryChildV4(TestAsyncQueryInputV2 input) : base(input)
+ {
+ }
+
+ ///
+ /// 获取查询是否已执行
+ ///
+ public bool Executed { get; private set; }
+
+ ///
+ /// 执行异步查询操作的具体实现(子类实现,乘以3)
+ ///
+ /// 查询输入参数
+ /// 查询结果,将输入值乘以3
+ protected override Task OnDoAsync(TestAsyncQueryInputV2 input)
+ {
+ Executed = true;
+ return Task.FromResult(input.Value * 3);
+ }
+}
diff --git a/GFramework.Core.Tests/Query/TestAsyncQueryInputV2.cs b/GFramework.Core.Tests/Query/TestAsyncQueryInputV2.cs
new file mode 100644
index 00000000..cba1d6d1
--- /dev/null
+++ b/GFramework.Core.Tests/Query/TestAsyncQueryInputV2.cs
@@ -0,0 +1,14 @@
+using GFramework.Cqrs.Abstractions.Cqrs.Query;
+
+namespace GFramework.Core.Tests.Query;
+
+///
+/// 测试用异步查询输入类V2
+///
+public sealed class TestAsyncQueryInputV2 : IQueryInput
+{
+ ///
+ /// 获取或设置值
+ ///
+ public int Value { get; init; }
+}
diff --git a/GFramework.Core.Tests/Query/TestAsyncQueryResultV2.cs b/GFramework.Core.Tests/Query/TestAsyncQueryResultV2.cs
new file mode 100644
index 00000000..068b2a66
--- /dev/null
+++ b/GFramework.Core.Tests/Query/TestAsyncQueryResultV2.cs
@@ -0,0 +1,17 @@
+namespace GFramework.Core.Tests.Query;
+
+///
+/// 测试用复杂查询结果类V2
+///
+public sealed class TestAsyncQueryResultV2
+{
+ ///
+ /// 获取或设置值
+ ///
+ public int Value { get; init; }
+
+ ///
+ /// 获取或设置双倍值
+ ///
+ public int DoubleValue { get; init; }
+}
diff --git a/GFramework.Core.Tests/Query/TestAsyncQueryV4.cs b/GFramework.Core.Tests/Query/TestAsyncQueryV4.cs
new file mode 100644
index 00000000..a01e42c1
--- /dev/null
+++ b/GFramework.Core.Tests/Query/TestAsyncQueryV4.cs
@@ -0,0 +1,33 @@
+using GFramework.Core.Query;
+
+namespace GFramework.Core.Tests.Query;
+
+///
+/// 整数类型测试异步查询类V4,继承AbstractAsyncQuery
+///
+public sealed class TestAsyncQueryV4 : AbstractAsyncQuery
+{
+ ///
+ /// 初始化TestAsyncQueryV4的新实例
+ ///
+ /// 查询输入参数
+ public TestAsyncQueryV4(TestAsyncQueryInputV2 input) : base(input)
+ {
+ }
+
+ ///
+ /// 获取查询是否已执行
+ ///
+ public bool Executed { get; private set; }
+
+ ///
+ /// 执行异步查询操作的具体实现
+ ///
+ /// 查询输入参数
+ /// 查询结果,将输入值乘以2
+ protected override Task OnDoAsync(TestAsyncQueryInputV2 input)
+ {
+ Executed = true;
+ return Task.FromResult(input.Value * 2);
+ }
+}
diff --git a/GFramework.Core.Tests/Query/TestAsyncQueryWithExceptionV4.cs b/GFramework.Core.Tests/Query/TestAsyncQueryWithExceptionV4.cs
new file mode 100644
index 00000000..3c9ac640
--- /dev/null
+++ b/GFramework.Core.Tests/Query/TestAsyncQueryWithExceptionV4.cs
@@ -0,0 +1,28 @@
+using GFramework.Core.Query;
+
+namespace GFramework.Core.Tests.Query;
+
+///
+/// 测试用异步查询类(抛出异常)
+///
+public sealed class TestAsyncQueryWithExceptionV4 : AbstractAsyncQuery
+{
+ ///
+ /// 初始化TestAsyncQueryWithExceptionV4的新实例
+ ///
+ /// 查询输入参数
+ public TestAsyncQueryWithExceptionV4(TestAsyncQueryInputV2 input) : base(input)
+ {
+ }
+
+ ///
+ /// 执行异步查询操作并抛出异常
+ ///
+ /// 查询输入参数
+ /// 返回一个不会正常完成的 ,因为该方法始终抛出异常。
+ /// 总是抛出异常
+ protected override Task OnDoAsync(TestAsyncQueryInputV2 input)
+ {
+ throw new InvalidOperationException("Test exception");
+ }
+}
diff --git a/GFramework.Core.Tests/Query/TestAsyncStringQueryV4.cs b/GFramework.Core.Tests/Query/TestAsyncStringQueryV4.cs
new file mode 100644
index 00000000..59d313d5
--- /dev/null
+++ b/GFramework.Core.Tests/Query/TestAsyncStringQueryV4.cs
@@ -0,0 +1,33 @@
+using GFramework.Core.Query;
+
+namespace GFramework.Core.Tests.Query;
+
+///
+/// 字符串类型测试异步查询类V4,继承AbstractAsyncQuery
+///
+public sealed class TestAsyncStringQueryV4 : AbstractAsyncQuery
+{
+ ///
+ /// 初始化TestAsyncStringQueryV4的新实例
+ ///
+ /// 查询输入参数
+ public TestAsyncStringQueryV4(TestAsyncQueryInputV2 input) : base(input)
+ {
+ }
+
+ ///
+ /// 获取查询是否已执行
+ ///
+ public bool Executed { get; private set; }
+
+ ///
+ /// 执行异步查询操作的具体实现
+ ///
+ /// 查询输入参数
+ /// 格式化的字符串结果
+ protected override Task OnDoAsync(TestAsyncQueryInputV2 input)
+ {
+ Executed = true;
+ return Task.FromResult($"Value: {input.Value * 2}");
+ }
+}
diff --git a/GFramework.Game.Tests/Config/YamlConfigLoaderTests.cs b/GFramework.Game.Tests/Config/YamlConfigLoaderTests.cs
index 4b04ae0b..5cb22d08 100644
--- a/GFramework.Game.Tests/Config/YamlConfigLoaderTests.cs
+++ b/GFramework.Game.Tests/Config/YamlConfigLoaderTests.cs
@@ -1,7 +1,10 @@
using System.IO;
+using System.Reflection;
+using System.Threading;
using GFramework.Core.Abstractions.Events;
using GFramework.Game.Abstractions.Config;
using GFramework.Game.Config;
+using YamlDotNet.Serialization;
namespace GFramework.Game.Tests.Config;
@@ -2784,6 +2787,130 @@ public class YamlConfigLoaderTests
});
}
+ ///
+ /// 验证底层文件读取在取消时会保留 ,
+ /// 避免热重载把会话级取消误报为配置读取失败。
+ ///
+ [Test]
+ public void ReadYamlAsync_Should_Preserve_OperationCanceledException_When_Cancellation_Is_Requested()
+ {
+ CreateConfigFile(
+ "monster/slime.yaml",
+ """
+ id: 1
+ name: Slime
+ hp: 10
+ """);
+
+ var loader = new YamlConfigLoader(_rootPath)
+ .RegisterTable("monster", "monster", static config => config.Id);
+ var registration = GetSingleYamlTableRegistration(loader);
+ var readYamlAsyncMethod = registration.GetType()
+ .GetMethod("ReadYamlAsync", BindingFlags.Instance | BindingFlags.NonPublic);
+
+ Assert.That(readYamlAsyncMethod, Is.Not.Null);
+
+ using var cancellationTokenSource = new CancellationTokenSource();
+ cancellationTokenSource.Cancel();
+
+ // 通过反射直接命中注册项的文件读取路径,稳定回归本次取消语义修复。
+ var readTask = (Task)readYamlAsyncMethod!.Invoke(
+ registration,
+ new object?[]
+ {
+ Path.Combine(_rootPath, "monster"),
+ Path.Combine(_rootPath, "monster", "slime.yaml"),
+ null,
+ cancellationTokenSource.Token
+ })!;
+
+ Assert.That(
+ async () => await readTask.ConfigureAwait(false),
+ Throws.InstanceOf());
+ }
+
+ ///
+ /// 验证同步反序列化阶段遇到已取消 token 时会直接透传 ,
+ /// 避免把停止加载误报为 YAML 解析失败。
+ ///
+ [Test]
+ public void DeserializeValue_Should_Preserve_OperationCanceledException_When_Cancellation_Is_Requested()
+ {
+ var loader = new YamlConfigLoader(_rootPath)
+ .RegisterTable("monster", "monster", static config => config.Id);
+ var registration = GetSingleYamlTableRegistration(loader);
+ var deserializeValueMethod = registration.GetType()
+ .GetMethod("DeserializeValue", BindingFlags.Instance | BindingFlags.NonPublic);
+
+ Assert.That(deserializeValueMethod, Is.Not.Null);
+
+ using var cancellationTokenSource = new CancellationTokenSource();
+ cancellationTokenSource.Cancel();
+
+ var deserializer = new DeserializerBuilder().Build();
+ var exception = Assert.Throws(() =>
+ deserializeValueMethod!.Invoke(
+ registration,
+ new object?[]
+ {
+ deserializer,
+ Path.Combine(_rootPath, "monster"),
+ Path.Combine(_rootPath, "monster", "slime.yaml"),
+ null,
+ """
+ id: 1
+ name: Slime
+ hp: 10
+ """,
+ cancellationTokenSource.Token
+ }));
+
+ // 反射调用同步私有方法时会把原始异常包装为 TargetInvocationException。
+ Assert.That(exception!.InnerException, Is.InstanceOf());
+ }
+
+ ///
+ /// 验证构建最终配置表阶段遇到已取消 token 时会继续透传 ,
+ /// 避免热重载把提交前取消记录成构表失败。
+ ///
+ [Test]
+ public void BuildLoadResult_Should_Preserve_OperationCanceledException_When_Cancellation_Is_Requested()
+ {
+ var loader = new YamlConfigLoader(_rootPath)
+ .RegisterTable("monster", "monster", static config => config.Id);
+ var registration = GetSingleYamlTableRegistration(loader);
+ var buildLoadResultMethod = registration.GetType()
+ .GetMethod("BuildLoadResult", BindingFlags.Instance | BindingFlags.NonPublic);
+
+ Assert.That(buildLoadResultMethod, Is.Not.Null);
+
+ using var cancellationTokenSource = new CancellationTokenSource();
+ cancellationTokenSource.Cancel();
+
+ var exception = Assert.Throws(() =>
+ buildLoadResultMethod!.Invoke(
+ registration,
+ new object?[]
+ {
+ Path.Combine(_rootPath, "monster"),
+ null,
+ new List
+ {
+ new()
+ {
+ Id = 1,
+ Name = "Slime",
+ Hp = 10
+ }
+ },
+ new List(),
+ cancellationTokenSource.Token
+ }));
+
+ // 反射调用同步私有方法时会把原始异常包装为 TargetInvocationException。
+ Assert.That(exception!.InnerException, Is.InstanceOf());
+ }
+
///
/// 验证依赖关系仅来自 contains 子 schema 时,热重载仍会追踪该依赖并在目标表破坏引用后回滚。
///
@@ -2928,7 +3055,7 @@ public class YamlConfigLoaderTests
Assert.That(exception!.ParamName, Is.EqualTo("options"));
}
-
+
///
/// 验证热重载失败时会保留旧表状态,并通过失败回调暴露诊断信息。
///
@@ -3372,6 +3499,22 @@ public class YamlConfigLoaderTests
CreateConfigFile(relativePath, content);
}
+ private static object GetSingleYamlTableRegistration(YamlConfigLoader loader)
+ {
+ var registrationsField = typeof(YamlConfigLoader).GetField(
+ "_registrations",
+ BindingFlags.Instance | BindingFlags.NonPublic);
+
+ Assert.That(registrationsField, Is.Not.Null);
+
+ var registrations = registrationsField!.GetValue(loader) as System.Collections.IList;
+
+ Assert.That(registrations, Is.Not.Null);
+ Assert.That(registrations!.Count, Is.EqualTo(1));
+
+ return registrations[0]!;
+ }
+
///
/// 在限定时间内等待异步任务完成,避免文件监听测试无限挂起。
///
diff --git a/GFramework.Game/Config/YamlConfigLoader.cs b/GFramework.Game/Config/YamlConfigLoader.cs
index ce6d9ae3..d6faaaf0 100644
--- a/GFramework.Game/Config/YamlConfigLoader.cs
+++ b/GFramework.Game/Config/YamlConfigLoader.cs
@@ -1,4 +1,6 @@
using System.Diagnostics;
+using System.Globalization;
+using System.Threading;
using GFramework.Core.Abstractions.Events;
using GFramework.Game.Abstractions.Config;
using YamlDotNet.Serialization;
@@ -472,93 +474,178 @@ public sealed class YamlConfigLoader : IConfigLoader
IDeserializer deserializer,
CancellationToken cancellationToken)
{
- var directoryPath = Path.Combine(rootPath, RelativePath);
- if (!Directory.Exists(directoryPath))
- {
- throw ConfigLoadExceptionFactory.Create(
- ConfigLoadFailureKind.ConfigDirectoryNotFound,
- Name,
- $"Config directory '{directoryPath}' was not found for table '{Name}'.",
- configDirectoryPath: directoryPath);
- }
-
- YamlConfigSchema? schema = null;
- IReadOnlyCollection referencedTableNames = Array.Empty();
- if (!string.IsNullOrEmpty(SchemaRelativePath))
- {
- var schemaPath = Path.Combine(rootPath, SchemaRelativePath);
- schema = await YamlConfigSchemaValidator.LoadAsync(Name, schemaPath, cancellationToken)
- .ConfigureAwait(false);
- referencedTableNames = schema.ReferencedTableNames;
- }
-
+ var directoryPath = GetValidatedDirectoryPath(rootPath);
+ var schema = await LoadSchemaAsync(rootPath, cancellationToken).ConfigureAwait(false);
var referenceUsages = new List();
+ var values = await LoadValuesAsync(
+ directoryPath,
+ deserializer,
+ schema,
+ referenceUsages,
+ cancellationToken)
+ .ConfigureAwait(false);
+ return BuildLoadResult(directoryPath, schema, values, referenceUsages, cancellationToken);
+ }
+
+ private string GetValidatedDirectoryPath(string rootPath)
+ {
+ var directoryPath = Path.Combine(rootPath, RelativePath);
+ if (Directory.Exists(directoryPath))
+ {
+ return directoryPath;
+ }
+
+ throw ConfigLoadExceptionFactory.Create(
+ ConfigLoadFailureKind.ConfigDirectoryNotFound,
+ Name,
+ $"Config directory '{directoryPath}' was not found for table '{Name}'.",
+ configDirectoryPath: directoryPath);
+ }
+
+ private async Task LoadSchemaAsync(string rootPath, CancellationToken cancellationToken)
+ {
+ if (string.IsNullOrEmpty(SchemaRelativePath))
+ {
+ return null;
+ }
+
+ var schemaPath = Path.Combine(rootPath, SchemaRelativePath);
+ return await YamlConfigSchemaValidator.LoadAsync(Name, schemaPath, cancellationToken).ConfigureAwait(false);
+ }
+
+ private async Task> LoadValuesAsync(
+ string directoryPath,
+ IDeserializer deserializer,
+ YamlConfigSchema? schema,
+ List referenceUsages,
+ CancellationToken cancellationToken)
+ {
var values = new List();
- var files = Directory
+ foreach (var file in GetYamlFiles(directoryPath))
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ var yaml = await ReadYamlAsync(directoryPath, file, schema, cancellationToken).ConfigureAwait(false);
+ CollectReferenceUsages(referenceUsages, schema, file, yaml);
+ values.Add(DeserializeValue(deserializer, directoryPath, file, schema, yaml, cancellationToken));
+ }
+
+ return values;
+ }
+
+ private static string[] GetYamlFiles(string directoryPath)
+ {
+ return Directory
.EnumerateFiles(directoryPath, "*.*", SearchOption.TopDirectoryOnly)
.Where(static path =>
path.EndsWith(".yaml", StringComparison.OrdinalIgnoreCase) ||
path.EndsWith(".yml", StringComparison.OrdinalIgnoreCase))
.OrderBy(static path => path, StringComparer.Ordinal)
.ToArray();
+ }
- foreach (var file in files)
- {
- cancellationToken.ThrowIfCancellationRequested();
-
- string yaml;
- try
- {
- yaml = await File.ReadAllTextAsync(file, cancellationToken).ConfigureAwait(false);
- }
- catch (Exception exception)
- {
- throw ConfigLoadExceptionFactory.Create(
- ConfigLoadFailureKind.ConfigFileReadFailed,
- Name,
- $"Failed to read config file '{file}' for table '{Name}'.",
- configDirectoryPath: directoryPath,
- yamlPath: file,
- schemaPath: schema?.SchemaPath,
- innerException: exception);
- }
-
- if (schema != null)
- {
- // 先按 schema 拒绝结构问题并提取跨表引用,避免被 IgnoreUnmatchedProperties 或默认值掩盖配置错误。
- referenceUsages.AddRange(
- YamlConfigSchemaValidator.ValidateAndCollectReferences(Name, schema, file, yaml));
- }
-
- try
- {
- var value = deserializer.Deserialize(yaml);
-
- if (value == null)
- {
- throw new InvalidOperationException("YAML content was deserialized to null.");
- }
-
- values.Add(value);
- }
- catch (Exception exception)
- {
- throw ConfigLoadExceptionFactory.Create(
- ConfigLoadFailureKind.DeserializationFailed,
- Name,
- $"Failed to deserialize config file '{file}' for table '{Name}' as '{typeof(TValue).Name}'.",
- configDirectoryPath: directoryPath,
- yamlPath: file,
- schemaPath: schema?.SchemaPath,
- detail: $"Target CLR type: {typeof(TValue).FullName}.",
- innerException: exception);
- }
- }
-
+ private async Task ReadYamlAsync(
+ string directoryPath,
+ string file,
+ YamlConfigSchema? schema,
+ CancellationToken cancellationToken)
+ {
try
{
+ return await File.ReadAllTextAsync(file, cancellationToken).ConfigureAwait(false);
+ }
+ catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
+ {
+ // 保留原始取消语义,避免热重载把会话级取消误报为配置读取失败。
+ throw;
+ }
+ catch (Exception exception)
+ {
+ throw ConfigLoadExceptionFactory.Create(
+ ConfigLoadFailureKind.ConfigFileReadFailed,
+ Name,
+ $"Failed to read config file '{file}' for table '{Name}'.",
+ configDirectoryPath: directoryPath,
+ yamlPath: file,
+ schemaPath: schema?.SchemaPath,
+ innerException: exception);
+ }
+ }
+
+ private void CollectReferenceUsages(
+ List referenceUsages,
+ YamlConfigSchema? schema,
+ string file,
+ string yaml)
+ {
+ if (schema == null)
+ {
+ return;
+ }
+
+ // 先按 schema 拒绝结构问题并提取跨表引用,避免被 IgnoreUnmatchedProperties 或默认值掩盖配置错误。
+ referenceUsages.AddRange(
+ YamlConfigSchemaValidator.ValidateAndCollectReferences(Name, schema, file, yaml));
+ }
+
+ private TValue DeserializeValue(
+ IDeserializer deserializer,
+ string directoryPath,
+ string file,
+ YamlConfigSchema? schema,
+ string yaml,
+ CancellationToken cancellationToken)
+ {
+ try
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ var value = deserializer.Deserialize(yaml);
+ if (value != null)
+ {
+ return value;
+ }
+
+ throw new InvalidOperationException("YAML content was deserialized to null.");
+ }
+ catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
+ {
+ // 同步反序列化阶段也要透传会话级取消,避免把停止加载误报为 YAML 解析失败。
+ throw;
+ }
+ catch (Exception exception)
+ {
+ throw ConfigLoadExceptionFactory.Create(
+ ConfigLoadFailureKind.DeserializationFailed,
+ Name,
+ $"Failed to deserialize config file '{file}' for table '{Name}' as '{typeof(TValue).Name}'.",
+ configDirectoryPath: directoryPath,
+ yamlPath: file,
+ schemaPath: schema?.SchemaPath,
+ detail: $"Target CLR type: {typeof(TValue).FullName}.",
+ innerException: exception);
+ }
+ }
+
+ private YamlTableLoadResult BuildLoadResult(
+ string directoryPath,
+ YamlConfigSchema? schema,
+ List values,
+ List referenceUsages,
+ CancellationToken cancellationToken)
+ {
+ try
+ {
+ cancellationToken.ThrowIfCancellationRequested();
var table = new InMemoryConfigTable(values, _keySelector, _comparer);
- return new YamlTableLoadResult(Name, table, referencedTableNames, referenceUsages);
+ return new YamlTableLoadResult(
+ Name,
+ table,
+ schema?.ReferencedTableNames ?? Array.Empty(),
+ referenceUsages);
+ }
+ catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
+ {
+ // 构建最终配置表时继续保留原始取消语义,避免热重载把提交前取消记录成构表失败。
+ throw;
}
catch (Exception exception)
{
@@ -630,6 +717,12 @@ public sealed class YamlConfigLoader : IConfigLoader
///
private static class CrossTableReferenceValidator
{
+ private delegate bool IntegerTryParseDelegate(
+ string? value,
+ NumberStyles style,
+ IFormatProvider? provider,
+ out T result);
+
///
/// 使用本轮新加载结果与注册表中保留的旧表,一起验证跨表引用是否全部有效。
///
@@ -754,59 +847,15 @@ public sealed class YamlConfigLoader : IConfigLoader
convertedKey = null;
errorMessage = string.Empty;
- if (targetKeyType == typeof(int) &&
- int.TryParse(rawValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intValue))
+ if (TryConvertIntegerKey(rawValue, targetKeyType, typeof(int), int.TryParse, out convertedKey) ||
+ TryConvertIntegerKey(rawValue, targetKeyType, typeof(long), long.TryParse, out convertedKey) ||
+ TryConvertIntegerKey(rawValue, targetKeyType, typeof(short), short.TryParse, out convertedKey) ||
+ TryConvertIntegerKey(rawValue, targetKeyType, typeof(byte), byte.TryParse, out convertedKey) ||
+ TryConvertIntegerKey(rawValue, targetKeyType, typeof(uint), uint.TryParse, out convertedKey) ||
+ TryConvertIntegerKey(rawValue, targetKeyType, typeof(ulong), ulong.TryParse, out convertedKey) ||
+ TryConvertIntegerKey(rawValue, targetKeyType, typeof(ushort), ushort.TryParse, out convertedKey) ||
+ TryConvertIntegerKey(rawValue, targetKeyType, typeof(sbyte), sbyte.TryParse, out convertedKey))
{
- convertedKey = intValue;
- return true;
- }
-
- if (targetKeyType == typeof(long) &&
- long.TryParse(rawValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out var longValue))
- {
- convertedKey = longValue;
- return true;
- }
-
- if (targetKeyType == typeof(short) &&
- short.TryParse(rawValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out var shortValue))
- {
- convertedKey = shortValue;
- return true;
- }
-
- if (targetKeyType == typeof(byte) &&
- byte.TryParse(rawValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out var byteValue))
- {
- convertedKey = byteValue;
- return true;
- }
-
- if (targetKeyType == typeof(uint) &&
- uint.TryParse(rawValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out var uintValue))
- {
- convertedKey = uintValue;
- return true;
- }
-
- if (targetKeyType == typeof(ulong) &&
- ulong.TryParse(rawValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out var ulongValue))
- {
- convertedKey = ulongValue;
- return true;
- }
-
- if (targetKeyType == typeof(ushort) &&
- ushort.TryParse(rawValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out var ushortValue))
- {
- convertedKey = ushortValue;
- return true;
- }
-
- if (targetKeyType == typeof(sbyte) &&
- sbyte.TryParse(rawValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out var sbyteValue))
- {
- convertedKey = sbyteValue;
return true;
}
@@ -815,6 +864,25 @@ public sealed class YamlConfigLoader : IConfigLoader
return false;
}
+ private static bool TryConvertIntegerKey(
+ string rawValue,
+ Type targetKeyType,
+ Type supportedType,
+ IntegerTryParseDelegate tryParse,
+ out object? convertedKey)
+ where T : struct
+ {
+ convertedKey = null;
+ if (targetKeyType != supportedType ||
+ !tryParse(rawValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedValue))
+ {
+ return false;
+ }
+
+ convertedKey = parsedValue;
+ return true;
+ }
+
private static bool ContainsKey(IConfigTable table, object key)
{
var tableInterface = table.GetType()
@@ -838,7 +906,13 @@ public sealed class YamlConfigLoader : IConfigLoader
new(StringComparer.Ordinal);
private readonly IDeserializer _deserializer;
+#if NET9_0_OR_GREATER
+ // net9.0 及以上目标使用专用 Lock,以满足分析器对专用同步原语的建议。
+ private readonly Lock _gate = new();
+#else
+ // net8.0 目标仍回退到 object 锁,以保持多目标编译兼容性。
private readonly object _gate = new();
+#endif
private readonly Action? _onTableReloaded;
private readonly Action? _onTableReloadFailed;
private readonly Dictionary _registrations = new(StringComparer.Ordinal);
@@ -1121,7 +1195,7 @@ public sealed class YamlConfigLoader : IConfigLoader
foreach (var dependency in _dependenciesByTable)
{
- if (!dependency.Value.Contains(currentTableName))
+ if (!ContainsDependency(dependency.Value, currentTableName))
{
continue;
}
@@ -1138,6 +1212,14 @@ public sealed class YamlConfigLoader : IConfigLoader
.ToArray();
}
+ private static bool ContainsDependency(
+ IReadOnlyCollection dependencies,
+ string tableName)
+ {
+ return dependencies.Any(
+ dependency => string.Equals(dependency, tableName, StringComparison.Ordinal));
+ }
+
private void InvokeReloaded(string tableName)
{
if (_onTableReloaded == null)
diff --git a/ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md b/ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md
index 67fa6d19..55ad6417 100644
--- a/ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md
+++ b/ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md
@@ -6,54 +6,43 @@
## 当前恢复点
-- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-081`
-- 当前阶段:`Phase 81`
+- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-084`
+- 当前阶段:`Phase 84`
- 当前焦点:
- - `2026-04-27` 已复核 PR `#295` 的 latest-head review,确认 `ThrowShouldNotRetry` 的 `ParamName` open thread 属于 stale finding,本地代码已经使用传入值而非 `nameof(parameterName)`
- - 已清理 `AsyncExtensionsTests.WithRetry_Should_Respect_ShouldRetry_Predicate` 中的冗余 `Task.Delay(50)`,保留 `ParamName == nameof(taskFactory)` 断言锁定契约
- - 已增强 `.agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py` 的 failed-test 表格解析,允许 `Name` / `Failure Message` 后出现尾随额外列
- - 已新增 Python `unittest` 回归用例覆盖“尾随额外列不影响前两列提取”的场景
- - 当前剩余 warning 热点仍集中在 `YamlConfigSchemaValidator*`、`YamlConfigLoader.cs` 与大批量 `MA0048` 文件名拆分;这些 slice 仍高于本轮 PR review follow-up 的低风险边界
+ - `2026-04-27` 已完成 PR `#297` 的 CodeRabbit follow-up,修复 `YamlConfigLoader` 的取消语义与 `IntegerTryParseDelegate` 可空性问题
+ - 已补齐 `GFramework.Core.Tests/Ioc` 与 `GFramework.Core.Tests/Query` 中 review 指向的 XML 文档缺口,并让 `IPrioritizedService` 复用 `IMixedService.Name` 契约
+ - 已新增 `YamlConfigLoaderTests` 回归测试,锁定“取消时保留 `OperationCanceledException`”这一行为
+ - 当前分支的下一波 warning reduction 仍建议回到 `ArchitectureContextTests.cs`、`AsyncQueryExecutorTests.cs` 或 `YamlConfigSchemaValidator*` 的后续 slice
## 当前活跃事实
-- 当前 `origin/main` 基线提交为 `617e0bf`(`2026-04-26T12:17:15+08:00`)。
-- 当前 PR review 真值:
- - `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --json-output `
- - 最新结果:成功;当前分支对应 PR 为 `#295`
- - 当前测试报告输出已能显示 `Summary` 统计、失败测试名称,以及 `Name / Failure Message` 表格中的关键信息
- - 当前 GitHub latest-head review 仍显示 `1` 条 open thread,但该线程指向的 `nameof(parameterName)` 问题已不在本地代码中成立,属于 stale finding
- - 当前 latest review 中仍有 `2` 条与本地工作树一致的 nitpick:`AsyncExtensionsTests` 冗余等待,以及 failed-test 表格解析对尾随列不鲁棒
+- 当前 `origin/main` 基线提交为 `b6a9fef`(`2026-04-27T10:53:34+08:00`)。
- 当前直接验证结果:
- - `python3 .agents/skills/gframework-pr-review/scripts/test_fetch_current_pr_review.py`
- - 最新结果:成功;`Ran 1 test in 0.000s`, `OK`
- - `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --section tests --json-output /tmp/current-pr-review-postfix.json`
- - 最新结果:成功;真实 PR 评论抓取仍能输出 `2` 份测试报告,失败用例详情保持可见
- - `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~WithRetry_Should_Respect_ShouldRetry_Predicate"`
- - 最新结果:成功;`Failed: 0, Passed: 1, Skipped: 0, Total: 1`
- - `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~RegisterMigration_During_Cache_Rebuild_Should_Not_Leave_Stale_Type_Cache"`
- - 最新结果:成功;`Failed: 0, Passed: 1, Skipped: 0, Total: 1`
-- 当前分支 stop-condition 指标:
- - `git diff --name-only refs/remotes/origin/main...HEAD | wc -l`
- - 最新结果:`35`
- - `git diff --numstat refs/remotes/origin/main...HEAD`
- - 最新结果:`642` changed lines
+ - `dotnet build GFramework.Game/GFramework.Game.csproj -c Release`
+ - 最新结果:成功;`0 Warning(s)`、`0 Error(s)`
+ - `dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release`
+ - 最新结果:成功;`0 Warning(s)`、`0 Error(s)`
+ - `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~YamlConfigLoaderTests.ReadYamlAsync_Should_Preserve_OperationCanceledException_When_Cancellation_Is_Requested"`
+ - 最新结果:成功;`1` 通过、`0` 失败
+ - `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~MicrosoftDiContainerTests.GetAllByPriority_Should_Sort_By_Priority_Ascending"`
+ - 最新结果:成功;`1` 通过、`0` 失败
+ - `dotnet format GFramework.sln --verify-no-changes --include GFramework.Game/Config/YamlConfigLoader.cs GFramework.Game.Tests/Config/YamlConfigLoaderTests.cs GFramework.Core.Tests/Ioc/IMixedService.cs GFramework.Core.Tests/Ioc/IPrioritizedService.cs GFramework.Core.Tests/Ioc/PrioritizedService.cs GFramework.Core.Tests/Query/TestAsyncQueryWithExceptionV4.cs`
+ - 最新结果:成功;本次 PR follow-up 改动文件无需额外格式化
- 当前批次摘要:
- - 三轮低风险 warning 清理已在此前验证中将仓库根 warning 从 `639` 降到 `397`
- - 当前批次的已完成 slice 明细已迁移到归档,active todo 仅保留恢复真值
- - 本轮新增内容为 PR review nitpick 收口与脚本回归测试补齐,不扩展 warning reduction 的热点清理边界
+ - 本轮完成 PR `#297` 最新 head review 中仍然有效的 `3` 个 open threads 修复:`YamlConfigLoader` 取消语义、`IMixedService.Name` XML 文档、`IPrioritizedService` 相关契约整理
+ - 本轮同时吸收 CodeRabbit folded nitpick 中仍然成立的 `2` 个点:`IntegerTryParseDelegate` 可空性对齐、`TestAsyncQueryWithExceptionV4.OnDoAsync` 的 `` 文档
+ - 本轮新增一条精确回归测试,确保底层 YAML 文件读取在取消时继续抛出 `OperationCanceledException` 系列异常,而不是包装成 `ConfigLoadException`
- 当前建议保留到下一波次的候选:
- - `GFramework.Game/Config/YamlConfigLoader.cs` 的 `MA0158`(单点可修,但文件本身同时承载其他高耦合 warning)
- - 测试项目中的 `MA0048` 文件名拆分波次(会显著增加 changed-file 数)
+ - `GFramework.Core.Tests/Architectures/ArchitectureContextTests.cs` 的 `7` 个 `MA0048`
+ - `GFramework.Core.Tests/Query/AsyncQueryExecutorTests.cs` 的 `7` 个 `MA0048`
+ - `GFramework.Game/Config/YamlConfigSchemaValidator.cs` 与 `YamlConfigSchemaValidator.ObjectKeywords.cs` 的高耦合 warning 热点
## 当前风险
-- `GFramework.Game/Config/YamlConfigSchemaValidator*.cs` 仍然聚集多类高耦合 warning。
- - 缓解措施:本轮先避开该热点,只清理低风险且 ownership 清晰的文件集合。
-- `MA0158` 迁移涉及 `net8.0` / `net9.0` / `net10.0` 多目标兼容。
- - 缓解措施:复用 `StoreSelection.cs` 已存在的 `#if NET9_0_OR_GREATER` 专用锁模式,不在 `net8.0` 引入不兼容 API。
-- 当前 PR open thread 与 CI 失败信号仍依赖新提交进入远端 PR head 才能复核。
- - 缓解措施:本轮提交并推送后重新执行 `$gframework-pr-review`,确认 stale open thread 是否被 GitHub 收口,以及两条 nitpick 是否从 latest review 中消失。
+- `GFramework.Cqrs.Tests/Mediator/*` 仍有 `47` / `44` / `34` 个唯一 warning 位点,属于高 changed-file 风险的 `MA0048` 大波次。
+ - 缓解措施:优先继续处理 `6-7` 个 warning 的小文件切片,避免一次性推高文件数。
+- `YamlConfigSchemaValidator*` 仍然聚集多类高耦合 warning。
+ - 缓解措施:继续把它们留在独立波次,不与测试项目的低风险拆分混提。
## 活跃文档
@@ -73,11 +62,11 @@
## 验证说明
- 权威验证结果统一维护在“当前活跃事实”。
-- `GFramework.Core.Tests` 当前仍有既有 analyzer / nullable warning 基线,因此本轮验证只证明 PR review 修复未引入构建错误,未将该项目 warning 清零。
-- 后续若刷新构建或 PR review 真值,只更新上述权威区块,不在本节重复抄录。
+- `GFramework.Core.Tests` 项目级 Release 构建已在本轮清零,但仓库根 non-incremental 构建仍保留大量既有 warning。
+- warning reduction 的仓库级真值只以同轮 `dotnet clean` 后的 `dotnet build` 为准。
## 下一步建议
-1. 提交本轮 `AsyncExtensionsTests` / `$gframework-pr-review` nitpick 修复、Python 回归测试与 `ai-plan` 同步。
-2. 推送后重新执行 `$gframework-pr-review`,确认 PR `#295` 的 stale open thread、nitpick 与测试报告是否已刷新为新 head 真值。
-3. 若后续继续推进 warning reduction,建议另开下一波次处理 `YamlConfigLoader.cs` 热点或测试项目 `MA0048` 拆分波次。
+1. 提交本轮 PR `#297` review follow-up 与 `ai-plan` 同步。
+2. 下一波优先挑选 `ArchitectureContextTests.cs` 或 `AsyncQueryExecutorTests.cs` 这类 `7`-warning 的纯 `MA0048` 单文件切片。
+3. 继续将 `YamlConfigSchemaValidator*` 与 `GFramework.Cqrs.Tests/Mediator/*` 作为独立高风险波次处理。
diff --git a/ai-plan/public/analyzer-warning-reduction/traces/analyzer-warning-reduction-trace.md b/ai-plan/public/analyzer-warning-reduction/traces/analyzer-warning-reduction-trace.md
index 9c8bc443..20ac885c 100644
--- a/ai-plan/public/analyzer-warning-reduction/traces/analyzer-warning-reduction-trace.md
+++ b/ai-plan/public/analyzer-warning-reduction/traces/analyzer-warning-reduction-trace.md
@@ -1,39 +1,75 @@
# Analyzer Warning Reduction 追踪
-## 2026-04-27 — RP-081
+## 2026-04-27 — RP-084
-### 阶段:核实 PR `#295` 的剩余 nitpick,并补齐脚本解析回归测试
+### 阶段:收敛 PR #297 的 CodeRabbit follow-up
- 触发背景:
- - 用户再次执行 `$gframework-pr-review`,需要根据当前 PR `#295` 的 latest-head review 继续核实哪些反馈仍需在本地处理
- - 远端 review 显示 `1` 条 open thread 与 `2` 条 nitpick,需要区分 stale finding 与仍然成立的本地问题
+ - 用户执行 `$gframework-pr-review`,要求以当前分支对应 PR 为准,提取并核对 AI review / check 信号
+ - `fetch_current_pr_review.py` 返回 PR `#297` 的最新 head review 中仍有 `3` 个 open threads,另有 `2` 个 folded nitpick 仍然适用
- 主线程实施:
- - 复核 `/tmp/current-pr-review.json` 与本地 `AsyncExtensionsTests.cs`,确认 open thread 指向的 `nameof(parameterName)` 问题已在现有代码中修复,属于 stale finding
- - 删除 `GFramework.Core.Tests/Extensions/AsyncExtensionsTests.cs` 中 `WithRetry_Should_Respect_ShouldRetry_Predicate` 的冗余 `Task.Delay(50)`,将测试改回同步断言路径
- - 调整 `.agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py` 的 `parse_failed_test_details`,允许 failed-test HTML 表格在 `Name` / `Failure Message` 后追加额外列
- - 新增 `.agents/skills/gframework-pr-review/scripts/test_fetch_current_pr_review.py`,以 `unittest` 覆盖“尾随额外列不影响前两列提取”的回归场景
+ - 校验 `GFramework.Game/Config/YamlConfigLoader.cs` 后,保留 `ReadYamlAsync` 的原始取消语义,并把 `IntegerTryParseDelegate` 第一个参数改为 `string?`
+ - 校验 `GFramework.Core.Tests/Ioc/*` 与 `Query/TestAsyncQueryWithExceptionV4.cs` 后,补齐缺失 XML 文档,让 `IPrioritizedService` 继承 `IMixedService` 复用 `Name` 契约,并补上 `` 文档
+ - 新增 `YamlConfigLoaderTests.ReadYamlAsync_Should_Preserve_OperationCanceledException_When_Cancellation_Is_Requested`,用反射直接命中私有读取路径,稳定回归本次取消语义修复
+ - 用 `dotnet format --verify-no-changes --include ...` 清理并验证本次改动文件的格式状态
- 验证里程碑:
- - `python3 .agents/skills/gframework-pr-review/scripts/test_fetch_current_pr_review.py`
- - 结果:成功;`Ran 1 test in 0.000s`, `OK`
- - `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --section tests --json-output /tmp/current-pr-review-postfix.json`
- - 结果:成功;真实 PR 评论抓取仍显示 `2` 份测试报告,失败测试名与 failure message 摘要保持可见
- - `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~WithRetry_Should_Respect_ShouldRetry_Predicate"`
- - 结果:成功;`Failed: 0, Passed: 1, Skipped: 0, Total: 1`
+ - `dotnet test GFramework.Game.Tests/GFramework.Game.Tests.csproj -c Release --filter "FullyQualifiedName~YamlConfigLoaderTests.ReadYamlAsync_Should_Preserve_OperationCanceledException_When_Cancellation_Is_Requested"`
+ - 结果:成功;`1` 通过、`0` 失败
+ - `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~MicrosoftDiContainerTests.GetAllByPriority_Should_Sort_By_Priority_Ascending"`
+ - 结果:成功;`1` 通过、`0` 失败
+ - `dotnet build GFramework.Game/GFramework.Game.csproj -c Release`
+ - 结果:成功;`0 Warning(s)`、`0 Error(s)`
+ - `dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release`
+ - 结果:成功;`0 Warning(s)`、`0 Error(s)`
+ - `dotnet format GFramework.sln --verify-no-changes --include GFramework.Game/Config/YamlConfigLoader.cs GFramework.Game.Tests/Config/YamlConfigLoaderTests.cs GFramework.Core.Tests/Ioc/IMixedService.cs GFramework.Core.Tests/Ioc/IPrioritizedService.cs GFramework.Core.Tests/Ioc/PrioritizedService.cs GFramework.Core.Tests/Query/TestAsyncQueryWithExceptionV4.cs`
+ - 结果:成功
- 当前结论:
- - 本轮 latest-head review 中只有 `AsyncExtensionsTests` 冗余等待与 failed-test 表格尾随列容错性两个 nitpick 仍与本地代码一致,现已修复
- - `ThrowShouldNotRetry` 的 `ParamName` open thread 属于 stale finding,本地代码已经符合预期,只需等待新提交进入远端后复核 thread 状态
+ - PR `#297` 当前仍然有效的 CodeRabbit open threads 与 folded nitpick 已在本地全部核对并收敛
+ - 当前恢复点完成后,分支可以回到 `ArchitectureContextTests.cs` / `AsyncQueryExecutorTests.cs` / `YamlConfigSchemaValidator*` 的 warning reduction 主线
+- 下一步:
+ 1. 提交本轮 PR review follow-up。
+ 2. 继续执行下一波 `MA0048` 小切片,优先避免一次性进入 `Mediator*` 的高 changed-file 风险波次。
+
+## 2026-04-27 — RP-083
+
+### 阶段:修复 `YamlConfigLoader` 单文件 warning,并拆分 `MicrosoftDiContainerTests` 的辅助类型
+
+- 触发背景:
+ - 用户执行 `$gframework-batch-boot 50`,要求先拿仓库根构建 warning,再按 bounded slice 分派给不同 subagent 并持续推进
+ - 当前分支在本轮开始时与 `origin/main@b6a9fef` 零提交差异,适合从低风险 warning slice 起步
+- 主线程实施:
+ - 先执行 non-incremental 仓库根基线:`dotnet clean` + `dotnet build`,得到 `397 Warning(s)` / `316` 个唯一位点
+ - 主线程修复 `GFramework.Game/Config/YamlConfigLoader.cs` 的 `MA0051`、`MA0002` 与 `MA0158`
+ - 接受一个 worker batch:将 `GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs` 末尾的 `10` 个测试辅助接口/类拆分到 `Ioc/` 同目录独立文件
+ - 接受第二波 worker 的已落地结果:将 `GFramework.Core.Tests/Query/AbstractAsyncQueryTests.cs` 末尾的 `7` 个测试辅助类型拆分到 `Query/` 同目录独立文件
+ - 启动 `ArchitectureContextTests.cs` 候选 worker,但在共享工作树落地前主动停止,以避免本轮上下文与 review 面积继续膨胀
+- 验证里程碑:
+ - `dotnet build GFramework.Game/GFramework.Game.csproj -c Release`
+ - 结果:成功;`111 Warning(s)`、`0 Error(s)`
+ - 观察:构建输出未再报告 `GFramework.Game/Config/YamlConfigLoader.cs`
+ - `dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release`
+ - 结果:成功;`0 Warning(s)`、`0 Error(s)`
+ - `dotnet clean`
+ - 结果:成功;刷新最终 non-incremental 仓库根 warning 基线
+ - `dotnet build`
+ - 结果:成功;`353 Warning(s)`、`0 Error(s)`,唯一位点 `279`
+ - 观察:构建输出未再报告 `GFramework.Game/Config/YamlConfigLoader.cs`、`GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs` 与 `GFramework.Core.Tests/Query/AbstractAsyncQueryTests.cs`
+- 当前结论:
+ - 本轮已完成一个主线程单文件 slice 和两个 worker 拆分 slice;仓库根 non-incremental warning 从 `397` 降到 `353`
+ - 当前共享工作树 footprint 为 `22` 个 changed files,仍低于 `$gframework-batch-boot 50` 的停止线
+ - 下一波更适合继续处理 `7` 个 `MA0048` 的小文件,而不是立即进入 `Mediator*` 或 `YamlConfigSchemaValidator*` 的高耦合热点
## 活跃风险
-- PR 上的 latest-head review thread 与测试报告仍需要等新提交进入远端后再复核。
- - 缓解措施:提交并推送后重新执行 `$gframework-pr-review`,只以新的 latest-head 和 test report 为准。
-- `YamlConfigSchemaValidator*`、`YamlConfigLoader.cs` 与 `MA0048` 拆分仍是下一波次的高耦合候选。
- - 缓解措施:保持本轮边界只处理 PR review nitpick follow-up,不顺手扩展 warning reduction 范围。
+- `GFramework.Cqrs.Tests/Mediator/*` 的 `MA0048` 位点密度很高,一次性拆分会迅速推高 changed-file 数。
+ - 缓解措施:下一波优先继续拿 `7` warning 级别的小切片。
+- `YamlConfigSchemaValidator*` 仍然聚集多类高耦合 warning。
+ - 缓解措施:继续维持为独立波次,不与测试项目拆分混提。
## 下一步
-1. 完成本轮提交。
-2. 推送后重新执行 `$gframework-pr-review`,确认 PR `#295` 的 stale open thread 与 nitpick 是否已刷新。
+1. 完成本轮 `YamlConfigLoader.cs`、`MicrosoftDiContainerTests.cs` 与 `ai-plan` 的提交。
+2. 下一波优先从 `ArchitectureContextTests.cs` 或 `AsyncQueryExecutorTests.cs` 继续拆分纯 `MA0048`。
## 历史归档指针