diff --git a/GFramework.Core.Tests/Events/EventTests.cs b/GFramework.Core.Tests/Events/EventTests.cs index d77981a5..d0dfd7ed 100644 --- a/GFramework.Core.Tests/Events/EventTests.cs +++ b/GFramework.Core.Tests/Events/EventTests.cs @@ -124,6 +124,24 @@ public class EventTests Assert.That(values, Does.Contain(10)); } + /// + /// 测试单参数事件的监听器计数只统计真实注册的处理器。 + /// + [Test] + public void EventT_GetListenerCount_Should_Exclude_Placeholder_Handler() + { + Assert.That(_eventInt.GetListenerCount(), Is.EqualTo(0)); + + Action handler = _ => { }; + _eventInt.Register(handler); + + Assert.That(_eventInt.GetListenerCount(), Is.EqualTo(1)); + + _eventInt.UnRegister(handler); + + Assert.That(_eventInt.GetListenerCount(), Is.EqualTo(0)); + } + /// /// 测试带两个泛型参数的事件注册功能是否正确添加处理器 /// @@ -161,4 +179,22 @@ public class EventTests _eventIntString.Trigger(2, "b"); Assert.That(count, Is.EqualTo(1)); } -} \ No newline at end of file + + /// + /// 测试双参数事件的监听器计数只统计真实注册的处理器。 + /// + [Test] + public void EventTTK_GetListenerCount_Should_Exclude_Placeholder_Handler() + { + Assert.That(_eventIntString.GetListenerCount(), Is.EqualTo(0)); + + Action handler = (_, _) => { }; + _eventIntString.Register(handler); + + Assert.That(_eventIntString.GetListenerCount(), Is.EqualTo(1)); + + _eventIntString.UnRegister(handler); + + Assert.That(_eventIntString.GetListenerCount(), Is.EqualTo(0)); + } +} diff --git a/GFramework.Core/Events/Event.cs b/GFramework.Core/Events/Event.cs index 57205255..c1f0707e 100644 --- a/GFramework.Core/Events/Event.cs +++ b/GFramework.Core/Events/Event.cs @@ -11,9 +11,9 @@ public class Event : IEvent { /// /// 存储已注册的事件处理委托。 - /// 默认为空操作(no-op)委托,避免 null 检查。 + /// 未注册监听器时保持 ,从而让监听器计数与真实订阅数量保持一致。 /// - private Action? _mOnEvent = _ => { }; + private Action? _mOnEvent; /// /// 显式实现 接口中的 Register 方法。 @@ -80,9 +80,9 @@ public class Event : IEvent { /// /// 存储已注册的双参数事件处理委托。 - /// 默认为空操作(no-op)委托。 + /// 未注册监听器时保持 ,从而让监听器计数与真实订阅数量保持一致。 /// - private Action? _mOnEvent = (_, _) => { }; + private Action? _mOnEvent; /// /// 显式实现 接口中的 Register 方法。 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 550a1e0e..39a2931d 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 @@ -7,10 +7,10 @@ ## 当前恢复点 -- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-010` -- 当前阶段:`Phase 10` +- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-011` +- 当前阶段:`Phase 11` - 当前焦点: - - 当前 PR #265 的 follow-up 已收口;下一轮恢复到 `MA0046` 主批次 + - 当前 PR #265 的 follow-up 已继续收口到 `Event.cs` 监听器计数修正;下一轮恢复到 `MA0046` 主批次 - 后续继续按 warning 类型和数量批处理,而不是回退到按单文件切片推进 - 当某一轮主类型数量不足时,允许顺手合并其他低冲突 warning 类型,`MA0015` 与 `MA0077` 只是当前最明显的低数量示例,不构成限定 @@ -22,6 +22,7 @@ - 已完成 `GFramework.Core`、`GFramework.Cqrs`、`GFramework.Godot` 与部分 source generator 的低风险 warning 清理 - 已完成多轮 CodeRabbit follow-up 修复,并用定向测试与项目/解决方案构建验证了关键回归风险 - 已完成当前 PR #265 review follow-up:修复 `CoroutineScheduler` 的零容量扩容边界,并补上 `Store` dispatch 作用域的异常安全回滚 +- 已继续完成当前 PR #265 review follow-up:修复 `Event` 与 `Event` 监听器计数的 off-by-one,并补充回归测试 - 当前 `PauseStackManager`、`Store`、`CoroutineScheduler` 与 `GFramework.Core` 的 `MA0048` 文件/类型命名冲突已从 active 入口移除;主题内剩余 warning 主要集中在 `MA0046` delegate 形状、 `MA0016` 集合抽象接口、`MA0002` comparer 重载,以及 `MA0015` / `MA0077` 两个低数量尾项 @@ -45,6 +46,8 @@ - `RP-010` 使用 `gframework-pr-review` 复核当前分支 PR #265 后,修复了仍在本地成立的两个 follow-up 风险: `CoroutineScheduler` 的 `initialCapacity: 0` 扩容越界,以及 `Store` 在 dispatch 快照阶段抛异常时可能残留 `_isDispatching = true` 的锁死问题 +- `RP-011` 根据补充复核继续收口 PR #265 的 outside-diff comment,修复 `Event` / `Event` 默认 no-op + 委托导致的 `GetListenerCount()` off-by-one,并以定向事件测试验证注册、注销和计数语义 - 当前工作树分支 `fix/analyzer-warning-reduction-batch` 已在 `ai-plan/public/README.md` 建立 topic 映射 ## 当前风险 @@ -103,6 +106,11 @@ - 结果:`15 Warning(s)`,`0 Error(s)`;新增修复未引入新的 `GFramework.Core` `net8.0` 构建错误 - `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~CoroutineSchedulerTests.Run_Should_Grow_From_Zero_Initial_Capacity|FullyQualifiedName~StoreTests.Dispatch_Should_Reset_Dispatching_Flag_When_Snapshot_Creation_Throws" -m:1 -p:RestoreFallbackFolders="" -nologo` - 结果:`2 Passed`,`0 Failed` +- `RP-011` 的定向验证结果: + - `dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -nologo` + - 结果:`15 Warning(s)`,`0 Error(s)`;`Event.cs` 的 listener count 修复未引入新的 `GFramework.Core` `net8.0` 构建错误 + - `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~EventTests.EventT_GetListenerCount_Should_Exclude_Placeholder_Handler|FullyQualifiedName~EventTests.EventTTK_GetListenerCount_Should_Exclude_Placeholder_Handler" -m:1 -p:RestoreFallbackFolders="" -nologo` + - 结果:`2 Passed`,`0 Failed` - active 跟踪文件只保留当前恢复点、活跃事实、风险与下一步,不再重复保存已完成阶段的长篇历史 ## 下一步 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 106a7d97..f0c21724 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,5 +1,31 @@ # Analyzer Warning Reduction 追踪 +## 2026-04-21 — RP-011 + +### 阶段:PR #265 outside-diff follow-up 补收口(RP-011) + +- 用户补充指出 CodeRabbit 在 `Some comments are outside the diff` 中还有 `GFramework.Core/Events/Event.cs` 的 minor finding: + 默认 no-op 委托会被 `GetInvocationList()` 计入,导致 `GetListenerCount()` 在无监听器和单监听器场景分别返回 `1` 和 `2` +- 本地复核确认该问题仍成立: + - `Event` 当前字段初始化为 `_ => { }` + - `Event` 当前字段初始化为 `(_, _) => { }` + - 两个 `Trigger(...)` 实现本身已是 null-safe,因此无需依赖占位委托规避空引用 +- 实施最小修复: + - 移除两个事件字段的 no-op 初始委托,改为以 `null` 表示“无监听器” + - 保持 `Register` / `UnRegister` / `Trigger` 的公开 API 和调用方式不变 + - 在 `EventTests` 中新增单参数与双参数 `GetListenerCount()` 回归测试,覆盖初始值、注册后和注销后的计数语义 +- 过程说明: + - 这条不是 skill 设计遗漏;`gframework-pr-review` 的目标本来就包含 latest review body 和 outside-diff 信号 + - 上一轮是我在处理时漏看了这条 outside-diff item,且终端里展示的超长 JSON 输出被截断,未单独把 `Event.cs` 项再抽出来复核 +- 定向验证命令: + - `dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -nologo` + - 结果:`15 Warning(s)`,`0 Error(s)` + - `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~EventTests.EventT_GetListenerCount_Should_Exclude_Placeholder_Handler|FullyQualifiedName~EventTests.EventTTK_GetListenerCount_Should_Exclude_Placeholder_Handler" -m:1 -p:RestoreFallbackFolders="" -nologo` + - 结果:`2 Passed`,`0 Failed` +- 下一步建议: + - 若继续 PR #265 follow-up,只接受当前本地仍成立的剩余 outside-diff 或 unresolved review 项 + - 若没有新的有效 review 点,再恢复到 `MA0046` 主批次 + ## 2026-04-21 — RP-010 ### 阶段:PR #265 follow-up 收口(RP-010)