From e51b64f8d52491d9f48b7a6c36c6c0db81c507a0 Mon Sep 17 00:00:00 2001 From: gewuyou <95328647+GeWuYou@users.noreply.github.com> Date: Wed, 29 Apr 2026 17:33:27 +0800 Subject: [PATCH] =?UTF-8?q?test(cqrs):=20=E8=A1=A5=E9=BD=90=E5=A4=96?= =?UTF-8?q?=E9=83=A8=E9=9A=90=E8=97=8F=E6=B3=9B=E5=9E=8B=E7=B2=BE=E7=A1=AE?= =?UTF-8?q?=E6=B3=A8=E5=86=8C=E5=9B=9E=E5=BD=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增外部程序集隐藏泛型定义与可见类型实参的 precise registration 回归 - 更新 CQRS 重写跟踪与 trace,记录本轮覆盖范围和验证结果 --- GFramework.Cqrs/Internal/CqrsDispatcher.cs | 18 ++++-- .../Internal/CqrsHandlerRegistrar.cs | 6 +- .../Cqrs/CqrsHandlerRegistryGeneratorTests.cs | 58 +++++++++++++++++++ .../todos/cqrs-rewrite-migration-tracking.md | 7 +++ .../traces/cqrs-rewrite-migration-trace.md | 4 ++ 5 files changed, 86 insertions(+), 7 deletions(-) diff --git a/GFramework.Cqrs/Internal/CqrsDispatcher.cs b/GFramework.Cqrs/Internal/CqrsDispatcher.cs index 505299c5..17e81e8e 100644 --- a/GFramework.Cqrs/Internal/CqrsDispatcher.cs +++ b/GFramework.Cqrs/Internal/CqrsDispatcher.cs @@ -62,7 +62,7 @@ internal sealed class CqrsDispatcher( var notificationType = notification.GetType(); var dispatchBinding = NotificationDispatchBindings.GetOrAdd( notificationType, - CreateNotificationDispatchBinding); + static notificationType => CreateNotificationDispatchBinding(notificationType)); var handlers = container.GetAll(dispatchBinding.HandlerType); if (handlers.Count == 0) @@ -134,7 +134,7 @@ internal sealed class CqrsDispatcher( var dispatchBinding = StreamDispatchBindings.GetOrAdd( requestType, typeof(TResponse), - CreateStreamDispatchBinding); + static (requestType, responseType) => CreateStreamDispatchBinding(requestType, responseType)); var handler = container.Get(dispatchBinding.HandlerType) ?? throw new InvalidOperationException( $"No CQRS stream handler registered for {requestType.FullName}."); @@ -181,7 +181,8 @@ internal sealed class CqrsDispatcher( var bindingBox = RequestDispatchBindings.GetOrAdd( requestType, typeof(TResponse), - CreateRequestDispatchBindingBox); + static (cachedRequestType, cachedResponseType) => + CreateRequestDispatchBindingBox(cachedRequestType, cachedResponseType)); return bindingBox.Get(); } @@ -438,9 +439,10 @@ internal sealed class CqrsDispatcher( public RequestPipelineExecutor GetPipelineExecutor(int behaviorCount) { ArgumentOutOfRangeException.ThrowIfNegative(behaviorCount); - return _pipelineExecutors.GetOrAdd( + return _pipelineExecutors.GetOrAdd>( behaviorCount, - count => CreateRequestPipelineExecutor(requestType, count)); + static (count, state) => CreateRequestPipelineExecutor(state.RequestType, count), + new RequestPipelineExecutorFactoryState(requestType)); } /// @@ -504,6 +506,12 @@ internal sealed class CqrsDispatcher( } } + /// + /// 为 pipeline executor 缓存携带当前请求类型,避免按行为数量建缓存时创建闭包。 + /// + /// 请求响应类型。 + private readonly record struct RequestPipelineExecutorFactoryState(Type RequestType); + /// /// 保存单次 request pipeline 分发所需的当前 handler、behavior 列表和 continuation 缓存。 /// 该对象只存在于本次分发,不会跨请求保留容器解析出的实例。 diff --git a/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs b/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs index 21db7821..1be2729c 100644 --- a/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs +++ b/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs @@ -83,7 +83,8 @@ internal static class CqrsHandlerRegistrar { var assemblyMetadata = AssemblyMetadataCache.GetOrAdd( assembly, - key => AnalyzeAssemblyRegistrationMetadata(key, logger)); + logger, + static (key, state) => AnalyzeAssemblyRegistrationMetadata(key, state)); var registryTypes = assemblyMetadata.RegistryTypes; if (registryTypes.Count == 0) @@ -442,7 +443,8 @@ internal static class CqrsHandlerRegistrar { return LoadableTypesCache.GetOrAdd( assembly, - key => LoadAndSortTypes(key, logger)); + logger, + static (key, state) => LoadAndSortTypes(key, state)); } /// diff --git a/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs b/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs index e8cc2378..51926a0d 100644 --- a/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs +++ b/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs @@ -1103,6 +1103,26 @@ public class CqrsHandlerRegistryGeneratorTests } """; + private const string ExternalProtectedGenericDefinitionDependencySource = """ + using GFramework.Cqrs.Abstractions.Cqrs; + + namespace Dep; + + public abstract class VisibilityScope + { + protected internal sealed class ProtectedEnvelope + { + } + + protected internal sealed record ProtectedRequest() : IRequest>; + } + + public abstract class HandlerBase : + IRequestHandler> + { + } + """; + private const string LegacyFallbackMarkerHiddenHandlerSource = """ using System; @@ -1901,6 +1921,44 @@ public class CqrsHandlerRegistryGeneratorTests }); } + /// + /// 验证当外部程序集隐藏泛型定义以“隐藏定义 + 可见类型实参”的形式参与 CQRS 合同时, + /// 生成器会继续输出定向程序集查找与运行时泛型重建,而不是退回字符串 fallback 元数据。 + /// + [Test] + public void Generates_Precise_Assembly_Type_Lookups_For_Inaccessible_External_Generic_Definitions_With_Visible_Type_Arguments() + { + var contractsReference = MetadataReferenceTestBuilder.CreateFromSource( + "Contracts", + ExternalProtectedTypeContractsSource); + var dependencyReference = MetadataReferenceTestBuilder.CreateFromSource( + "Dependency", + ExternalProtectedGenericDefinitionDependencySource, + contractsReference); + var generatedSource = RunGenerator( + ExternalProtectedTypeLookupSource, + contractsReference, + dependencyReference); + + Assert.Multiple(() => + { + Assert.That( + generatedSource, + Does.Contain( + "var serviceType0_0Argument0 = ResolveReferencedAssemblyType(\"Dependency, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null\", \"Dep.VisibilityScope+ProtectedRequest\");")); + Assert.That( + generatedSource, + Does.Contain( + "var serviceType0_0Argument1GenericDefinition = ResolveReferencedAssemblyType(\"Dependency, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null\", \"Dep.VisibilityScope+ProtectedEnvelope`1\");")); + Assert.That( + generatedSource, + Does.Contain( + "var serviceType0_0 = typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<,>).MakeGenericType(serviceType0_0Argument0, serviceType0_0Argument1GenericDefinition.MakeGenericType(typeof(string)));")); + Assert.That(generatedSource, Does.Not.Contain("RegisterRemainingReflectedHandlerInterfaces(")); + Assert.That(generatedSource, Does.Not.Contain("CqrsReflectionFallbackAttribute(")); + }); + } + /// /// 验证即使 runtime 仍暴露旧版无参 fallback marker,生成器也会优先在生成注册器内部处理隐藏 handler, /// 不再输出 fallback marker。 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 7390846d..6825adb2 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 @@ -77,6 +77,10 @@ CQRS 迁移与收敛。 - `GFramework.SourceGenerators.Tests` 已新增多维数组、交错数组、外部程序集隐藏元素类型三类回归 - 当前生成器在 precise runtime type lookup 下已稳定保留数组秩信息,并递归发射交错数组的 `MakeArrayType()` 链 - 本轮定向测试未暴露数组发射缺陷,因此未改动 fallback 合同选择逻辑,也未调整 direct / named / mixed fallback 排版路径 +- `2026-04-29` 已补齐一轮外部程序集隐藏泛型定义回归覆盖: + - `GFramework.SourceGenerators.Tests` 已新增“外部程序集隐藏泛型定义 + 可见类型实参”的 precise registration 回归 + - 当前生成器会继续为这类 handler 合同发射 `ResolveReferencedAssemblyType(...) + MakeGenericType(...)` 组合,而不是退回字符串 fallback 元数据 + - 本轮定向测试未暴露新的实现缺口,因此未改动 direct / named / mixed fallback 选择逻辑,也未调整 generator runtime type 建模实现 - `2026-04-29` 已完成一轮 request pipeline executor 形状缓存: - `CqrsDispatcher` 现会继续按 `requestType + responseType` 缓存 request dispatch binding,并在 binding 内按 `behaviorCount` 缓存强类型 pipeline executor - 每次分发只绑定当前 handler / behaviors 实例,不缓存容器解析结果,因此不改变 transient 生命周期与上下文注入语义 @@ -149,6 +153,9 @@ CQRS 迁移与收敛。 - `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests"` - 结果:通过 - 备注:`21/21` 测试通过;本轮新增多维数组、交错数组与外部程序集隐藏元素类型的 precise runtime type lookup 回归 +- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests"` + - 结果:通过 + - 备注:`22/22` 测试通过;本轮新增“外部程序集隐藏泛型定义 + 可见类型实参”的 precise registration 回归,确认仍走定向运行时类型重建 - `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsDispatcherCacheTests"` - 结果:通过 - 备注:`4/4` 测试通过;本轮覆盖 request pipeline executor 的首次创建、复用与双行为顺序回归 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 d01396a5..fc868571 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 @@ -9,6 +9,10 @@ - 当前分支头最初与 `origin/main` 对齐,批次阈值从 `0 files / 0 lines` 起算 - 本轮可以安全拆成三个互不冲突的切片:request pipeline executor 形状缓存、precise runtime type lookup 数组回归补强、CQRS 入口文档对齐 - 主线程保留集成与验证职责,subagent 只负责各自写集 +- 本轮继续收口一个更窄的 generator 覆盖缺口: + - 在 `GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs` 新增“外部程序集隐藏泛型定义 + 可见类型实参”的 precise registration 回归 + - 该回归锁定生成器会输出 `ResolveReferencedAssemblyType("...ProtectedEnvelope\`1")` 与 `MakeGenericType(typeof(string))` 的组合,而不是退回程序集级字符串 fallback + - 定向测试 `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests"` 通过,结果为 `22/22` passed,因此本轮未触发 `RuntimeTypeReferences` / `SourceEmission` 的实现修正 - 已接受并整合的并行写集: - docs 切片:更新 `GFramework.Cqrs/README.md`、`docs/zh-CN/core/cqrs.md`、`docs/zh-CN/api-reference/index.md`,明确 generated registry 优先、targeted fallback 只补剩余 handler - generator 切片:在 `GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs` 新增多维数组、交错数组、外部程序集隐藏元素类型三组 precise lookup 回归