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)