From a926748def1660c62f9ff0e4c0104a8318c20a27 Mon Sep 17 00:00:00 2001
From: gewuyou <95328647+GeWuYou@users.noreply.github.com>
Date: Mon, 20 Apr 2026 08:56:47 +0800
Subject: [PATCH 1/7] =?UTF-8?q?docs(ai-plan):=20=E5=90=8C=E6=AD=A5CQRS?=
=?UTF-8?q?=E6=81=A2=E5=A4=8D=E7=82=B9=E5=A4=8D=E6=A0=B8=E7=BB=93=E6=9E=9C?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 更新 CQRS 重写跟踪中的恢复点与活跃事实,记录 PR #253 已关闭且剩余 thread 为 stale review\n- 调整 CQRS 重写 trace 的下一步,恢复 Phase 8 主线优先级
---
.../todos/cqrs-rewrite-migration-tracking.md | 18 +++++++++---------
.../traces/cqrs-rewrite-migration-trace.md | 19 +++++++++----------
2 files changed, 18 insertions(+), 19 deletions(-)
diff --git a/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md b/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md
index 4dda7fbc..c4c68a2f 100644
--- a/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md
+++ b/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md
@@ -7,11 +7,11 @@ CQRS 迁移与收敛。
## 当前恢复点
-- 恢复点编号:`CQRS-REWRITE-RP-044`
+- 恢复点编号:`CQRS-REWRITE-RP-045`
- 当前阶段:`Phase 8`
- 当前焦点:
- 当前功能历史已归档,active 跟踪仅保留 `Phase 8` 主线的恢复入口
- - 短期上先处理 `PR #253` 的 latest head review thread 复核,确认当前本地修正是否已在远端收敛
+ - 已完成 `PR #253` 的 latest head review thread 复核,确认远端剩余 open thread 属于未关闭的 stale review 噪音
- 中期上继续 `Phase 8` 主线:参考 `ai-libs/Mediator`,扩大 generator 覆盖、减少 dispatch/invoker 热路径反射,并继续收口 package / facade / 兼容层
## 当前状态摘要
@@ -25,10 +25,10 @@ CQRS 迁移与收敛。
## 当前活跃事实
- `Phase 8` 仍是当前主线,不再回退到 `Phase 7`
-- 最近一轮功能恢复点是 `RP-043`:
- - tracking 顶部阶段与恢复建议已对齐到 `Phase 8`
- - `$gframework-pr-review` 会在 open thread 中显式提醒“`Addressed in commit ...` 文案不等于线程已关闭”
-- 若当前分支已推送,应优先重新执行 `$gframework-pr-review`,确认 PR `#253` 的 latest head review threads 是否已收敛
+- `2026-04-20` 已重新执行 `$gframework-pr-review`:
+ - `PR #253` 当前状态为 `CLOSED`
+ - latest reviewed commit 仍显示 `1` 条 open thread,但其内容针对的是已过时的 `Phase 7` 恢复建议
+ - 当前 active tracking / trace 已统一到 `Phase 8`,因此该 thread 不再作为当前主线阻塞项
- 若 PR review 噪音已收敛,再回到以下主线优先级:
- generator 覆盖面继续扩大
- dispatch/invoker 反射占比继续下降
@@ -53,6 +53,6 @@ CQRS 迁移与收敛。
## 下一步
-1. 推送当前分支后重新执行 `$gframework-pr-review`,确认 `PR #253` 的 latest head review threads 是否已收敛
-2. 若 PR review 已收敛,回到 `Phase 8` 主线,优先选择一个收益明确的反射收敛点继续推进
-3. 若继续文档主线,优先再扫 `docs/zh-CN/api-reference` 与教程入口页,补齐仍过时的 CQRS API / 命名空间表述
+1. 回到 `Phase 8` 主线,优先选择一个收益明确的反射收敛点继续推进
+2. 若继续文档主线,优先再扫 `docs/zh-CN/api-reference` 与教程入口页,补齐仍过时的 CQRS API / 命名空间表述
+3. 若后续再出现新的 PR review 或 review thread 变化,再重新执行 `$gframework-pr-review` 作为独立验证步骤
diff --git a/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md b/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md
index dc02bae2..65de190d 100644
--- a/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md
+++ b/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md
@@ -1,14 +1,13 @@
# CQRS 重写迁移追踪
-## 2026-04-19
+## 2026-04-20
-### 阶段:active 入口归档收口(CQRS-REWRITE-RP-044)
+### 阶段:PR #253 latest head review thread 复核(CQRS-REWRITE-RP-045)
-- 已将截至 `RP-043` 的详细实现历史、验证记录与阶段性 trace 迁入主题内归档
-- active trace 现在只保留当前恢复点与下一步,避免默认 boot 入口继续读取 1400+ 行已完成历史
-- 当前功能主线保持不变:
- - 先复核 `PR #253` 的 latest head review threads 是否已收敛
- - 再继续 `Phase 8` 的 generator / dispatch / package 收口工作
+- 已重新执行 `$gframework-pr-review`,确认 `PR #253` 当前状态为 `CLOSED`
+- latest reviewed commit 仍显示 `1` 条 open thread,但评论指向的是 tracking 文件中已经修正的旧版 `Phase 7` 恢复建议
+- 复核当前 active tracking / trace 后确认:默认 boot 入口已经统一到 `Phase 8`,该 thread 属于未关闭的 stale review 噪音
+- 当前功能主线恢复为 `Phase 8` 的 generator / dispatch / package 收口工作
### Archive Context
@@ -19,6 +18,6 @@
### 当前下一步
-1. 推送当前分支后重新执行 `$gframework-pr-review`
-2. 以 latest head review thread 状态和本地文件事实为准,确认 `RP-042` / `RP-043` 修正是否真正收敛
-3. 若收敛完成,回到 `Phase 8` 主线,优先选一个明确的反射缩减点继续推进
+1. 回到 `Phase 8` 主线,优先选一个明确的反射缩减点继续推进
+2. 若继续文档主线,优先补齐 `docs/zh-CN/api-reference` 与教程入口页中仍过时的 CQRS API / 命名空间表述
+3. 若后续 review thread 或 PR 状态再次变化,再重新执行 `$gframework-pr-review` 复核远端信号
From 7cf0a75568510da5c445f7037127d2b0b7f33f12 Mon Sep 17 00:00:00 2001
From: gewuyou <95328647+GeWuYou@users.noreply.github.com>
Date: Mon, 20 Apr 2026 09:28:19 +0800
Subject: [PATCH 2/7] =?UTF-8?q?refactor(cqrs):=20=E6=94=B6=E6=95=9B?=
=?UTF-8?q?=E7=94=9F=E6=88=90=E6=B3=A8=E5=86=8C=E5=99=A8=E6=BF=80=E6=B4=BB?=
=?UTF-8?q?=E5=8F=8D=E5=B0=84=E8=B7=AF=E5=BE=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 优化 generated registry 激活流程,使用缓存工厂委托优先替代 ConstructorInfo.Invoke\n- 补充私有无参构造 registry 的回归测试,保持生成器产物兼容性\n- 更新 CQRS ai-plan 恢复点与验证记录,指向新的 Phase 8 下一步
---
.../Cqrs/CqrsHandlerRegistrarTests.cs | 55 +++++++++++++++++++
.../Internal/CqrsHandlerRegistrar.cs | 47 +++++++++++++++-
.../todos/cqrs-rewrite-migration-tracking.md | 18 ++++--
.../traces/cqrs-rewrite-migration-trace.md | 16 ++++--
4 files changed, 124 insertions(+), 12 deletions(-)
diff --git a/GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs b/GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs
index 3e92281d..349c80f7 100644
--- a/GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs
+++ b/GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs
@@ -140,6 +140,31 @@ internal sealed class CqrsHandlerRegistrarTests
Is.EqualTo([typeof(GeneratedRegistryNotificationHandler)]));
}
+ ///
+ /// 验证 generated registry 使用私有无参构造器时,运行时仍可激活它并完成处理器注册。
+ ///
+ [Test]
+ public void RegisterHandlers_Should_Activate_Generated_Registry_With_Private_Parameterless_Constructor()
+ {
+ var generatedAssembly = new Mock();
+ generatedAssembly
+ .SetupGet(static assembly => assembly.FullName)
+ .Returns("GFramework.Core.Tests.Cqrs.PrivateGeneratedRegistryAssembly, Version=1.0.0.0");
+ generatedAssembly
+ .Setup(static assembly => assembly.GetCustomAttributes(typeof(CqrsHandlerRegistryAttribute), false))
+ .Returns([new CqrsHandlerRegistryAttribute(typeof(PrivateConstructorNotificationHandlerRegistry))]);
+
+ var container = new MicrosoftDiContainer();
+ CqrsTestRuntime.RegisterHandlers(container, generatedAssembly.Object);
+ container.Freeze();
+
+ var handlers = container.GetAll>();
+
+ Assert.That(
+ handlers.Select(static handler => handler.GetType()),
+ Is.EqualTo([typeof(GeneratedRegistryNotificationHandler)]));
+ }
+
///
/// 验证当生成注册器元数据损坏时,运行时会记录告警并回退到反射扫描路径。
///
@@ -608,3 +633,33 @@ internal sealed class PartialGeneratedNotificationHandlerRegistry : ICqrsHandler
$"Registered CQRS handler {typeof(GeneratedRegistryNotificationHandler).FullName} as {typeof(INotificationHandler).FullName}.");
}
}
+
+///
+/// 模拟生成注册器使用私有无参构造器的场景,验证运行时仍可通过缓存工厂激活它。
+///
+internal sealed class PrivateConstructorNotificationHandlerRegistry : ICqrsHandlerRegistry
+{
+ ///
+ /// 初始化一个新的私有生成注册器实例。
+ ///
+ private PrivateConstructorNotificationHandlerRegistry()
+ {
+ }
+
+ ///
+ /// 将测试通知处理器注册到目标服务集合。
+ ///
+ /// 承载处理器映射的服务集合。
+ /// 用于记录注册诊断的日志器。
+ public void Register(IServiceCollection services, ILogger logger)
+ {
+ ArgumentNullException.ThrowIfNull(services);
+ ArgumentNullException.ThrowIfNull(logger);
+
+ services.AddTransient(
+ typeof(INotificationHandler),
+ typeof(GeneratedRegistryNotificationHandler));
+ logger.Debug(
+ $"Registered CQRS handler {typeof(GeneratedRegistryNotificationHandler).FullName} as {typeof(INotificationHandler).FullName}.");
+ }
+}
diff --git a/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs b/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs
index 031cb7e4..c9562b17 100644
--- a/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs
+++ b/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs
@@ -1,6 +1,7 @@
using GFramework.Core.Abstractions.Ioc;
using GFramework.Core.Abstractions.Logging;
using GFramework.Cqrs.Abstractions.Cqrs;
+using System.Reflection.Emit;
namespace GFramework.Cqrs.Internal;
@@ -323,7 +324,51 @@ internal static class CqrsHandlerRegistrar
: new RegistryActivationMetadata(
true,
false,
- () => (ICqrsHandlerRegistry)constructor.Invoke(null));
+ CreateRegistryFactory(registryType, constructor));
+ }
+
+ ///
+ /// 为生成注册器创建可复用的激活工厂,优先使用一次性编译的动态方法,
+ /// 避免后续每次命中缓存时仍走 的反射激活路径。
+ ///
+ /// 生成注册器类型。
+ /// 已解析的无参构造函数。
+ /// 可直接实例化注册器的工厂委托。
+ private static Func CreateRegistryFactory(
+ Type registryType,
+ ConstructorInfo constructor)
+ {
+ ArgumentNullException.ThrowIfNull(registryType);
+ ArgumentNullException.ThrowIfNull(constructor);
+
+ try
+ {
+ // 生成器产物通常是稳定的无参 registry;这里把构造反射收敛为一次性 IL 工厂,
+ // 这样同一 registry 类型在多个容器间复用缓存时不会重复付出 ConstructorInfo.Invoke 成本。
+ var dynamicMethod = new DynamicMethod(
+ $"Create_{registryType.Name}_CqrsHandlerRegistry",
+ typeof(ICqrsHandlerRegistry),
+ Type.EmptyTypes,
+ registryType.Module,
+ skipVisibility: true);
+ var il = dynamicMethod.GetILGenerator();
+ il.Emit(OpCodes.Newobj, constructor);
+
+ if (registryType.IsValueType)
+ {
+ il.Emit(OpCodes.Box, registryType);
+ }
+
+ il.Emit(OpCodes.Castclass, typeof(ICqrsHandlerRegistry));
+ il.Emit(OpCodes.Ret);
+
+ return (Func)dynamicMethod.CreateDelegate(typeof(Func));
+ }
+ catch
+ {
+ // 某些受限运行环境若不允许动态方法,仍保留原有的反射激活语义,避免阻塞 generated registry 路径。
+ return () => (ICqrsHandlerRegistry)constructor.Invoke(null);
+ }
}
///
diff --git a/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md b/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md
index c4c68a2f..7e4a4198 100644
--- a/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md
+++ b/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md
@@ -7,12 +7,13 @@ CQRS 迁移与收敛。
## 当前恢复点
-- 恢复点编号:`CQRS-REWRITE-RP-045`
+- 恢复点编号:`CQRS-REWRITE-RP-046`
- 当前阶段:`Phase 8`
- 当前焦点:
- 当前功能历史已归档,active 跟踪仅保留 `Phase 8` 主线的恢复入口
- - 已完成 `PR #253` 的 latest head review thread 复核,确认远端剩余 open thread 属于未关闭的 stale review 噪音
- - 中期上继续 `Phase 8` 主线:参考 `ai-libs/Mediator`,扩大 generator 覆盖、减少 dispatch/invoker 热路径反射,并继续收口 package / facade / 兼容层
+ - 已完成 generated registry 激活路径收敛:`CqrsHandlerRegistrar` 现优先复用缓存工厂委托,避免重复 `ConstructorInfo.Invoke`
+ - 已补充私有无参构造 generated registry 的回归测试,确保兼容现有生成器产物
+ - 中期上继续 `Phase 8` 主线:参考 `ai-libs/Mediator`,继续扩大 generator 覆盖,并选择下一个收益明确的 dispatch / invoker 反射收敛点
## 当前状态摘要
@@ -29,7 +30,11 @@ CQRS 迁移与收敛。
- `PR #253` 当前状态为 `CLOSED`
- latest reviewed commit 仍显示 `1` 条 open thread,但其内容针对的是已过时的 `Phase 7` 恢复建议
- 当前 active tracking / trace 已统一到 `Phase 8`,因此该 thread 不再作为当前主线阻塞项
-- 若 PR review 噪音已收敛,再回到以下主线优先级:
+- `2026-04-20` 已完成一轮冷启动反射收敛:
+ - generated registry 类型首次分析后,会缓存一个可复用的激活工厂,而不是在后续容器注册时重复走 `ConstructorInfo.Invoke`
+ - 若运行环境不允许动态方法,仍保留原有的反射激活回退,避免阻塞 generated registry 路径
+ - `GFramework.Cqrs.Tests` 已补充“私有无参构造 registry 仍可激活”的回归覆盖
+- 当前主线优先级:
- generator 覆盖面继续扩大
- dispatch/invoker 反射占比继续下降
- package / facade / 兼容层继续收口
@@ -50,9 +55,12 @@ CQRS 迁移与收敛。
- `RP-043` 之前的详细阶段记录、定向验证命令和阶段性决策均已移入主题内归档
- active 跟踪文件只保留当前恢复点、当前活跃事实、风险和下一步,避免 `boot` 在默认入口中重复扫描 1000+ 行历史 trace
+- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --no-restore -p:RestoreFallbackFolders= -m:1 -nodeReuse:false`
+ - 结果:通过
+ - 备注:`63/63` 测试通过;当前沙箱限制了 MSBuild named pipe,验证需在提权环境下运行
## 下一步
-1. 回到 `Phase 8` 主线,优先选择一个收益明确的反射收敛点继续推进
+1. 继续 `Phase 8` 主线,优先选择下一个收益明确的 dispatch / invoker 反射收敛点继续推进
2. 若继续文档主线,优先再扫 `docs/zh-CN/api-reference` 与教程入口页,补齐仍过时的 CQRS API / 命名空间表述
3. 若后续再出现新的 PR review 或 review thread 变化,再重新执行 `$gframework-pr-review` 作为独立验证步骤
diff --git a/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md b/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md
index 65de190d..9353cdd9 100644
--- a/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md
+++ b/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md
@@ -2,12 +2,16 @@
## 2026-04-20
-### 阶段:PR #253 latest head review thread 复核(CQRS-REWRITE-RP-045)
+### 阶段:generated registry 激活反射收敛(CQRS-REWRITE-RP-046)
-- 已重新执行 `$gframework-pr-review`,确认 `PR #253` 当前状态为 `CLOSED`
-- latest reviewed commit 仍显示 `1` 条 open thread,但评论指向的是 tracking 文件中已经修正的旧版 `Phase 7` 恢复建议
-- 复核当前 active tracking / trace 后确认:默认 boot 入口已经统一到 `Phase 8`,该 thread 属于未关闭的 stale review 噪音
-- 当前功能主线恢复为 `Phase 8` 的 generator / dispatch / package 收口工作
+- 已在 `CqrsHandlerRegistrar` 中将 generated registry 的无参构造激活改为类型级缓存工厂
+- 默认路径优先使用一次性动态方法直接创建 registry,避免后续每次命中缓存仍走 `ConstructorInfo.Invoke`
+- 若运行环境不允许动态方法,则保留原有反射激活回退,确保 generated registry 路径不因运行时限制失效
+- 已补充“私有无参构造 generated registry 仍可激活”的回归测试,覆盖现有生成器产物兼容性
+- 定向验证已通过:
+ - `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --no-restore -p:RestoreFallbackFolders= -m:1 -nodeReuse:false`
+ - `63/63` passed
+ - 当前沙箱限制 MSBuild named pipe,因此验证在提权环境下执行
### Archive Context
@@ -18,6 +22,6 @@
### 当前下一步
-1. 回到 `Phase 8` 主线,优先选一个明确的反射缩减点继续推进
+1. 回到 `Phase 8` 主线,优先选一个明确的 dispatch / invoker 反射缩减点继续推进
2. 若继续文档主线,优先补齐 `docs/zh-CN/api-reference` 与教程入口页中仍过时的 CQRS API / 命名空间表述
3. 若后续 review thread 或 PR 状态再次变化,再重新执行 `$gframework-pr-review` 复核远端信号
From 5f3964d4c089cd730b5712ff13be025b96348183 Mon Sep 17 00:00:00 2001
From: gewuyou <95328647+GeWuYou@users.noreply.github.com>
Date: Mon, 20 Apr 2026 10:17:03 +0800
Subject: [PATCH 3/7] =?UTF-8?q?refactor(cqrs):=20=E6=89=A9=E5=B1=95?=
=?UTF-8?q?=E6=8C=87=E9=92=88=E7=B1=BB=E5=9E=8B=E6=B3=A8=E5=86=8C=E7=94=9F?=
=?UTF-8?q?=E6=88=90=E8=A6=86=E7=9B=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 优化 CqrsHandlerRegistryGenerator 对 pointer 类型的 runtime type 递归重建与发射逻辑
- 修复 function pointer 签名默认直出导致隐藏类型漏回退的判定边界
- 补充 pointer precise registration 与 function pointer fallback 回归测试
- 更新 cqrs-rewrite 跟踪与 trace 到 RP-047
---
.../Cqrs/CqrsHandlerRegistryGenerator.cs | 53 ++++++-
.../Cqrs/CqrsHandlerRegistryGeneratorTests.cs | 143 +++++++++++++++++-
.../todos/cqrs-rewrite-migration-tracking.md | 13 +-
.../traces/cqrs-rewrite-migration-trace.md | 12 ++
4 files changed, 208 insertions(+), 13 deletions(-)
diff --git a/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs b/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs
index 6421e0ca..7888f613 100644
--- a/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs
+++ b/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs
@@ -412,6 +412,13 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
return true;
}
+ if (type is IPointerTypeSymbol pointerType &&
+ TryCreateRuntimeTypeReference(compilation, pointerType.PointedAtType, out var pointedAtTypeReference))
+ {
+ runtimeTypeReference = RuntimeTypeReferenceSpec.FromPointer(pointedAtTypeReference!);
+ return true;
+ }
+
if (type is INamedTypeSymbol genericNamedType &&
genericNamedType.IsGenericType &&
!genericNamedType.IsUnboundGenericType &&
@@ -520,6 +527,17 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
return true;
case IPointerTypeSymbol pointerType:
return CanReferenceFromGeneratedRegistry(compilation, pointerType.PointedAtType);
+ case IFunctionPointerTypeSymbol functionPointerType:
+ if (!CanReferenceFromGeneratedRegistry(compilation, functionPointerType.Signature.ReturnType))
+ return false;
+
+ foreach (var parameter in functionPointerType.Signature.Parameters)
+ {
+ if (!CanReferenceFromGeneratedRegistry(compilation, parameter.Type))
+ return false;
+ }
+
+ return true;
case ITypeParameterSymbol:
return false;
default:
@@ -975,6 +993,18 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
: $"{elementExpression}.MakeArrayType({runtimeTypeReference.ArrayRank})";
}
+ if (runtimeTypeReference.PointerElementTypeReference is not null)
+ {
+ var pointedAtExpression = AppendRuntimeTypeReferenceResolution(
+ builder,
+ runtimeTypeReference.PointerElementTypeReference,
+ $"{variableBaseName}PointedAt",
+ reflectedArgumentNames,
+ indent);
+
+ return $"{pointedAtExpression}.MakePointerType()";
+ }
+
if (runtimeTypeReference.GenericTypeDefinitionReference is not null)
{
var genericTypeDefinitionExpression = AppendRuntimeTypeReferenceResolution(
@@ -1091,6 +1121,12 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
return true;
}
+ if (runtimeTypeReference.PointerElementTypeReference is not null &&
+ ContainsExternalAssemblyTypeLookup(runtimeTypeReference.PointerElementTypeReference))
+ {
+ return true;
+ }
+
if (runtimeTypeReference.GenericTypeDefinitionReference is not null &&
ContainsExternalAssemblyTypeLookup(runtimeTypeReference.GenericTypeDefinitionReference))
{
@@ -1129,18 +1165,19 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
string? ReflectionAssemblyName,
RuntimeTypeReferenceSpec? ArrayElementTypeReference,
int ArrayRank,
+ RuntimeTypeReferenceSpec? PointerElementTypeReference,
RuntimeTypeReferenceSpec? GenericTypeDefinitionReference,
ImmutableArray GenericTypeArguments)
{
public static RuntimeTypeReferenceSpec FromDirectReference(string typeDisplayName)
{
- return new RuntimeTypeReferenceSpec(typeDisplayName, null, null, null, 0, null,
+ return new RuntimeTypeReferenceSpec(typeDisplayName, null, null, null, 0, null, null,
ImmutableArray.Empty);
}
public static RuntimeTypeReferenceSpec FromReflectionLookup(string reflectionTypeMetadataName)
{
- return new RuntimeTypeReferenceSpec(null, reflectionTypeMetadataName, null, null, 0, null,
+ return new RuntimeTypeReferenceSpec(null, reflectionTypeMetadataName, null, null, 0, null, null,
ImmutableArray.Empty);
}
@@ -1149,13 +1186,19 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
string reflectionTypeMetadataName)
{
return new RuntimeTypeReferenceSpec(null, reflectionTypeMetadataName, reflectionAssemblyName, null, 0,
- null,
+ null, null,
ImmutableArray.Empty);
}
public static RuntimeTypeReferenceSpec FromArray(RuntimeTypeReferenceSpec elementTypeReference, int arrayRank)
{
- return new RuntimeTypeReferenceSpec(null, null, null, elementTypeReference, arrayRank, null,
+ return new RuntimeTypeReferenceSpec(null, null, null, elementTypeReference, arrayRank, null, null,
+ ImmutableArray.Empty);
+ }
+
+ public static RuntimeTypeReferenceSpec FromPointer(RuntimeTypeReferenceSpec pointedAtTypeReference)
+ {
+ return new RuntimeTypeReferenceSpec(null, null, null, null, 0, pointedAtTypeReference, null,
ImmutableArray.Empty);
}
@@ -1163,7 +1206,7 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
RuntimeTypeReferenceSpec genericTypeDefinitionReference,
ImmutableArray genericTypeArguments)
{
- return new RuntimeTypeReferenceSpec(null, null, null, null, 0, genericTypeDefinitionReference,
+ return new RuntimeTypeReferenceSpec(null, null, null, null, 0, null, genericTypeDefinitionReference,
genericTypeArguments);
}
}
diff --git a/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs b/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs
index 6db73364..c0b4508f 100644
--- a/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs
+++ b/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs
@@ -164,6 +164,45 @@ public class CqrsHandlerRegistryGeneratorTests
""";
+ private const string HiddenPointerResponseExpected = """
+ //
+ #nullable enable
+
+ [assembly: global::GFramework.Cqrs.CqrsHandlerRegistryAttribute(typeof(global::GFramework.Generated.Cqrs.__GFrameworkGeneratedCqrsHandlerRegistry))]
+
+ namespace GFramework.Generated.Cqrs;
+
+ internal sealed class __GFrameworkGeneratedCqrsHandlerRegistry : global::GFramework.Cqrs.ICqrsHandlerRegistry
+ {
+ public void Register(global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, global::GFramework.Core.Abstractions.Logging.ILogger logger)
+ {
+ if (services is null)
+ throw new global::System.ArgumentNullException(nameof(services));
+ if (logger is null)
+ throw new global::System.ArgumentNullException(nameof(logger));
+
+ var registryAssembly = typeof(global::GFramework.Generated.Cqrs.__GFrameworkGeneratedCqrsHandlerRegistry).Assembly;
+
+ var implementationType0 = typeof(global::TestApp.Container.HiddenHandler);
+ if (implementationType0 is not null)
+ {
+ var serviceType0_0Argument0 = registryAssembly.GetType("TestApp.Container+HiddenRequest", throwOnError: false, ignoreCase: false);
+ var serviceType0_0Argument1PointedAt = registryAssembly.GetType("TestApp.Container+HiddenResponse", throwOnError: false, ignoreCase: false);
+ if (serviceType0_0Argument0 is not null && serviceType0_0Argument1PointedAt is not null)
+ {
+ var serviceType0_0 = typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<,>).MakeGenericType(serviceType0_0Argument0, serviceType0_0Argument1PointedAt.MakePointerType());
+ global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddTransient(
+ services,
+ serviceType0_0,
+ implementationType0);
+ logger.Debug("Registered CQRS handler TestApp.Container.HiddenHandler as GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler.");
+ }
+ }
+ }
+ }
+
+ """;
+
private const string MixedDirectAndPreciseRegistrationsExpected = """
//
#nullable enable
@@ -745,6 +784,98 @@ public class CqrsHandlerRegistryGeneratorTests
("CqrsHandlerRegistry.g.cs", HiddenGenericEnvelopeResponseExpected));
}
+ ///
+ /// 验证精确重建路径会递归覆盖隐藏指针元素类型,
+ /// 使“隐藏 pointed-at 类型 + unsafe 指针响应”的 handler 也能直接生成 closed service type。
+ ///
+ [Test]
+ public async Task Generates_Precise_Service_Type_For_Hidden_Pointer_Response()
+ {
+ const string source = """
+ using System;
+
+ namespace Microsoft.Extensions.DependencyInjection
+ {
+ public interface IServiceCollection { }
+
+ public static class ServiceCollectionServiceExtensions
+ {
+ public static void AddTransient(IServiceCollection services, Type serviceType, Type implementationType) { }
+ }
+ }
+
+ namespace GFramework.Core.Abstractions.Logging
+ {
+ public interface ILogger
+ {
+ void Debug(string msg);
+ }
+ }
+
+ namespace GFramework.Cqrs.Abstractions.Cqrs
+ {
+ public interface IRequest { }
+ public interface INotification { }
+ public interface IStreamRequest { }
+
+ public interface IRequestHandler where TRequest : IRequest { }
+ public interface INotificationHandler where TNotification : INotification { }
+ public interface IStreamRequestHandler where TRequest : IStreamRequest { }
+ }
+
+ namespace GFramework.Cqrs
+ {
+ public interface ICqrsHandlerRegistry
+ {
+ void Register(Microsoft.Extensions.DependencyInjection.IServiceCollection services, GFramework.Core.Abstractions.Logging.ILogger logger);
+ }
+
+ [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
+ public sealed class CqrsHandlerRegistryAttribute : Attribute
+ {
+ public CqrsHandlerRegistryAttribute(Type registryType) { }
+ }
+ }
+
+ namespace TestApp
+ {
+ using GFramework.Cqrs.Abstractions.Cqrs;
+
+ public sealed class Container
+ {
+ private unsafe struct HiddenResponse
+ {
+ }
+
+ private unsafe sealed record HiddenRequest() : IRequest;
+
+ public unsafe sealed class HiddenHandler : IRequestHandler
+ {
+ }
+ }
+ }
+ """;
+
+ var execution = ExecuteGenerator(
+ source,
+ allowUnsafe: true);
+ var generatedCompilationErrors = execution.GeneratedCompilationDiagnostics
+ .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
+ .ToArray();
+ var generatorErrors = execution.GeneratorDiagnostics
+ .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
+ .ToArray();
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(generatedCompilationErrors, Is.Empty);
+ Assert.That(generatorErrors, Is.Empty);
+ Assert.That(execution.GeneratedSources, Has.Length.EqualTo(1));
+ Assert.That(execution.GeneratedSources[0].filename, Is.EqualTo("CqrsHandlerRegistry.g.cs"));
+ Assert.That(execution.GeneratedSources[0].content, Is.EqualTo(HiddenPointerResponseExpected));
+ });
+ }
+
///
/// 验证同一个 implementation 同时包含可直接注册接口与需精确重建接口时,
/// 生成器会保留两类注册,并继续按 handler interface 名称稳定排序。
@@ -1232,9 +1363,9 @@ public class CqrsHandlerRegistryGeneratorTests
{
}
- private unsafe sealed record HiddenRequest() : IRequest;
+ private unsafe sealed record HiddenRequest() : IRequest>;
- public unsafe sealed class HiddenHandler : IRequestHandler
+ public unsafe sealed class HiddenHandler : IRequestHandler>
{
}
}
@@ -1341,15 +1472,15 @@ public class CqrsHandlerRegistryGeneratorTests
{
}
- private unsafe sealed record AlphaRequest() : IRequest;
+ private unsafe sealed record AlphaRequest() : IRequest>;
- private unsafe sealed record BetaRequest() : IRequest;
+ private unsafe sealed record BetaRequest() : IRequest>;
- public unsafe sealed class BetaHandler : IRequestHandler
+ public unsafe sealed class BetaHandler : IRequestHandler>
{
}
- public unsafe sealed class AlphaHandler : IRequestHandler
+ public unsafe sealed class AlphaHandler : IRequestHandler>
{
}
}
diff --git a/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md b/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md
index 7e4a4198..a0843460 100644
--- a/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md
+++ b/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md
@@ -7,12 +7,14 @@ CQRS 迁移与收敛。
## 当前恢复点
-- 恢复点编号:`CQRS-REWRITE-RP-046`
+- 恢复点编号:`CQRS-REWRITE-RP-047`
- 当前阶段:`Phase 8`
- 当前焦点:
- 当前功能历史已归档,active 跟踪仅保留 `Phase 8` 主线的恢复入口
- 已完成 generated registry 激活路径收敛:`CqrsHandlerRegistrar` 现优先复用缓存工厂委托,避免重复 `ConstructorInfo.Invoke`
- 已补充私有无参构造 generated registry 的回归测试,确保兼容现有生成器产物
+ - 已补充 pointer 响应类型的 precise runtime type 生成,避免这类 handler 再退回程序集级 reflection fallback
+ - 已收紧 function pointer 签名的可直接生成判定,仅在其返回值与参数类型都可安全引用时才走静态注册路径
- 中期上继续 `Phase 8` 主线:参考 `ai-libs/Mediator`,继续扩大 generator 覆盖,并选择下一个收益明确的 dispatch / invoker 反射收敛点
## 当前状态摘要
@@ -34,6 +36,10 @@ CQRS 迁移与收敛。
- generated registry 类型首次分析后,会缓存一个可复用的激活工厂,而不是在后续容器注册时重复走 `ConstructorInfo.Invoke`
- 若运行环境不允许动态方法,仍保留原有的反射激活回退,避免阻塞 generated registry 路径
- `GFramework.Cqrs.Tests` 已补充“私有无参构造 registry 仍可激活”的回归覆盖
+- `2026-04-20` 已完成一轮 generator 覆盖面扩展:
+ - `CqrsHandlerRegistryGenerator` 现可为 pointer 类型递归重建 runtime type,并通过 `MakePointerType()` 生成精确 service type
+ - function pointer 签名不再默认视为“可直接引用”;只有当返回值与每个参数类型都可从 generated registry 安全引用时,才允许直接生成
+ - 含隐藏类型的 function pointer handler 仍会保留原有 fallback / 诊断路径,避免此次覆盖扩展误伤已有回退边界
- 当前主线优先级:
- generator 覆盖面继续扩大
- dispatch/invoker 反射占比继续下降
@@ -58,9 +64,12 @@ CQRS 迁移与收敛。
- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --no-restore -p:RestoreFallbackFolders= -m:1 -nodeReuse:false`
- 结果:通过
- 备注:`63/63` 测试通过;当前沙箱限制了 MSBuild named pipe,验证需在提权环境下运行
+- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore -p:RestoreFallbackFolders= -m:1 -nodeReuse:false --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests"`
+ - 结果:通过
+ - 备注:`14/14` 测试通过;本轮覆盖 pointer precise registration 与 function pointer fallback 边界
## 下一步
-1. 继续 `Phase 8` 主线,优先选择下一个收益明确的 dispatch / invoker 反射收敛点继续推进
+1. 继续 `Phase 8` 主线,优先再找一个收益明确的 generator 覆盖缺口或 dispatch / invoker 反射收敛点继续推进
2. 若继续文档主线,优先再扫 `docs/zh-CN/api-reference` 与教程入口页,补齐仍过时的 CQRS API / 命名空间表述
3. 若后续再出现新的 PR review 或 review thread 变化,再重新执行 `$gframework-pr-review` 作为独立验证步骤
diff --git a/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md b/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md
index 9353cdd9..98023113 100644
--- a/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md
+++ b/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md
@@ -2,6 +2,18 @@
## 2026-04-20
+### 阶段:pointer precise runtime type 覆盖扩展(CQRS-REWRITE-RP-047)
+
+- 已在 `CqrsHandlerRegistryGenerator` 中补充 pointer 类型的 runtime type 递归建模与源码发射,precise registration 现可通过 `MakePointerType()` 还原隐藏 pointer 响应类型
+- 已同步收紧 function pointer 签名的可直接生成判定,只有当签名中的返回值与参数类型均可从 generated registry 安全引用时才走静态注册
+- 已保留含隐藏类型 function pointer handler 的 fallback / 诊断回归覆盖,确保 pointer 支持扩展不会误删原有程序集级 fallback 契约边界
+- 定向验证与 `CqrsHandlerRegistryGeneratorTests` 全组验证均已通过:
+ - `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore -p:RestoreFallbackFolders= -m:1 -nodeReuse:false --filter "FullyQualifiedName~Generates_Precise_Service_Type_For_Hidden_Pointer_Response|FullyQualifiedName~Reports_Diagnostic_And_Skips_Registry_When_Fallback_Metadata_Is_Required_But_Runtime_Contract_Lacks_Fallback_Attribute|FullyQualifiedName~Emits_Assembly_Level_Fallback_Metadata_When_Fallback_Is_Required_And_Runtime_Contract_Is_Available"`
+ - `3/3` passed
+ - `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore -p:RestoreFallbackFolders= -m:1 -nodeReuse:false --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests"`
+ - `14/14` passed
+ - 当前沙箱限制 MSBuild named pipe,因此验证在提权环境下执行
+
### 阶段:generated registry 激活反射收敛(CQRS-REWRITE-RP-046)
- 已在 `CqrsHandlerRegistrar` 中将 generated registry 的无参构造激活改为类型级缓存工厂
From 110666d06b0e4ff5ab5690e7df6bda0b675962b1 Mon Sep 17 00:00:00 2001
From: gewuyou <95328647+GeWuYou@users.noreply.github.com>
Date: Mon, 20 Apr 2026 10:34:24 +0800
Subject: [PATCH 4/7] =?UTF-8?q?refactor(cqrs):=20=E7=BC=93=E5=AD=98?=
=?UTF-8?q?=E5=A4=84=E7=90=86=E5=99=A8=E6=8E=A5=E5=8F=A3=E5=8F=8D=E5=B0=84?=
=?UTF-8?q?=E5=85=83=E6=95=B0=E6=8D=AE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 优化 CqrsHandlerRegistrar 复用 supported handler interface 缓存
- 补充 registrar 静态缓存隔离与接口缓存复用回归测试
- 更新 cqrs-rewrite 跟踪与 trace 到 RP-048
---
.../Cqrs/CqrsHandlerRegistrarTests.cs | 119 ++++++++++++++++++
.../Internal/CqrsHandlerRegistrar.cs | 31 ++++-
.../todos/cqrs-rewrite-migration-tracking.md | 10 +-
.../traces/cqrs-rewrite-migration-trace.md | 10 ++
4 files changed, 163 insertions(+), 7 deletions(-)
diff --git a/GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs b/GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs
index 349c80f7..878ba484 100644
--- a/GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs
+++ b/GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs
@@ -32,6 +32,7 @@ internal sealed class CqrsHandlerRegistrarTests
_container.Freeze();
_context = new ArchitectureContext(_container);
+ ClearRegistrarCaches();
}
///
@@ -43,6 +44,7 @@ internal sealed class CqrsHandlerRegistrarTests
_context = null;
_container = null;
DeterministicNotificationHandlerState.Reset();
+ ClearRegistrarCaches();
}
///
@@ -435,6 +437,123 @@ internal sealed class CqrsHandlerRegistrarTests
partiallyLoadableAssembly.Verify(static assembly => assembly.GetTypes(), Times.Once);
}
+
+ ///
+ /// 验证同一 handler 类型跨容器重复注册时,会复用已筛选的 supported handler interface 列表,
+ /// 而不是为每个容器重新执行接口反射分析。
+ ///
+ [Test]
+ public void RegisterHandlers_Should_Cache_Supported_Handler_Interfaces_Across_Containers()
+ {
+ var supportedHandlerInterfacesCache = GetRegistrarCacheField("SupportedHandlerInterfacesCache");
+ var firstHandlerType = typeof(AlphaDeterministicNotificationHandler);
+ var secondHandlerType = typeof(ZetaDeterministicNotificationHandler);
+ var handlerAssembly = new Mock();
+ handlerAssembly
+ .SetupGet(static assembly => assembly.FullName)
+ .Returns("GFramework.Core.Tests.Cqrs.CachedHandlerInterfacesAssembly, Version=1.0.0.0");
+ handlerAssembly
+ .Setup(static assembly => assembly.GetTypes())
+ .Returns([firstHandlerType, secondHandlerType]);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(GetSingleKeyCacheValue(supportedHandlerInterfacesCache, firstHandlerType), Is.Null);
+ Assert.That(GetSingleKeyCacheValue(supportedHandlerInterfacesCache, secondHandlerType), Is.Null);
+ });
+
+ var firstContainer = new MicrosoftDiContainer();
+ var secondContainer = new MicrosoftDiContainer();
+
+ CqrsTestRuntime.RegisterHandlers(firstContainer, handlerAssembly.Object);
+ var firstHandlerInterfaces =
+ GetSingleKeyCacheValue(supportedHandlerInterfacesCache, firstHandlerType);
+ var secondHandlerInterfaces =
+ GetSingleKeyCacheValue(supportedHandlerInterfacesCache, secondHandlerType);
+
+ CqrsTestRuntime.RegisterHandlers(secondContainer, handlerAssembly.Object);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(firstHandlerInterfaces, Is.Not.Null);
+ Assert.That(secondHandlerInterfaces, Is.Not.Null);
+ Assert.That(
+ GetSingleKeyCacheValue(supportedHandlerInterfacesCache, firstHandlerType),
+ Is.SameAs(firstHandlerInterfaces));
+ Assert.That(
+ GetSingleKeyCacheValue(supportedHandlerInterfacesCache, secondHandlerType),
+ Is.SameAs(secondHandlerInterfaces));
+ });
+
+ handlerAssembly.Verify(static assembly => assembly.GetTypes(), Times.Once);
+ }
+
+ ///
+ /// 清空本测试依赖的 registrar 静态缓存,避免跨用例共享进程级状态导致断言漂移。
+ ///
+ private static void ClearRegistrarCaches()
+ {
+ ClearCache(GetRegistrarCacheField("AssemblyMetadataCache"));
+ ClearCache(GetRegistrarCacheField("RegistryActivationMetadataCache"));
+ ClearCache(GetRegistrarCacheField("LoadableTypesCache"));
+ ClearCache(GetRegistrarCacheField("SupportedHandlerInterfacesCache"));
+ }
+
+ ///
+ /// 通过反射读取 registrar 的静态缓存对象。
+ ///
+ private static object GetRegistrarCacheField(string fieldName)
+ {
+ var registrarType = GetRegistrarType();
+ var field = registrarType.GetField(
+ fieldName,
+ BindingFlags.NonPublic | BindingFlags.Static);
+
+ Assert.That(field, Is.Not.Null, $"Missing registrar cache field {fieldName}.");
+
+ return field!.GetValue(null)
+ ?? throw new InvalidOperationException(
+ $"Registrar cache field {fieldName} returned null.");
+ }
+
+ ///
+ /// 清空指定缓存对象。
+ ///
+ private static void ClearCache(object cache)
+ {
+ _ = InvokeInstanceMethod(cache, "Clear");
+ }
+
+ ///
+ /// 读取单键缓存中当前保存的对象。
+ ///
+ private static object? GetSingleKeyCacheValue(object cache, Type key)
+ {
+ return InvokeInstanceMethod(cache, "GetValueOrDefaultForTesting", key);
+ }
+
+ ///
+ /// 调用缓存对象上的实例方法。
+ ///
+ private static object? InvokeInstanceMethod(object target, string methodName, params object[] arguments)
+ {
+ var method = target.GetType().GetMethod(
+ methodName,
+ BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
+
+ Assert.That(method, Is.Not.Null, $"Missing cache method {target.GetType().FullName}.{methodName}.");
+
+ return method!.Invoke(target, arguments);
+ }
+
+ ///
+ /// 获取 CQRS handler registrar 运行时类型。
+ ///
+ private static Type GetRegistrarType()
+ {
+ return typeof(CqrsReflectionFallbackAttribute).Assembly
+ .GetType("GFramework.Cqrs.Internal.CqrsHandlerRegistrar", throwOnError: true)!;
+ }
}
///
diff --git a/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs b/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs
index c9562b17..ce380d48 100644
--- a/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs
+++ b/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs
@@ -26,6 +26,11 @@ internal static class CqrsHandlerRegistrar
private static readonly WeakKeyCache> LoadableTypesCache =
new();
+ // 卸载安全的进程级缓存:同一 handler 类型跨容器重复注册时,
+ // 复用已筛选且排序好的 supported handler interface 列表,避免重复执行 GetInterfaces()。
+ private static readonly WeakKeyCache> SupportedHandlerInterfacesCache =
+ new();
+
///
/// 扫描指定程序集并注册所有 CQRS 请求/通知/流式处理器。
///
@@ -163,11 +168,7 @@ internal static class CqrsHandlerRegistrar
foreach (var implementationType in GetCandidateHandlerTypes(assembly, logger, reflectionFallbackMetadata)
.Where(IsConcreteHandlerType))
{
- var handlerInterfaces = implementationType
- .GetInterfaces()
- .Where(IsSupportedHandlerInterface)
- .OrderBy(GetTypeSortKey, StringComparer.Ordinal)
- .ToList();
+ var handlerInterfaces = GetSupportedHandlerInterfaces(implementationType);
if (handlerInterfaces.Count == 0)
continue;
@@ -184,12 +185,30 @@ internal static class CqrsHandlerRegistrar
// Request/notification handlers receive context injection before every dispatch.
// Transient registration avoids sharing mutable Context across concurrent requests.
services.AddTransient(handlerInterface, implementationType);
- logger.Debug(
+ logger.Debug(
$"Registered CQRS handler {implementationType.FullName} as {handlerInterface.FullName}.");
}
}
}
+ ///
+ /// 获取指定实现类型上所有受支持的 CQRS handler 接口,并缓存筛选与排序结果。
+ ///
+ /// 要分析的处理器实现类型。
+ /// 当前实现类型声明的受支持 handler 接口列表。
+ private static IReadOnlyList GetSupportedHandlerInterfaces(Type implementationType)
+ {
+ ArgumentNullException.ThrowIfNull(implementationType);
+
+ return SupportedHandlerInterfacesCache.GetOrAdd(
+ implementationType,
+ static key => key
+ .GetInterfaces()
+ .Where(IsSupportedHandlerInterface)
+ .OrderBy(GetTypeSortKey, StringComparer.Ordinal)
+ .ToArray());
+ }
+
///
/// 根据生成器提供的 fallback 清单或整程序集扫描结果,获取本轮要注册的候选处理器类型。
///
diff --git a/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md b/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md
index a0843460..8b85d05f 100644
--- a/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md
+++ b/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md
@@ -7,7 +7,7 @@ CQRS 迁移与收敛。
## 当前恢复点
-- 恢复点编号:`CQRS-REWRITE-RP-047`
+- 恢复点编号:`CQRS-REWRITE-RP-048`
- 当前阶段:`Phase 8`
- 当前焦点:
- 当前功能历史已归档,active 跟踪仅保留 `Phase 8` 主线的恢复入口
@@ -15,6 +15,7 @@ CQRS 迁移与收敛。
- 已补充私有无参构造 generated registry 的回归测试,确保兼容现有生成器产物
- 已补充 pointer 响应类型的 precise runtime type 生成,避免这类 handler 再退回程序集级 reflection fallback
- 已收紧 function pointer 签名的可直接生成判定,仅在其返回值与参数类型都可安全引用时才走静态注册路径
+ - 已为 registrar 的 reflection 注册路径补充 handler-interface 元数据缓存,减少跨容器重复注册时的 `GetInterfaces()` 反射
- 中期上继续 `Phase 8` 主线:参考 `ai-libs/Mediator`,继续扩大 generator 覆盖,并选择下一个收益明确的 dispatch / invoker 反射收敛点
## 当前状态摘要
@@ -40,6 +41,10 @@ CQRS 迁移与收敛。
- `CqrsHandlerRegistryGenerator` 现可为 pointer 类型递归重建 runtime type,并通过 `MakePointerType()` 生成精确 service type
- function pointer 签名不再默认视为“可直接引用”;只有当返回值与每个参数类型都可从 generated registry 安全引用时,才允许直接生成
- 含隐藏类型的 function pointer handler 仍会保留原有 fallback / 诊断路径,避免此次覆盖扩展误伤已有回退边界
+- `2026-04-20` 已完成一轮 registrar reflection 路径收敛:
+ - `CqrsHandlerRegistrar` 现会按 `Type` 弱键缓存已筛选且排序好的 supported handler interface 列表
+ - 同一 handler 类型跨容器重复注册时,不再重复执行 `GetInterfaces()` 与支持接口筛选
+ - `GFramework.Cqrs.Tests` 已补充 registrar 静态缓存隔离与 supported interface 缓存复用回归
- 当前主线优先级:
- generator 覆盖面继续扩大
- dispatch/invoker 反射占比继续下降
@@ -67,6 +72,9 @@ CQRS 迁移与收敛。
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore -p:RestoreFallbackFolders= -m:1 -nodeReuse:false --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests"`
- 结果:通过
- 备注:`14/14` 测试通过;本轮覆盖 pointer precise registration 与 function pointer fallback 边界
+- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --no-restore -p:RestoreFallbackFolders= -m:1 -nodeReuse:false --filter "FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsHandlerRegistrarTests"`
+ - 结果:通过
+ - 备注:`10/10` 测试通过;本轮覆盖 registrar 的 supported handler interface 缓存
## 下一步
diff --git a/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md b/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md
index 98023113..6ad5b62a 100644
--- a/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md
+++ b/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md
@@ -2,6 +2,16 @@
## 2026-04-20
+### 阶段:registrar handler-interface 反射缓存(CQRS-REWRITE-RP-048)
+
+- 已在 `CqrsHandlerRegistrar` 中新增按 `Type` 弱键缓存的 supported handler interface 元数据,reflection 注册路径现会复用已筛选且排序好的接口列表
+- 同一 handler 类型跨容器重复注册时,不再重复执行 `GetInterfaces()` 与支持接口筛选;缓存仍保持卸载安全,不会长期钉住 collectible 类型
+- `GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs` 已补充 registrar 静态缓存清理与 supported interface 缓存复用回归
+- 定向验证已通过:
+ - `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --no-restore -p:RestoreFallbackFolders= -m:1 -nodeReuse:false --filter "FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsHandlerRegistrarTests"`
+ - `10/10` passed
+ - 当前沙箱限制 MSBuild named pipe,因此验证在提权环境下执行
+
### 阶段:pointer precise runtime type 覆盖扩展(CQRS-REWRITE-RP-047)
- 已在 `CqrsHandlerRegistryGenerator` 中补充 pointer 类型的 runtime type 递归建模与源码发射,precise registration 现可通过 `MakePointerType()` 还原隐藏 pointer 响应类型
From db6524931567e08fb2a0c5798f5b30fae2efdcd0 Mon Sep 17 00:00:00 2001
From: gewuyou <95328647+GeWuYou@users.noreply.github.com>
Date: Mon, 20 Apr 2026 10:37:36 +0800
Subject: [PATCH 5/7] =?UTF-8?q?refactor(cqrs):=20=E6=94=B6=E6=95=9B?=
=?UTF-8?q?=E5=A4=84=E7=90=86=E5=99=A8=E9=87=8D=E5=A4=8D=E6=98=A0=E5=B0=84?=
=?UTF-8?q?=E5=88=A4=E5=AE=9A?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 优化 CqrsHandlerRegistrar 使用本地映射索引替代重复线性扫描
- 补充 重复 handler 类型输入仍只注册一份映射的回归测试
- 更新 cqrs-rewrite 跟踪与 trace 到 RP-049
---
.../Cqrs/CqrsHandlerRegistrarTests.cs | 27 ++++++++++++++
.../Internal/CqrsHandlerRegistrar.cs | 35 ++++++++++---------
.../todos/cqrs-rewrite-migration-tracking.md | 9 +++--
.../traces/cqrs-rewrite-migration-trace.md | 10 ++++++
4 files changed, 63 insertions(+), 18 deletions(-)
diff --git a/GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs b/GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs
index 878ba484..8bcdeff3 100644
--- a/GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs
+++ b/GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs
@@ -488,6 +488,33 @@ internal sealed class CqrsHandlerRegistrarTests
handlerAssembly.Verify(static assembly => assembly.GetTypes(), Times.Once);
}
+ ///
+ /// 验证当程序集枚举结果包含重复 handler 类型时,registrar 仍只会写入一份 handler 映射。
+ ///
+ [Test]
+ public void RegisterHandlers_Should_Skip_Duplicate_Handler_Mappings_When_Assembly_Returns_Duplicate_Types()
+ {
+ var handlerType = typeof(AlphaDeterministicNotificationHandler);
+ var handlerAssembly = new Mock();
+ handlerAssembly
+ .SetupGet(static assembly => assembly.FullName)
+ .Returns("GFramework.Core.Tests.Cqrs.DuplicateHandlerMappingsAssembly, Version=1.0.0.0");
+ handlerAssembly
+ .Setup(static assembly => assembly.GetTypes())
+ .Returns([handlerType, handlerType]);
+
+ var container = new MicrosoftDiContainer();
+ CqrsTestRuntime.RegisterHandlers(container, handlerAssembly.Object);
+
+ var registrations = container.GetServicesUnsafe
+ .Where(static descriptor =>
+ descriptor.ServiceType == typeof(INotificationHandler) &&
+ descriptor.ImplementationType == typeof(AlphaDeterministicNotificationHandler))
+ .ToArray();
+
+ Assert.That(registrations, Has.Length.EqualTo(1));
+ }
+
///
/// 清空本测试依赖的 registrar 静态缓存,避免跨用例共享进程级状态导致断言漂移。
///
diff --git a/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs b/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs
index ce380d48..c6fd3909 100644
--- a/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs
+++ b/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs
@@ -165,6 +165,7 @@ internal static class CqrsHandlerRegistrar
ILogger logger,
ReflectionFallbackMetadata? reflectionFallbackMetadata)
{
+ var registeredMappings = CreateRegisteredHandlerMappings(services);
foreach (var implementationType in GetCandidateHandlerTypes(assembly, logger, reflectionFallbackMetadata)
.Where(IsConcreteHandlerType))
{
@@ -175,7 +176,7 @@ internal static class CqrsHandlerRegistrar
foreach (var handlerInterface in handlerInterfaces)
{
- if (IsHandlerMappingAlreadyRegistered(services, handlerInterface, implementationType))
+ if (!registeredMappings.Add(new HandlerMapping(handlerInterface, implementationType)))
{
logger.Debug(
$"Skipping duplicate CQRS handler {implementationType.FullName} as {handlerInterface.FullName}.");
@@ -209,6 +210,21 @@ internal static class CqrsHandlerRegistrar
.ToArray());
}
+ ///
+ /// 根据当前服务集合创建已注册 handler 映射的快速索引,避免 reflection fallback 路径重复线性扫描服务描述符。
+ ///
+ /// 当前容器的服务描述符集合。
+ /// 已存在的 handler 映射集合。
+ private static HashSet CreateRegisteredHandlerMappings(IServiceCollection services)
+ {
+ ArgumentNullException.ThrowIfNull(services);
+
+ return services
+ .Where(static descriptor => descriptor.ImplementationType is not null)
+ .Select(static descriptor => new HandlerMapping(descriptor.ServiceType, descriptor.ImplementationType!))
+ .ToHashSet();
+ }
+
///
/// 根据生成器提供的 fallback 清单或整程序集扫描结果,获取本轮要注册的候选处理器类型。
///
@@ -455,21 +471,6 @@ internal static class CqrsHandlerRegistrar
definition == typeof(IStreamRequestHandler<,>);
}
- ///
- /// 判断同一 handler 映射是否已经由生成注册器或先前扫描步骤写入服务集合。
- ///
- private static bool IsHandlerMappingAlreadyRegistered(
- IServiceCollection services,
- Type handlerInterface,
- Type implementationType)
- {
- // 这里保持线性扫描,避免为常见的小到中等规模程序集长期维护额外索引。
- // 若未来大型服务集合出现热点,可在更高层批处理中引入 HashSet<(Type, Type)> 做 O(1) 去重。
- return services.Any(descriptor =>
- descriptor.ServiceType == handlerInterface &&
- descriptor.ImplementationType == implementationType);
- }
-
///
/// 生成程序集排序键,保证跨运行环境的处理器注册顺序稳定。
///
@@ -486,6 +487,8 @@ internal static class CqrsHandlerRegistrar
return type.FullName ?? type.Name;
}
+ private readonly record struct HandlerMapping(Type ServiceType, Type ImplementationType);
+
private readonly record struct GeneratedRegistrationResult(
bool UsedGeneratedRegistry,
bool RequiresReflectionFallback,
diff --git a/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md b/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md
index 8b85d05f..a2c18116 100644
--- a/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md
+++ b/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md
@@ -7,7 +7,7 @@ CQRS 迁移与收敛。
## 当前恢复点
-- 恢复点编号:`CQRS-REWRITE-RP-048`
+- 恢复点编号:`CQRS-REWRITE-RP-049`
- 当前阶段:`Phase 8`
- 当前焦点:
- 当前功能历史已归档,active 跟踪仅保留 `Phase 8` 主线的恢复入口
@@ -16,6 +16,7 @@ CQRS 迁移与收敛。
- 已补充 pointer 响应类型的 precise runtime type 生成,避免这类 handler 再退回程序集级 reflection fallback
- 已收紧 function pointer 签名的可直接生成判定,仅在其返回值与参数类型都可安全引用时才走静态注册路径
- 已为 registrar 的 reflection 注册路径补充 handler-interface 元数据缓存,减少跨容器重复注册时的 `GetInterfaces()` 反射
+ - 已将 registrar 的重复映射判定从线性扫描 `IServiceCollection` 收敛为本地映射索引,减少 fallback 注册路径的重复查找
- 中期上继续 `Phase 8` 主线:参考 `ai-libs/Mediator`,继续扩大 generator 覆盖,并选择下一个收益明确的 dispatch / invoker 反射收敛点
## 当前状态摘要
@@ -45,6 +46,10 @@ CQRS 迁移与收敛。
- `CqrsHandlerRegistrar` 现会按 `Type` 弱键缓存已筛选且排序好的 supported handler interface 列表
- 同一 handler 类型跨容器重复注册时,不再重复执行 `GetInterfaces()` 与支持接口筛选
- `GFramework.Cqrs.Tests` 已补充 registrar 静态缓存隔离与 supported interface 缓存复用回归
+- `2026-04-20` 已完成一轮 registrar 去重路径收敛:
+ - `CqrsHandlerRegistrar` 现会在单次 reflection 注册流程开始时构建已注册 handler 映射索引
+ - 同一批注册中后续 duplicate handler mapping 不再重复线性扫描 `IServiceCollection`
+ - `GFramework.Cqrs.Tests` 已补充“程序集返回重复 handler 类型时仍只注册一份映射”的回归
- 当前主线优先级:
- generator 覆盖面继续扩大
- dispatch/invoker 反射占比继续下降
@@ -74,7 +79,7 @@ CQRS 迁移与收敛。
- 备注:`14/14` 测试通过;本轮覆盖 pointer precise registration 与 function pointer fallback 边界
- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --no-restore -p:RestoreFallbackFolders= -m:1 -nodeReuse:false --filter "FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsHandlerRegistrarTests"`
- 结果:通过
- - 备注:`10/10` 测试通过;本轮覆盖 registrar 的 supported handler interface 缓存
+ - 备注:`11/11` 测试通过;本轮覆盖 registrar 的 supported handler interface 缓存与 duplicate mapping 去重路径
## 下一步
diff --git a/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md b/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md
index 6ad5b62a..63ab4b33 100644
--- a/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md
+++ b/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md
@@ -2,6 +2,16 @@
## 2026-04-20
+### 阶段:registrar duplicate mapping 索引收敛(CQRS-REWRITE-RP-049)
+
+- 已将 `CqrsHandlerRegistrar` 的重复 handler mapping 判定从逐条线性扫描 `IServiceCollection` 收敛为单次构建的本地映射索引
+- reflection fallback 或重复类型输入场景下,后续 duplicate mapping 判定改为 `HashSet` 命中,不再重复遍历已有服务描述符
+- `GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs` 已补充“程序集枚举返回重复 handler 类型时仍只注册一份映射”的回归
+- 定向验证已通过:
+ - `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --no-restore -p:RestoreFallbackFolders= -m:1 -nodeReuse:false --filter "FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsHandlerRegistrarTests"`
+ - `11/11` passed
+ - 当前沙箱限制 MSBuild named pipe,因此验证在提权环境下执行
+
### 阶段:registrar handler-interface 反射缓存(CQRS-REWRITE-RP-048)
- 已在 `CqrsHandlerRegistrar` 中新增按 `Type` 弱键缓存的 supported handler interface 元数据,reflection 注册路径现会复用已筛选且排序好的接口列表
From c1f9fa8b9aac20e5a065bf43e5e18c493ee4ec5e Mon Sep 17 00:00:00 2001
From: GeWuYou <95328647+GeWuYou@users.noreply.github.com>
Date: Mon, 20 Apr 2026 17:55:41 +0800
Subject: [PATCH 6/7] =?UTF-8?q?fix(cqrs):=20=E4=BF=AE=E5=A4=8D=E6=8C=87?=
=?UTF-8?q?=E9=92=88=E5=90=88=E5=90=8C=E7=94=9F=E6=88=90=E5=9B=9E=E5=BD=92?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 修复 CqrsHandlerRegistryGenerator 对 pointer 与 function pointer 的精确注册建模
- 补充生成器测试对输入源 CS0306 与 fallback 诊断的断言
- 更新 cqrs-rewrite 跟踪文档记录 PR #261 review follow-up
---
.../Cqrs/CqrsHandlerRegistryGenerator.cs | 32 +++----
.../Cqrs/CqrsHandlerRegistryGeneratorTests.cs | 83 ++++++++-----------
.../todos/cqrs-rewrite-migration-tracking.md | 23 ++---
.../traces/cqrs-rewrite-migration-trace.md | 14 ++++
4 files changed, 75 insertions(+), 77 deletions(-)
diff --git a/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs b/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs
index 7888f613..9b3f889e 100644
--- a/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs
+++ b/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs
@@ -398,6 +398,15 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
ITypeSymbol type,
out RuntimeTypeReferenceSpec? runtimeTypeReference)
{
+ // CLR forbids pointer and function-pointer types from being used as generic arguments.
+ // CQRS handler contracts are generic interfaces, so emitting runtime reconstruction code for these
+ // shapes would only defer the failure to MakeGenericType(...) at runtime.
+ if (type is IPointerTypeSymbol or IFunctionPointerTypeSymbol)
+ {
+ runtimeTypeReference = null;
+ return false;
+ }
+
if (CanReferenceFromGeneratedRegistry(compilation, type))
{
runtimeTypeReference = RuntimeTypeReferenceSpec.FromDirectReference(
@@ -412,13 +421,6 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
return true;
}
- if (type is IPointerTypeSymbol pointerType &&
- TryCreateRuntimeTypeReference(compilation, pointerType.PointedAtType, out var pointedAtTypeReference))
- {
- runtimeTypeReference = RuntimeTypeReferenceSpec.FromPointer(pointedAtTypeReference!);
- return true;
- }
-
if (type is INamedTypeSymbol genericNamedType &&
genericNamedType.IsGenericType &&
!genericNamedType.IsUnboundGenericType &&
@@ -525,19 +527,9 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
}
return true;
- case IPointerTypeSymbol pointerType:
- return CanReferenceFromGeneratedRegistry(compilation, pointerType.PointedAtType);
- case IFunctionPointerTypeSymbol functionPointerType:
- if (!CanReferenceFromGeneratedRegistry(compilation, functionPointerType.Signature.ReturnType))
- return false;
-
- foreach (var parameter in functionPointerType.Signature.Parameters)
- {
- if (!CanReferenceFromGeneratedRegistry(compilation, parameter.Type))
- return false;
- }
-
- return true;
+ case IPointerTypeSymbol:
+ case IFunctionPointerTypeSymbol:
+ return false;
case ITypeParameterSymbol:
return false;
default:
diff --git a/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs b/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs
index c0b4508f..e9a9bfa2 100644
--- a/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs
+++ b/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs
@@ -164,45 +164,6 @@ public class CqrsHandlerRegistryGeneratorTests
""";
- private const string HiddenPointerResponseExpected = """
- //
- #nullable enable
-
- [assembly: global::GFramework.Cqrs.CqrsHandlerRegistryAttribute(typeof(global::GFramework.Generated.Cqrs.__GFrameworkGeneratedCqrsHandlerRegistry))]
-
- namespace GFramework.Generated.Cqrs;
-
- internal sealed class __GFrameworkGeneratedCqrsHandlerRegistry : global::GFramework.Cqrs.ICqrsHandlerRegistry
- {
- public void Register(global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, global::GFramework.Core.Abstractions.Logging.ILogger logger)
- {
- if (services is null)
- throw new global::System.ArgumentNullException(nameof(services));
- if (logger is null)
- throw new global::System.ArgumentNullException(nameof(logger));
-
- var registryAssembly = typeof(global::GFramework.Generated.Cqrs.__GFrameworkGeneratedCqrsHandlerRegistry).Assembly;
-
- var implementationType0 = typeof(global::TestApp.Container.HiddenHandler);
- if (implementationType0 is not null)
- {
- var serviceType0_0Argument0 = registryAssembly.GetType("TestApp.Container+HiddenRequest", throwOnError: false, ignoreCase: false);
- var serviceType0_0Argument1PointedAt = registryAssembly.GetType("TestApp.Container+HiddenResponse", throwOnError: false, ignoreCase: false);
- if (serviceType0_0Argument0 is not null && serviceType0_0Argument1PointedAt is not null)
- {
- var serviceType0_0 = typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<,>).MakeGenericType(serviceType0_0Argument0, serviceType0_0Argument1PointedAt.MakePointerType());
- global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddTransient(
- services,
- serviceType0_0,
- implementationType0);
- logger.Debug("Registered CQRS handler TestApp.Container.HiddenHandler as GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler.");
- }
- }
- }
- }
-
- """;
-
private const string MixedDirectAndPreciseRegistrationsExpected = """
//
#nullable enable
@@ -785,11 +746,11 @@ public class CqrsHandlerRegistryGeneratorTests
}
///
- /// 验证精确重建路径会递归覆盖隐藏指针元素类型,
- /// 使“隐藏 pointed-at 类型 + unsafe 指针响应”的 handler 也能直接生成 closed service type。
+ /// 验证当 handler 合同把 pointer 响应类型放进 CQRS 泛型参数时,
+ /// 生成器会保守回退而不是继续发射不可构造的精确注册代码。
///
[Test]
- public async Task Generates_Precise_Service_Type_For_Hidden_Pointer_Response()
+ public void Reports_Compilation_Error_And_Skips_Precise_Registration_For_Hidden_Pointer_Response()
{
const string source = """
using System;
@@ -859,20 +820,31 @@ public class CqrsHandlerRegistryGeneratorTests
var execution = ExecuteGenerator(
source,
allowUnsafe: true);
+ var inputCompilationErrors = execution.InputCompilationDiagnostics
+ .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
+ .ToArray();
var generatedCompilationErrors = execution.GeneratedCompilationDiagnostics
.Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
.ToArray();
var generatorErrors = execution.GeneratorDiagnostics
.Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
.ToArray();
+ var missingContractDiagnostic =
+ generatorErrors.SingleOrDefault(static diagnostic =>
+ string.Equals(diagnostic.Id, "GF_Cqrs_001", StringComparison.Ordinal));
Assert.Multiple(() =>
{
+ Assert.That(inputCompilationErrors.Select(static diagnostic => diagnostic.Id), Does.Contain("CS0306"));
Assert.That(generatedCompilationErrors, Is.Empty);
- Assert.That(generatorErrors, Is.Empty);
- Assert.That(execution.GeneratedSources, Has.Length.EqualTo(1));
- Assert.That(execution.GeneratedSources[0].filename, Is.EqualTo("CqrsHandlerRegistry.g.cs"));
- Assert.That(execution.GeneratedSources[0].content, Is.EqualTo(HiddenPointerResponseExpected));
+ Assert.That(execution.GeneratedSources, Is.Empty);
+ Assert.That(missingContractDiagnostic, Is.Not.Null);
+ Assert.That(
+ missingContractDiagnostic!.GetMessage(),
+ Does.Contain("TestApp.Container+HiddenHandler"));
+ Assert.That(
+ missingContractDiagnostic.GetMessage(),
+ Does.Contain("GFramework.Cqrs.CqrsReflectionFallbackAttribute"));
});
}
@@ -1375,6 +1347,9 @@ public class CqrsHandlerRegistryGeneratorTests
var execution = ExecuteGenerator(
source,
allowUnsafe: true);
+ var inputCompilationErrors = execution.InputCompilationDiagnostics
+ .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
+ .ToArray();
var generatedCompilationErrors = execution.GeneratedCompilationDiagnostics
.Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
.ToArray();
@@ -1382,10 +1357,12 @@ public class CqrsHandlerRegistryGeneratorTests
.Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
.ToArray();
var missingContractDiagnostic =
- generatorErrors.SingleOrDefault(static diagnostic => diagnostic.Id == "GF_Cqrs_001");
+ generatorErrors.SingleOrDefault(static diagnostic =>
+ string.Equals(diagnostic.Id, "GF_Cqrs_001", StringComparison.Ordinal));
Assert.Multiple(() =>
{
+ Assert.That(inputCompilationErrors.Select(static diagnostic => diagnostic.Id), Does.Contain("CS0306"));
Assert.That(generatedCompilationErrors, Is.Empty);
Assert.That(execution.GeneratedSources, Is.Empty);
Assert.That(missingContractDiagnostic, Is.Not.Null);
@@ -1490,6 +1467,9 @@ public class CqrsHandlerRegistryGeneratorTests
var execution = ExecuteGenerator(
source,
allowUnsafe: true);
+ var inputCompilationErrors = execution.InputCompilationDiagnostics
+ .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
+ .ToArray();
var generatedCompilationErrors = execution.GeneratedCompilationDiagnostics
.Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
.ToArray();
@@ -1499,6 +1479,7 @@ public class CqrsHandlerRegistryGeneratorTests
Assert.Multiple(() =>
{
+ Assert.That(inputCompilationErrors.Select(static diagnostic => diagnostic.Id), Does.Contain("CS0306"));
Assert.That(generatedCompilationErrors, Is.Empty);
Assert.That(generatorErrors, Is.Empty);
Assert.That(execution.GeneratedSources, Has.Length.EqualTo(1));
@@ -1611,6 +1592,11 @@ public class CqrsHandlerRegistryGeneratorTests
(filename: sourceResult.HintName, content: sourceResult.SourceText.ToString()))
.ToArray();
var compilationDiagnostics = updatedCompilation.GetDiagnostics().ToArray();
+ var inputCompilationDiagnostics = compilationDiagnostics
+ .Where(diagnostic =>
+ diagnostic.Location.SourceTree is null ||
+ !generatedSyntaxTrees.Contains(diagnostic.Location.SourceTree))
+ .ToArray();
var generatedCompilationDiagnostics = compilationDiagnostics
.Where(diagnostic =>
diagnostic.Location.SourceTree is not null &&
@@ -1620,6 +1606,7 @@ public class CqrsHandlerRegistryGeneratorTests
generatedSources,
generatorDiagnostics.ToArray(),
compilationDiagnostics,
+ inputCompilationDiagnostics,
generatedCompilationDiagnostics);
}
@@ -1629,10 +1616,12 @@ public class CqrsHandlerRegistryGeneratorTests
/// 本轮生成产生的源文件集合。
/// 生成器自身报告的诊断集合。
/// 将生成结果并回编译后的完整编译诊断集合。
+ /// 仅来自输入源文件的编译诊断集合。
/// 仅来自生成源文件的编译诊断集合。
private sealed record GeneratorExecutionResult(
(string filename, string content)[] GeneratedSources,
Diagnostic[] GeneratorDiagnostics,
Diagnostic[] CompilationDiagnostics,
+ Diagnostic[] InputCompilationDiagnostics,
Diagnostic[] GeneratedCompilationDiagnostics);
}
diff --git a/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md b/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md
index a2c18116..527cb1ad 100644
--- a/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md
+++ b/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md
@@ -7,14 +7,14 @@ CQRS 迁移与收敛。
## 当前恢复点
-- 恢复点编号:`CQRS-REWRITE-RP-049`
+- 恢复点编号:`CQRS-REWRITE-RP-050`
- 当前阶段:`Phase 8`
- 当前焦点:
- 当前功能历史已归档,active 跟踪仅保留 `Phase 8` 主线的恢复入口
- 已完成 generated registry 激活路径收敛:`CqrsHandlerRegistrar` 现优先复用缓存工厂委托,避免重复 `ConstructorInfo.Invoke`
- 已补充私有无参构造 generated registry 的回归测试,确保兼容现有生成器产物
- - 已补充 pointer 响应类型的 precise runtime type 生成,避免这类 handler 再退回程序集级 reflection fallback
- - 已收紧 function pointer 签名的可直接生成判定,仅在其返回值与参数类型都可安全引用时才走静态注册路径
+ - 已修正 pointer / function pointer 泛型合同的错误覆盖:生成器不再为这两类类型发射 precise runtime type 重建代码
+ - 已补充非法 CQRS 泛型合同的输入诊断断言,明确 `CS0306` 与 fallback / diagnostic 路径的组合语义
- 已为 registrar 的 reflection 注册路径补充 handler-interface 元数据缓存,减少跨容器重复注册时的 `GetInterfaces()` 反射
- 已将 registrar 的重复映射判定从线性扫描 `IServiceCollection` 收敛为本地映射索引,减少 fallback 注册路径的重复查找
- 中期上继续 `Phase 8` 主线:参考 `ai-libs/Mediator`,继续扩大 generator 覆盖,并选择下一个收益明确的 dispatch / invoker 反射收敛点
@@ -31,17 +31,17 @@ CQRS 迁移与收敛。
- `Phase 8` 仍是当前主线,不再回退到 `Phase 7`
- `2026-04-20` 已重新执行 `$gframework-pr-review`:
- - `PR #253` 当前状态为 `CLOSED`
- - latest reviewed commit 仍显示 `1` 条 open thread,但其内容针对的是已过时的 `Phase 7` 恢复建议
- - 当前 active tracking / trace 已统一到 `Phase 8`,因此该 thread 不再作为当前主线阻塞项
+ - 当前分支对应 `PR #261`,状态为 `OPEN`
+ - latest reviewed commit 上有 `2` 条 open CodeRabbit thread,均指向 pointer / function pointer 泛型合同处理
+ - 本地已接受并修复这两条建议:生成器拒绝为 pointer / function pointer 生成 precise registration,测试改为显式断言输入源 `CS0306`
- `2026-04-20` 已完成一轮冷启动反射收敛:
- generated registry 类型首次分析后,会缓存一个可复用的激活工厂,而不是在后续容器注册时重复走 `ConstructorInfo.Invoke`
- 若运行环境不允许动态方法,仍保留原有的反射激活回退,避免阻塞 generated registry 路径
- `GFramework.Cqrs.Tests` 已补充“私有无参构造 registry 仍可激活”的回归覆盖
- `2026-04-20` 已完成一轮 generator 覆盖面扩展:
- - `CqrsHandlerRegistryGenerator` 现可为 pointer 类型递归重建 runtime type,并通过 `MakePointerType()` 生成精确 service type
- - function pointer 签名不再默认视为“可直接引用”;只有当返回值与每个参数类型都可从 generated registry 安全引用时,才允许直接生成
- - 含隐藏类型的 function pointer handler 仍会保留原有 fallback / 诊断路径,避免此次覆盖扩展误伤已有回退边界
+ - `CqrsHandlerRegistryGenerator` 现会在 runtime type 建模入口直接拒绝 `IPointerTypeSymbol` 与 `IFunctionPointerTypeSymbol`
+ - `CanReferenceFromGeneratedRegistry` 不再递归判断 pointer / function pointer 的内部元素,而是统一返回 `false`
+ - 相关 source-generator 回归已改为区分输入源诊断与生成源诊断,避免把非法泛型合同误判为成功生成
- `2026-04-20` 已完成一轮 registrar reflection 路径收敛:
- `CqrsHandlerRegistrar` 现会按 `Type` 弱键缓存已筛选且排序好的 supported handler interface 列表
- 同一 handler 类型跨容器重复注册时,不再重复执行 `GetInterfaces()` 与支持接口筛选
@@ -76,7 +76,10 @@ CQRS 迁移与收敛。
- 备注:`63/63` 测试通过;当前沙箱限制了 MSBuild named pipe,验证需在提权环境下运行
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore -p:RestoreFallbackFolders= -m:1 -nodeReuse:false --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests"`
- 结果:通过
- - 备注:`14/14` 测试通过;本轮覆盖 pointer precise registration 与 function pointer fallback 边界
+ - 备注:`14/14` 测试通过;本轮覆盖 pointer / function pointer 合同拒绝、fallback 诊断与现有精确注册路径
+- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore -p:RestoreFallbackFolders= -m:1 -nodeReuse:false --filter "FullyQualifiedName~Reports_Compilation_Error_And_Skips_Precise_Registration_For_Hidden_Pointer_Response|FullyQualifiedName~Reports_Diagnostic_And_Skips_Registry_When_Fallback_Metadata_Is_Required_But_Runtime_Contract_Lacks_Fallback_Attribute|FullyQualifiedName~Emits_Assembly_Level_Fallback_Metadata_When_Fallback_Is_Required_And_Runtime_Contract_Is_Available"`
+ - 结果:通过
+ - 备注:`3/3` 测试通过;本轮直接覆盖 PR #261 指向的 3 个 pointer / function pointer 回归场景
- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --no-restore -p:RestoreFallbackFolders= -m:1 -nodeReuse:false --filter "FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsHandlerRegistrarTests"`
- 结果:通过
- 备注:`11/11` 测试通过;本轮覆盖 registrar 的 supported handler interface 缓存与 duplicate mapping 去重路径
diff --git a/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md b/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md
index 63ab4b33..7fd73df7 100644
--- a/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md
+++ b/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md
@@ -2,6 +2,20 @@
## 2026-04-20
+### 阶段:pointer / function pointer 泛型合同拒绝(CQRS-REWRITE-RP-050)
+
+- 重新执行 `$gframework-pr-review` 后,确认当前分支对应 `PR #261`,latest reviewed commit 上有 `2` 条仍未关闭的 CodeRabbit thread
+- 本地核对后确认这两条评论有效:`CqrsHandlerRegistryGenerator` 之前会为 `IPointerTypeSymbol` 递归构造 `MakePointerType()`,而测试只校验生成源诊断,未显式暴露输入源 `CS0306`
+- 已在 `GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs` 中收紧 `TryCreateRuntimeTypeReference` 与 `CanReferenceFromGeneratedRegistry`
+- pointer / function pointer 现统一视为不可精确生成的 CQRS 泛型合同,生成器会保守回退到既有 fallback / diagnostic 路径,而不再发射运行时 `MakeGenericType(...)` 风险代码
+- 已在 `GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs` 中补充输入源诊断分离,并将相关测试改为显式断言 `CS0306` 与 fallback / diagnostic 结果
+- 定向验证已通过:
+ - `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore -p:RestoreFallbackFolders= -m:1 -nodeReuse:false --filter "FullyQualifiedName~Reports_Compilation_Error_And_Skips_Precise_Registration_For_Hidden_Pointer_Response|FullyQualifiedName~Reports_Diagnostic_And_Skips_Registry_When_Fallback_Metadata_Is_Required_But_Runtime_Contract_Lacks_Fallback_Attribute|FullyQualifiedName~Emits_Assembly_Level_Fallback_Metadata_When_Fallback_Is_Required_And_Runtime_Contract_Is_Available"`
+ - `3/3` passed
+- 扩展验证已通过:
+ - `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore -p:RestoreFallbackFolders= -m:1 -nodeReuse:false --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests"`
+ - `14/14` passed
+
### 阶段:registrar duplicate mapping 索引收敛(CQRS-REWRITE-RP-049)
- 已将 `CqrsHandlerRegistrar` 的重复 handler mapping 判定从逐条线性扫描 `IServiceCollection` 收敛为单次构建的本地映射索引
From 13d52a8a943a2b4f68b154e379edd82eaef79180 Mon Sep 17 00:00:00 2001
From: GeWuYou <95328647+GeWuYou@users.noreply.github.com>
Date: Mon, 20 Apr 2026 19:48:31 +0800
Subject: [PATCH 7/7] =?UTF-8?q?docs(cqrs-rewrite):=20=E5=90=8C=E6=AD=A5PR?=
=?UTF-8?q?=E8=AF=84=E5=AE=A1=E8=BF=BD=E8=B8=AA=E7=8A=B6=E6=80=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 更新 cqrs-rewrite migration trace,标记 RP-047 已被 RP-050 覆盖并禁止恢复 MakePointerType precise registration
- 同步 migration tracking 中 PR #261 的 open thread、CTRF 测试结果与 MegaLinter 状态
---
.../todos/cqrs-rewrite-migration-tracking.md | 5 +++--
.../traces/cqrs-rewrite-migration-trace.md | 12 ++++++++----
2 files changed, 11 insertions(+), 6 deletions(-)
diff --git a/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md b/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md
index 527cb1ad..86a99c7a 100644
--- a/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md
+++ b/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md
@@ -32,8 +32,9 @@ CQRS 迁移与收敛。
- `Phase 8` 仍是当前主线,不再回退到 `Phase 7`
- `2026-04-20` 已重新执行 `$gframework-pr-review`:
- 当前分支对应 `PR #261`,状态为 `OPEN`
- - latest reviewed commit 上有 `2` 条 open CodeRabbit thread,均指向 pointer / function pointer 泛型合同处理
- - 本地已接受并修复这两条建议:生成器拒绝为 pointer / function pointer 生成 precise registration,测试改为显式断言输入源 `CS0306`
+ - latest reviewed commit 当前剩余 `1` 条 open CodeRabbit thread,指向 `ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md` 中 `RP-047` 与 `RP-050` 的历史语义冲突
+ - 本地已同步修正该追踪歧义:`RP-047` 明确标注为已被 `RP-050` 覆盖,后续不得恢复 `MakePointerType()` precise registration
+ - 远端测试信号保持通过:最新 CTRF 汇总为 `2118/2118` passed;MegaLinter 仅剩 `dotnet-format` restore failure 预警,当前未提供本地仍然成立的文件级格式问题
- `2026-04-20` 已完成一轮冷启动反射收敛:
- generated registry 类型首次分析后,会缓存一个可复用的激活工厂,而不是在后续容器注册时重复走 `ConstructorInfo.Invoke`
- 若运行环境不允许动态方法,仍保留原有的反射激活回退,避免阻塞 generated registry 路径
diff --git a/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md b/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md
index 7fd73df7..307dcf55 100644
--- a/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md
+++ b/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md
@@ -4,11 +4,13 @@
### 阶段:pointer / function pointer 泛型合同拒绝(CQRS-REWRITE-RP-050)
-- 重新执行 `$gframework-pr-review` 后,确认当前分支对应 `PR #261`,latest reviewed commit 上有 `2` 条仍未关闭的 CodeRabbit thread
-- 本地核对后确认这两条评论有效:`CqrsHandlerRegistryGenerator` 之前会为 `IPointerTypeSymbol` 递归构造 `MakePointerType()`,而测试只校验生成源诊断,未显式暴露输入源 `CS0306`
+- 重新执行 `$gframework-pr-review` 后,确认当前分支对应 `PR #261`,状态仍为 `OPEN`
+- latest reviewed commit 当前剩余 `1` 条 open CodeRabbit thread,指向 `RP-047` 历史记录仍把 `MakePointerType()` precise registration 写成现行路径
+- 本地核对后确认该评论有效:当前 pointer / function pointer 语义已由 `RP-050` 收敛为 fallback / diagnostic 路径,历史追踪必须显式标注 `RP-047` 已废弃,避免后续恢复时误回滚到旧方案
- 已在 `GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs` 中收紧 `TryCreateRuntimeTypeReference` 与 `CanReferenceFromGeneratedRegistry`
- pointer / function pointer 现统一视为不可精确生成的 CQRS 泛型合同,生成器会保守回退到既有 fallback / diagnostic 路径,而不再发射运行时 `MakeGenericType(...)` 风险代码
- 已在 `GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs` 中补充输入源诊断分离,并将相关测试改为显式断言 `CS0306` 与 fallback / diagnostic 结果
+- 已同步修正 `ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md` 中 `RP-047` 段落,明确其已被 `RP-050` 覆盖,且不得恢复 `MakePointerType()` precise registration
- 定向验证已通过:
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore -p:RestoreFallbackFolders= -m:1 -nodeReuse:false --filter "FullyQualifiedName~Reports_Compilation_Error_And_Skips_Precise_Registration_For_Hidden_Pointer_Response|FullyQualifiedName~Reports_Diagnostic_And_Skips_Registry_When_Fallback_Metadata_Is_Required_But_Runtime_Contract_Lacks_Fallback_Attribute|FullyQualifiedName~Emits_Assembly_Level_Fallback_Metadata_When_Fallback_Is_Required_And_Runtime_Contract_Is_Available"`
- `3/3` passed
@@ -36,11 +38,13 @@
- `10/10` passed
- 当前沙箱限制 MSBuild named pipe,因此验证在提权环境下执行
-### 阶段:pointer precise runtime type 覆盖扩展(CQRS-REWRITE-RP-047)
+### 阶段:pointer precise runtime type 覆盖扩展(CQRS-REWRITE-RP-047,已由 RP-050 覆盖)
-- 已在 `CqrsHandlerRegistryGenerator` 中补充 pointer 类型的 runtime type 递归建模与源码发射,precise registration 现可通过 `MakePointerType()` 还原隐藏 pointer 响应类型
+- 曾在 `CqrsHandlerRegistryGenerator` 中尝试补充 pointer 类型的 runtime type 递归建模与源码发射,计划通过 `MakePointerType()` 还原隐藏 pointer 响应类型
+- 该方案后续已被 `RP-050` 明确废弃:pointer / function pointer 不能作为 CQRS 泛型合同的 precise registration 输入,当前实现统一回到 fallback / diagnostic 路径,不能恢复到 `MakePointerType()` 精确注册
- 已同步收紧 function pointer 签名的可直接生成判定,只有当签名中的返回值与参数类型均可从 generated registry 安全引用时才走静态注册
- 已保留含隐藏类型 function pointer handler 的 fallback / 诊断回归覆盖,确保 pointer 支持扩展不会误删原有程序集级 fallback 契约边界
+- 后续若需恢复当前 pointer / function pointer 行为,应以 `RP-050` 为权威记录,而不是继续沿用本阶段的旧设计假设
- 定向验证与 `CqrsHandlerRegistryGeneratorTests` 全组验证均已通过:
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore -p:RestoreFallbackFolders= -m:1 -nodeReuse:false --filter "FullyQualifiedName~Generates_Precise_Service_Type_For_Hidden_Pointer_Response|FullyQualifiedName~Reports_Diagnostic_And_Skips_Registry_When_Fallback_Metadata_Is_Required_But_Runtime_Contract_Lacks_Fallback_Attribute|FullyQualifiedName~Emits_Assembly_Level_Fallback_Metadata_When_Fallback_Is_Required_And_Runtime_Contract_Is_Available"`
- `3/3` passed