mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-12 22:03:30 +08:00
Merge pull request #302 from GeWuYou/feat/cqrs-optimization
Feat/cqrs optimization
This commit is contained in:
commit
ddaabd8104
@ -154,6 +154,29 @@ public sealed partial class CqrsHandlerRegistryGenerator
|
|||||||
string HandlerInterfaceLogName,
|
string HandlerInterfaceLogName,
|
||||||
ImmutableArray<RuntimeTypeReferenceSpec> ServiceTypeArguments);
|
ImmutableArray<RuntimeTypeReferenceSpec> ServiceTypeArguments);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 描述单个程序集级 reflection fallback 特性实例的发射内容。
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 某些运行时合同允许生成器把可直接引用的 fallback handlers 与必须按名称恢复的 handlers
|
||||||
|
/// 拆成多个特性实例,以进一步减少运行时字符串查找成本。
|
||||||
|
/// </remarks>
|
||||||
|
private readonly record struct ReflectionFallbackAttributeEmissionSpec(
|
||||||
|
bool EmitDirectTypeReferences,
|
||||||
|
ImmutableArray<string> Values);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 描述本轮生成应如何发射程序集级 reflection fallback 元数据。
|
||||||
|
/// </summary>
|
||||||
|
private readonly record struct ReflectionFallbackEmissionSpec(
|
||||||
|
ImmutableArray<ReflectionFallbackAttributeEmissionSpec> Attributes)
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取当前是否需要发射任何 fallback 元数据。
|
||||||
|
/// </summary>
|
||||||
|
public bool HasFallbackHandlers => !Attributes.IsDefaultOrEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
private readonly record struct ImplementationRegistrationSpec(
|
private readonly record struct ImplementationRegistrationSpec(
|
||||||
string ImplementationTypeDisplayName,
|
string ImplementationTypeDisplayName,
|
||||||
string ImplementationLogName,
|
string ImplementationLogName,
|
||||||
@ -161,6 +184,7 @@ public sealed partial class CqrsHandlerRegistryGenerator
|
|||||||
ImmutableArray<ReflectedImplementationRegistrationSpec> ReflectedImplementationRegistrations,
|
ImmutableArray<ReflectedImplementationRegistrationSpec> ReflectedImplementationRegistrations,
|
||||||
ImmutableArray<PreciseReflectedRegistrationSpec> PreciseReflectedRegistrations,
|
ImmutableArray<PreciseReflectedRegistrationSpec> PreciseReflectedRegistrations,
|
||||||
string? ReflectionTypeMetadataName,
|
string? ReflectionTypeMetadataName,
|
||||||
|
string? ReflectionFallbackHandlerTypeDisplayName,
|
||||||
string? ReflectionFallbackHandlerTypeMetadataName);
|
string? ReflectionFallbackHandlerTypeMetadataName);
|
||||||
|
|
||||||
private readonly struct HandlerCandidateAnalysis : IEquatable<HandlerCandidateAnalysis>
|
private readonly struct HandlerCandidateAnalysis : IEquatable<HandlerCandidateAnalysis>
|
||||||
@ -172,6 +196,7 @@ public sealed partial class CqrsHandlerRegistryGenerator
|
|||||||
ImmutableArray<ReflectedImplementationRegistrationSpec> reflectedImplementationRegistrations,
|
ImmutableArray<ReflectedImplementationRegistrationSpec> reflectedImplementationRegistrations,
|
||||||
ImmutableArray<PreciseReflectedRegistrationSpec> preciseReflectedRegistrations,
|
ImmutableArray<PreciseReflectedRegistrationSpec> preciseReflectedRegistrations,
|
||||||
string? reflectionTypeMetadataName,
|
string? reflectionTypeMetadataName,
|
||||||
|
string? reflectionFallbackHandlerTypeDisplayName,
|
||||||
string? reflectionFallbackHandlerTypeMetadataName)
|
string? reflectionFallbackHandlerTypeMetadataName)
|
||||||
{
|
{
|
||||||
ImplementationTypeDisplayName = implementationTypeDisplayName;
|
ImplementationTypeDisplayName = implementationTypeDisplayName;
|
||||||
@ -180,6 +205,7 @@ public sealed partial class CqrsHandlerRegistryGenerator
|
|||||||
ReflectedImplementationRegistrations = reflectedImplementationRegistrations;
|
ReflectedImplementationRegistrations = reflectedImplementationRegistrations;
|
||||||
PreciseReflectedRegistrations = preciseReflectedRegistrations;
|
PreciseReflectedRegistrations = preciseReflectedRegistrations;
|
||||||
ReflectionTypeMetadataName = reflectionTypeMetadataName;
|
ReflectionTypeMetadataName = reflectionTypeMetadataName;
|
||||||
|
ReflectionFallbackHandlerTypeDisplayName = reflectionFallbackHandlerTypeDisplayName;
|
||||||
ReflectionFallbackHandlerTypeMetadataName = reflectionFallbackHandlerTypeMetadataName;
|
ReflectionFallbackHandlerTypeMetadataName = reflectionFallbackHandlerTypeMetadataName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,6 +221,8 @@ public sealed partial class CqrsHandlerRegistryGenerator
|
|||||||
|
|
||||||
public string? ReflectionTypeMetadataName { get; }
|
public string? ReflectionTypeMetadataName { get; }
|
||||||
|
|
||||||
|
public string? ReflectionFallbackHandlerTypeDisplayName { get; }
|
||||||
|
|
||||||
public string? ReflectionFallbackHandlerTypeMetadataName { get; }
|
public string? ReflectionFallbackHandlerTypeMetadataName { get; }
|
||||||
|
|
||||||
public bool Equals(HandlerCandidateAnalysis other)
|
public bool Equals(HandlerCandidateAnalysis other)
|
||||||
@ -204,6 +232,10 @@ public sealed partial class CqrsHandlerRegistryGenerator
|
|||||||
!string.Equals(ImplementationLogName, other.ImplementationLogName, StringComparison.Ordinal) ||
|
!string.Equals(ImplementationLogName, other.ImplementationLogName, StringComparison.Ordinal) ||
|
||||||
!string.Equals(ReflectionTypeMetadataName, other.ReflectionTypeMetadataName,
|
!string.Equals(ReflectionTypeMetadataName, other.ReflectionTypeMetadataName,
|
||||||
StringComparison.Ordinal) ||
|
StringComparison.Ordinal) ||
|
||||||
|
!string.Equals(
|
||||||
|
ReflectionFallbackHandlerTypeDisplayName,
|
||||||
|
other.ReflectionFallbackHandlerTypeDisplayName,
|
||||||
|
StringComparison.Ordinal) ||
|
||||||
!string.Equals(
|
!string.Equals(
|
||||||
ReflectionFallbackHandlerTypeMetadataName,
|
ReflectionFallbackHandlerTypeMetadataName,
|
||||||
other.ReflectionFallbackHandlerTypeMetadataName,
|
other.ReflectionFallbackHandlerTypeMetadataName,
|
||||||
@ -254,6 +286,10 @@ public sealed partial class CqrsHandlerRegistryGenerator
|
|||||||
(ReflectionTypeMetadataName is null
|
(ReflectionTypeMetadataName is null
|
||||||
? 0
|
? 0
|
||||||
: StringComparer.Ordinal.GetHashCode(ReflectionTypeMetadataName));
|
: StringComparer.Ordinal.GetHashCode(ReflectionTypeMetadataName));
|
||||||
|
hashCode = (hashCode * 397) ^
|
||||||
|
(ReflectionFallbackHandlerTypeDisplayName is null
|
||||||
|
? 0
|
||||||
|
: StringComparer.Ordinal.GetHashCode(ReflectionFallbackHandlerTypeDisplayName));
|
||||||
hashCode = (hashCode * 397) ^
|
hashCode = (hashCode * 397) ^
|
||||||
(ReflectionFallbackHandlerTypeMetadataName is null
|
(ReflectionFallbackHandlerTypeMetadataName is null
|
||||||
? 0
|
? 0
|
||||||
@ -280,5 +316,7 @@ public sealed partial class CqrsHandlerRegistryGenerator
|
|||||||
|
|
||||||
private readonly record struct GenerationEnvironment(
|
private readonly record struct GenerationEnvironment(
|
||||||
bool GenerationEnabled,
|
bool GenerationEnabled,
|
||||||
bool SupportsReflectionFallbackAttribute);
|
bool SupportsNamedReflectionFallbackTypes,
|
||||||
|
bool SupportsDirectReflectionFallbackTypes,
|
||||||
|
bool SupportsMultipleReflectionFallbackAttributes);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,31 +8,29 @@ public sealed partial class CqrsHandlerRegistryGenerator
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 生成程序集级 CQRS handler 注册器源码。
|
/// 生成程序集级 CQRS handler 注册器源码。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="generationEnvironment">
|
|
||||||
/// 当前轮次的生成环境,用于决定 runtime 是否提供 <c>CqrsReflectionFallbackAttribute</c> 契约,以及是否需要在输出中发射对应的程序集级元数据。
|
|
||||||
/// </param>
|
|
||||||
/// <param name="registrations">
|
/// <param name="registrations">
|
||||||
/// 已整理并排序的 handler 注册描述。方法会据此生成 <c>CqrsHandlerRegistry.g.cs</c>,其中包含直接注册、实现类型反射注册、精确运行时类型查找等分支。
|
/// 已整理并排序的 handler 注册描述。方法会据此生成 <c>CqrsHandlerRegistry.g.cs</c>,其中包含直接注册、实现类型反射注册、精确运行时类型查找等分支。
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <param name="fallbackHandlerTypeMetadataNames">
|
/// <param name="reflectionFallbackEmission">
|
||||||
/// 仍需依赖程序集级 reflection fallback 元数据恢复的 handler 元数据名称集合。
|
/// 当前轮次选定的程序集级 reflection fallback 元数据发射策略。
|
||||||
/// 调用方必须先确保:若该集合非空,则 <paramref name="generationEnvironment" /> 已声明支持对应的 fallback attribute 契约;
|
/// 调用方必须先确保:若该策略包含 fallback handlers,则当前 runtime 已声明支持对应的 fallback attribute 契约;
|
||||||
/// 否则应在进入本方法前报告诊断并放弃生成,而不是输出会静默漏注册的半成品注册器。
|
/// 否则应在进入本方法前报告诊断并放弃生成,而不是输出会静默漏注册的半成品注册器。
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <returns>完整的注册器源代码文本。</returns>
|
/// <returns>完整的注册器源代码文本。</returns>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// 当 <paramref name="fallbackHandlerTypeMetadataNames" /> 为空时,输出只包含程序集级 <c>CqrsHandlerRegistryAttribute</c> 和注册器实现。
|
/// 当 <paramref name="reflectionFallbackEmission" /> 不包含任何 fallback handlers 时,
|
||||||
/// 当其非空且 runtime 合同可用时,输出还会附带程序集级 <c>CqrsReflectionFallbackAttribute</c>,让运行时补齐生成阶段无法精确表达的剩余 handler。
|
/// 输出只包含程序集级 <c>CqrsHandlerRegistryAttribute</c> 和注册器实现。
|
||||||
|
/// 当其包含 fallback handlers 且 runtime 合同可用时,输出还会附带一个或多个程序集级
|
||||||
|
/// <c>CqrsReflectionFallbackAttribute</c>,让运行时补齐生成阶段无法精确表达的剩余 handler。
|
||||||
/// 该方法本身不报告诊断;“fallback 必需但 runtime 契约缺失”的错误由调用方在进入本方法前处理。
|
/// 该方法本身不报告诊断;“fallback 必需但 runtime 契约缺失”的错误由调用方在进入本方法前处理。
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
private static string GenerateSource(
|
private static string GenerateSource(
|
||||||
GenerationEnvironment generationEnvironment,
|
|
||||||
IReadOnlyList<ImplementationRegistrationSpec> registrations,
|
IReadOnlyList<ImplementationRegistrationSpec> registrations,
|
||||||
IReadOnlyList<string> fallbackHandlerTypeMetadataNames)
|
ReflectionFallbackEmissionSpec reflectionFallbackEmission)
|
||||||
{
|
{
|
||||||
var sourceShape = CreateGeneratedRegistrySourceShape(registrations);
|
var sourceShape = CreateGeneratedRegistrySourceShape(registrations);
|
||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
AppendGeneratedSourcePreamble(builder, generationEnvironment, fallbackHandlerTypeMetadataNames);
|
AppendGeneratedSourcePreamble(builder, reflectionFallbackEmission);
|
||||||
AppendGeneratedRegistryType(builder, registrations, sourceShape);
|
AppendGeneratedRegistryType(builder, registrations, sourceShape);
|
||||||
return builder.ToString();
|
return builder.ToString();
|
||||||
}
|
}
|
||||||
@ -66,19 +64,17 @@ public sealed partial class CqrsHandlerRegistryGenerator
|
|||||||
/// 发射生成文件头、nullable 指令以及注册器所需的程序集级元数据特性。
|
/// 发射生成文件头、nullable 指令以及注册器所需的程序集级元数据特性。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="builder">生成源码构造器。</param>
|
/// <param name="builder">生成源码构造器。</param>
|
||||||
/// <param name="generationEnvironment">当前轮次的生成环境。</param>
|
/// <param name="reflectionFallbackEmission">需要写入程序集级 reflection fallback 特性的元数据策略。</param>
|
||||||
/// <param name="fallbackHandlerTypeMetadataNames">需要程序集级 reflection fallback 的 handler 元数据名称。</param>
|
|
||||||
private static void AppendGeneratedSourcePreamble(
|
private static void AppendGeneratedSourcePreamble(
|
||||||
StringBuilder builder,
|
StringBuilder builder,
|
||||||
GenerationEnvironment generationEnvironment,
|
ReflectionFallbackEmissionSpec reflectionFallbackEmission)
|
||||||
IReadOnlyList<string> fallbackHandlerTypeMetadataNames)
|
|
||||||
{
|
{
|
||||||
builder.AppendLine("// <auto-generated />");
|
builder.AppendLine("// <auto-generated />");
|
||||||
builder.AppendLine("#nullable enable");
|
builder.AppendLine("#nullable enable");
|
||||||
builder.AppendLine();
|
builder.AppendLine();
|
||||||
if (generationEnvironment.SupportsReflectionFallbackAttribute && fallbackHandlerTypeMetadataNames.Count > 0)
|
if (reflectionFallbackEmission.HasFallbackHandlers)
|
||||||
{
|
{
|
||||||
AppendReflectionFallbackAttribute(builder, fallbackHandlerTypeMetadataNames);
|
AppendReflectionFallbackAttributes(builder, reflectionFallbackEmission);
|
||||||
builder.AppendLine();
|
builder.AppendLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,25 +91,53 @@ public sealed partial class CqrsHandlerRegistryGenerator
|
|||||||
/// 发射程序集级 reflection fallback 元数据特性,供运行时补齐生成阶段无法精确表达的 handler。
|
/// 发射程序集级 reflection fallback 元数据特性,供运行时补齐生成阶段无法精确表达的 handler。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="builder">生成源码构造器。</param>
|
/// <param name="builder">生成源码构造器。</param>
|
||||||
/// <param name="fallbackHandlerTypeMetadataNames">需要写入特性的 handler 元数据名称。</param>
|
/// <param name="reflectionFallbackEmission">需要写入特性的 fallback 元数据策略。</param>
|
||||||
|
private static void AppendReflectionFallbackAttributes(
|
||||||
|
StringBuilder builder,
|
||||||
|
ReflectionFallbackEmissionSpec reflectionFallbackEmission)
|
||||||
|
{
|
||||||
|
for (var index = 0; index < reflectionFallbackEmission.Attributes.Length; index++)
|
||||||
|
{
|
||||||
|
if (index > 0)
|
||||||
|
{
|
||||||
|
builder.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
AppendReflectionFallbackAttribute(builder, reflectionFallbackEmission.Attributes[index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 发射单个程序集级 reflection fallback 元数据特性实例。
|
||||||
|
/// </summary>
|
||||||
private static void AppendReflectionFallbackAttribute(
|
private static void AppendReflectionFallbackAttribute(
|
||||||
StringBuilder builder,
|
StringBuilder builder,
|
||||||
IReadOnlyList<string> fallbackHandlerTypeMetadataNames)
|
ReflectionFallbackAttributeEmissionSpec attributeEmission)
|
||||||
{
|
{
|
||||||
builder.Append("[assembly: global::");
|
builder.Append("[assembly: global::");
|
||||||
builder.Append(CqrsRuntimeNamespace);
|
builder.Append(CqrsRuntimeNamespace);
|
||||||
builder.Append(".CqrsReflectionFallbackAttribute(");
|
builder.Append(".CqrsReflectionFallbackAttribute(");
|
||||||
for (var index = 0; index < fallbackHandlerTypeMetadataNames.Count; index++)
|
|
||||||
|
for (var index = 0; index < attributeEmission.Values.Length; index++)
|
||||||
{
|
{
|
||||||
if (index > 0)
|
if (index > 0)
|
||||||
builder.Append(", ");
|
builder.Append(", ");
|
||||||
|
|
||||||
builder.Append('"');
|
if (attributeEmission.EmitDirectTypeReferences)
|
||||||
builder.Append(EscapeStringLiteral(fallbackHandlerTypeMetadataNames[index]));
|
{
|
||||||
builder.Append('"');
|
builder.Append("typeof(");
|
||||||
|
builder.Append(attributeEmission.Values[index]);
|
||||||
|
builder.Append(')');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
builder.Append('"');
|
||||||
|
builder.Append(EscapeStringLiteral(attributeEmission.Values[index]));
|
||||||
|
builder.Append('"');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.AppendLine(")]");
|
builder.Append(")]");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -56,6 +56,8 @@ public sealed partial class CqrsHandlerRegistryGenerator : IIncrementalGenerator
|
|||||||
|
|
||||||
private static GenerationEnvironment CreateGenerationEnvironment(Compilation compilation)
|
private static GenerationEnvironment CreateGenerationEnvironment(Compilation compilation)
|
||||||
{
|
{
|
||||||
|
var reflectionFallbackAttributeType =
|
||||||
|
compilation.GetTypeByMetadataName(CqrsReflectionFallbackAttributeMetadataName);
|
||||||
var generationEnabled = compilation.GetTypeByMetadataName(IRequestHandlerMetadataName) is not null &&
|
var generationEnabled = compilation.GetTypeByMetadataName(IRequestHandlerMetadataName) is not null &&
|
||||||
compilation.GetTypeByMetadataName(INotificationHandlerMetadataName) is not null &&
|
compilation.GetTypeByMetadataName(INotificationHandlerMetadataName) is not null &&
|
||||||
compilation.GetTypeByMetadataName(IStreamRequestHandlerMetadataName) is not null &&
|
compilation.GetTypeByMetadataName(IStreamRequestHandlerMetadataName) is not null &&
|
||||||
@ -64,10 +66,26 @@ public sealed partial class CqrsHandlerRegistryGenerator : IIncrementalGenerator
|
|||||||
CqrsHandlerRegistryAttributeMetadataName) is not null &&
|
CqrsHandlerRegistryAttributeMetadataName) is not null &&
|
||||||
compilation.GetTypeByMetadataName(ILoggerMetadataName) is not null &&
|
compilation.GetTypeByMetadataName(ILoggerMetadataName) is not null &&
|
||||||
compilation.GetTypeByMetadataName(IServiceCollectionMetadataName) is not null;
|
compilation.GetTypeByMetadataName(IServiceCollectionMetadataName) is not null;
|
||||||
var supportsReflectionFallbackAttribute =
|
var stringType = compilation.GetSpecialType(SpecialType.System_String);
|
||||||
compilation.GetTypeByMetadataName(CqrsReflectionFallbackAttributeMetadataName) is not null;
|
var typeType = compilation.GetTypeByMetadataName("System.Type");
|
||||||
|
var supportsNamedReflectionFallbackTypes = reflectionFallbackAttributeType is not null &&
|
||||||
|
HasParamsArrayConstructor(
|
||||||
|
reflectionFallbackAttributeType,
|
||||||
|
stringType);
|
||||||
|
var supportsDirectReflectionFallbackTypes = reflectionFallbackAttributeType is not null &&
|
||||||
|
typeType is not null &&
|
||||||
|
HasParamsArrayConstructor(
|
||||||
|
reflectionFallbackAttributeType,
|
||||||
|
typeType);
|
||||||
|
var supportsMultipleReflectionFallbackAttributes = reflectionFallbackAttributeType is not null &&
|
||||||
|
SupportsMultipleAttributeInstances(
|
||||||
|
reflectionFallbackAttributeType);
|
||||||
|
|
||||||
return new GenerationEnvironment(generationEnabled, supportsReflectionFallbackAttribute);
|
return new GenerationEnvironment(
|
||||||
|
generationEnabled,
|
||||||
|
supportsNamedReflectionFallbackTypes,
|
||||||
|
supportsDirectReflectionFallbackTypes,
|
||||||
|
supportsMultipleReflectionFallbackAttributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsHandlerCandidate(SyntaxNode node)
|
private static bool IsHandlerCandidate(SyntaxNode node)
|
||||||
@ -130,6 +148,7 @@ public sealed partial class CqrsHandlerRegistryGenerator : IIncrementalGenerator
|
|||||||
ImmutableArray.CreateBuilder<ReflectedImplementationRegistrationSpec>(handlerInterfaces.Length);
|
ImmutableArray.CreateBuilder<ReflectedImplementationRegistrationSpec>(handlerInterfaces.Length);
|
||||||
var preciseReflectedRegistrations =
|
var preciseReflectedRegistrations =
|
||||||
ImmutableArray.CreateBuilder<PreciseReflectedRegistrationSpec>(handlerInterfaces.Length);
|
ImmutableArray.CreateBuilder<PreciseReflectedRegistrationSpec>(handlerInterfaces.Length);
|
||||||
|
string? reflectionFallbackHandlerTypeDisplayName = null;
|
||||||
string? reflectionFallbackHandlerTypeMetadataName = null;
|
string? reflectionFallbackHandlerTypeMetadataName = null;
|
||||||
foreach (var handlerInterface in handlerInterfaces)
|
foreach (var handlerInterface in handlerInterfaces)
|
||||||
{
|
{
|
||||||
@ -151,6 +170,9 @@ public sealed partial class CqrsHandlerRegistryGenerator : IIncrementalGenerator
|
|||||||
// If a future Roslyn type shape still slips through this net, keep the generator conservative:
|
// If a future Roslyn type shape still slips through this net, keep the generator conservative:
|
||||||
// preserve the static registrations we do understand, and let the runtime recover the remaining
|
// preserve the static registrations we do understand, and let the runtime recover the remaining
|
||||||
// interfaces via the existing assembly-level targeted reflection fallback contract.
|
// interfaces via the existing assembly-level targeted reflection fallback contract.
|
||||||
|
if (canReferenceImplementation)
|
||||||
|
reflectionFallbackHandlerTypeDisplayName ??= implementationTypeDisplayName;
|
||||||
|
|
||||||
reflectionFallbackHandlerTypeMetadataName ??= GetReflectionTypeMetadataName(type);
|
reflectionFallbackHandlerTypeMetadataName ??= GetReflectionTypeMetadataName(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,6 +183,7 @@ public sealed partial class CqrsHandlerRegistryGenerator : IIncrementalGenerator
|
|||||||
reflectedImplementationRegistrations.ToImmutable(),
|
reflectedImplementationRegistrations.ToImmutable(),
|
||||||
preciseReflectedRegistrations.ToImmutable(),
|
preciseReflectedRegistrations.ToImmutable(),
|
||||||
canReferenceImplementation ? null : GetReflectionTypeMetadataName(type),
|
canReferenceImplementation ? null : GetReflectionTypeMetadataName(type),
|
||||||
|
reflectionFallbackHandlerTypeDisplayName,
|
||||||
reflectionFallbackHandlerTypeMetadataName);
|
reflectionFallbackHandlerTypeMetadataName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -259,57 +282,71 @@ public sealed partial class CqrsHandlerRegistryGenerator : IIncrementalGenerator
|
|||||||
if (registrations.Count == 0)
|
if (registrations.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var fallbackHandlerTypeMetadataNames = registrations
|
var reflectionFallbackEmission = CreateReflectionFallbackEmissionSpec(generationEnvironment, registrations);
|
||||||
.Select(static registration => registration.ReflectionFallbackHandlerTypeMetadataName)
|
|
||||||
.Where(static typeMetadataName => !string.IsNullOrWhiteSpace(typeMetadataName))
|
|
||||||
.Distinct(StringComparer.Ordinal)
|
|
||||||
.Cast<string>()
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
if (!CanEmitGeneratedRegistry(
|
if (!CanEmitGeneratedRegistry(
|
||||||
generationEnvironment.SupportsReflectionFallbackAttribute,
|
generationEnvironment,
|
||||||
fallbackHandlerTypeMetadataNames.Length))
|
reflectionFallbackEmission))
|
||||||
{
|
{
|
||||||
ReportMissingReflectionFallbackContractDiagnostic(
|
ReportMissingReflectionFallbackContractDiagnostic(
|
||||||
context,
|
context,
|
||||||
fallbackHandlerTypeMetadataNames);
|
registrations);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
context.AddSource(
|
context.AddSource(
|
||||||
HintName,
|
HintName,
|
||||||
GenerateSource(generationEnvironment, registrations, fallbackHandlerTypeMetadataNames));
|
GenerateSource(registrations, reflectionFallbackEmission));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 判断当前轮次是否允许输出生成注册器。
|
/// 判断当前轮次是否允许输出生成注册器。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="supportsReflectionFallbackAttribute">
|
/// <param name="generationEnvironment">当前轮次可用的 fallback 合同能力。</param>
|
||||||
/// runtime 合同中是否存在 <c>CqrsReflectionFallbackAttribute</c>,以承载生成器无法静态精确表达的 handler 回退元数据。
|
/// <param name="reflectionFallbackEmission">当前轮次选定的 fallback 元数据发射策略。</param>
|
||||||
/// </param>
|
|
||||||
/// <param name="fallbackHandlerTypeCount">
|
|
||||||
/// 当前轮次需要依赖程序集级 reflection fallback 元数据恢复的 handler 数量。
|
|
||||||
/// </param>
|
|
||||||
/// <returns>
|
/// <returns>
|
||||||
/// 当没有 handler 依赖 fallback,或 runtime 已提供承载该元数据的特性契约时返回 <see langword="true" />;
|
/// 当没有 handler 依赖 fallback,或 runtime 已提供本轮策略所需的元数据承载重载时返回 <see langword="true" />;
|
||||||
/// 否则返回 <see langword="false" />,调用方必须放弃生成以避免输出会静默漏注册的半成品注册器。
|
/// 否则返回 <see langword="false" />,调用方必须放弃生成以避免输出会静默漏注册的半成品注册器。
|
||||||
/// </returns>
|
/// </returns>
|
||||||
private static bool CanEmitGeneratedRegistry(
|
private static bool CanEmitGeneratedRegistry(
|
||||||
bool supportsReflectionFallbackAttribute,
|
GenerationEnvironment generationEnvironment,
|
||||||
int fallbackHandlerTypeCount)
|
ReflectionFallbackEmissionSpec reflectionFallbackEmission)
|
||||||
{
|
{
|
||||||
return fallbackHandlerTypeCount == 0 || supportsReflectionFallbackAttribute;
|
if (!reflectionFallbackEmission.HasFallbackHandlers)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
foreach (var attributeEmission in reflectionFallbackEmission.Attributes)
|
||||||
|
{
|
||||||
|
if (attributeEmission.EmitDirectTypeReferences)
|
||||||
|
{
|
||||||
|
if (!generationEnvironment.SupportsDirectReflectionFallbackTypes)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!generationEnvironment.SupportsNamedReflectionFallbackTypes)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 报告当前轮次因缺少 fallback 元数据承载契约而无法安全生成注册器的诊断。
|
/// 报告当前轮次因缺少 fallback 元数据承载契约而无法安全生成注册器的诊断。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="context">源生产上下文。</param>
|
/// <param name="context">源生产上下文。</param>
|
||||||
/// <param name="fallbackHandlerTypeMetadataNames">需要通过程序集级 reflection fallback 元数据恢复的 handler 元数据名称。</param>
|
/// <param name="registrations">当前轮次汇总后的 handler 注册描述。</param>
|
||||||
private static void ReportMissingReflectionFallbackContractDiagnostic(
|
private static void ReportMissingReflectionFallbackContractDiagnostic(
|
||||||
SourceProductionContext context,
|
SourceProductionContext context,
|
||||||
IReadOnlyList<string> fallbackHandlerTypeMetadataNames)
|
IReadOnlyList<ImplementationRegistrationSpec> registrations)
|
||||||
{
|
{
|
||||||
|
var fallbackHandlerTypeMetadataNames = registrations
|
||||||
|
.Select(static registration => registration.ReflectionFallbackHandlerTypeMetadataName)
|
||||||
|
.Where(static typeMetadataName => !string.IsNullOrWhiteSpace(typeMetadataName))
|
||||||
|
.Distinct(StringComparer.Ordinal)
|
||||||
|
.Cast<string>()
|
||||||
|
.ToArray();
|
||||||
var handlerList = string.Join(
|
var handlerList = string.Join(
|
||||||
", ",
|
", ",
|
||||||
fallbackHandlerTypeMetadataNames.OrderBy(static name => name, StringComparer.Ordinal));
|
fallbackHandlerTypeMetadataNames.OrderBy(static name => name, StringComparer.Ordinal));
|
||||||
@ -346,6 +383,7 @@ public sealed partial class CqrsHandlerRegistryGenerator : IIncrementalGenerator
|
|||||||
candidate.ReflectedImplementationRegistrations,
|
candidate.ReflectedImplementationRegistrations,
|
||||||
candidate.PreciseReflectedRegistrations,
|
candidate.PreciseReflectedRegistrations,
|
||||||
candidate.ReflectionTypeMetadataName,
|
candidate.ReflectionTypeMetadataName,
|
||||||
|
candidate.ReflectionFallbackHandlerTypeDisplayName,
|
||||||
candidate.ReflectionFallbackHandlerTypeMetadataName));
|
candidate.ReflectionFallbackHandlerTypeMetadataName));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -354,6 +392,224 @@ public sealed partial class CqrsHandlerRegistryGenerator : IIncrementalGenerator
|
|||||||
return registrations;
|
return registrations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 选择本轮生成应采用的 fallback 元数据发射策略。
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 当所有 fallback handlers 都能被生成代码直接引用,且 runtime 暴露了 <c>params Type[]</c> 重载时,
|
||||||
|
/// 优先输出单个直接 <see cref="Type" /> 元数据特性;当 runtime 同时支持多个特性实例时,
|
||||||
|
/// mixed 场景会拆分成“直接 <see cref="Type" /> + 字符串类型名”两类特性;其余场景统一回退到字符串元数据。
|
||||||
|
/// </remarks>
|
||||||
|
private static ReflectionFallbackEmissionSpec CreateReflectionFallbackEmissionSpec(
|
||||||
|
GenerationEnvironment generationEnvironment,
|
||||||
|
IReadOnlyList<ImplementationRegistrationSpec> registrations)
|
||||||
|
{
|
||||||
|
var fallbackCandidates = CollectFallbackCandidates(registrations);
|
||||||
|
if (fallbackCandidates.Count == 0)
|
||||||
|
return new ReflectionFallbackEmissionSpec(ImmutableArray<ReflectionFallbackAttributeEmissionSpec>.Empty);
|
||||||
|
|
||||||
|
var fallbackHandlerTypeMetadataNames = GetSortedFallbackMetadataNames(fallbackCandidates);
|
||||||
|
var fallbackHandlerTypeDisplayNames = GetSortedDirectFallbackDisplayNames(fallbackCandidates);
|
||||||
|
|
||||||
|
if (TryCreateDirectFallbackEmission(
|
||||||
|
generationEnvironment,
|
||||||
|
fallbackHandlerTypeDisplayNames,
|
||||||
|
fallbackHandlerTypeMetadataNames,
|
||||||
|
out var directFallbackEmission))
|
||||||
|
{
|
||||||
|
return directFallbackEmission;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryCreateMixedFallbackEmission(
|
||||||
|
generationEnvironment,
|
||||||
|
fallbackCandidates,
|
||||||
|
fallbackHandlerTypeDisplayNames,
|
||||||
|
out var mixedFallbackEmission))
|
||||||
|
{
|
||||||
|
return mixedFallbackEmission;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CreateNamedFallbackEmission(fallbackHandlerTypeMetadataNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 收集本轮所有 fallback handlers 的稳定元数据名和可选直接引用显示名。
|
||||||
|
/// </summary>
|
||||||
|
private static Dictionary<string, string?> CollectFallbackCandidates(
|
||||||
|
IReadOnlyList<ImplementationRegistrationSpec> registrations)
|
||||||
|
{
|
||||||
|
var fallbackCandidates = new Dictionary<string, string?>(StringComparer.Ordinal);
|
||||||
|
foreach (var registration in registrations)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(registration.ReflectionFallbackHandlerTypeMetadataName))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
fallbackCandidates[registration.ReflectionFallbackHandlerTypeMetadataName!] =
|
||||||
|
registration.ReflectionFallbackHandlerTypeDisplayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fallbackCandidates;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取按稳定顺序排列的 fallback handler 元数据名称集合。
|
||||||
|
/// </summary>
|
||||||
|
private static ImmutableArray<string> GetSortedFallbackMetadataNames(
|
||||||
|
IReadOnlyDictionary<string, string?> fallbackCandidates)
|
||||||
|
{
|
||||||
|
return fallbackCandidates.Keys
|
||||||
|
.OrderBy(static metadataName => metadataName, StringComparer.Ordinal)
|
||||||
|
.ToImmutableArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取按稳定顺序排列的可直接引用 fallback handler 显示名集合。
|
||||||
|
/// </summary>
|
||||||
|
private static ImmutableArray<string> GetSortedDirectFallbackDisplayNames(
|
||||||
|
IReadOnlyDictionary<string, string?> fallbackCandidates)
|
||||||
|
{
|
||||||
|
return fallbackCandidates.Values
|
||||||
|
.Where(static typeDisplayName => !string.IsNullOrWhiteSpace(typeDisplayName))
|
||||||
|
.Cast<string>()
|
||||||
|
.OrderBy(static typeDisplayName => typeDisplayName, StringComparer.Ordinal)
|
||||||
|
.ToImmutableArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当全部 fallback handlers 都可直接引用时,尝试创建直接 <see cref="Type" /> 元数据发射策略。
|
||||||
|
/// </summary>
|
||||||
|
private static bool TryCreateDirectFallbackEmission(
|
||||||
|
GenerationEnvironment generationEnvironment,
|
||||||
|
ImmutableArray<string> fallbackHandlerTypeDisplayNames,
|
||||||
|
ImmutableArray<string> fallbackHandlerTypeMetadataNames,
|
||||||
|
out ReflectionFallbackEmissionSpec emission)
|
||||||
|
{
|
||||||
|
if (generationEnvironment.SupportsDirectReflectionFallbackTypes &&
|
||||||
|
fallbackHandlerTypeDisplayNames.Length == fallbackHandlerTypeMetadataNames.Length)
|
||||||
|
{
|
||||||
|
emission = new ReflectionFallbackEmissionSpec(
|
||||||
|
[
|
||||||
|
new ReflectionFallbackAttributeEmissionSpec(
|
||||||
|
EmitDirectTypeReferences: true,
|
||||||
|
fallbackHandlerTypeDisplayNames)
|
||||||
|
]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
emission = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当 runtime 允许多个 fallback 特性实例时,尝试为 mixed 场景拆分直接 <see cref="Type" /> 与字符串元数据。
|
||||||
|
/// </summary>
|
||||||
|
private static bool TryCreateMixedFallbackEmission(
|
||||||
|
GenerationEnvironment generationEnvironment,
|
||||||
|
IReadOnlyDictionary<string, string?> fallbackCandidates,
|
||||||
|
ImmutableArray<string> fallbackHandlerTypeDisplayNames,
|
||||||
|
out ReflectionFallbackEmissionSpec emission)
|
||||||
|
{
|
||||||
|
if (!generationEnvironment.SupportsDirectReflectionFallbackTypes ||
|
||||||
|
!generationEnvironment.SupportsNamedReflectionFallbackTypes ||
|
||||||
|
!generationEnvironment.SupportsMultipleReflectionFallbackAttributes ||
|
||||||
|
fallbackHandlerTypeDisplayNames.Length == 0)
|
||||||
|
{
|
||||||
|
emission = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var namedOnlyFallbackMetadataNames = fallbackCandidates
|
||||||
|
.Where(static pair => string.IsNullOrWhiteSpace(pair.Value))
|
||||||
|
.Select(static pair => pair.Key)
|
||||||
|
.OrderBy(static metadataName => metadataName, StringComparer.Ordinal)
|
||||||
|
.ToImmutableArray();
|
||||||
|
|
||||||
|
if (namedOnlyFallbackMetadataNames.Length == 0)
|
||||||
|
{
|
||||||
|
emission = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
emission = new ReflectionFallbackEmissionSpec(
|
||||||
|
[
|
||||||
|
new ReflectionFallbackAttributeEmissionSpec(
|
||||||
|
EmitDirectTypeReferences: true,
|
||||||
|
fallbackHandlerTypeDisplayNames),
|
||||||
|
new ReflectionFallbackAttributeEmissionSpec(
|
||||||
|
EmitDirectTypeReferences: false,
|
||||||
|
namedOnlyFallbackMetadataNames)
|
||||||
|
]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建统一的字符串 fallback 元数据发射策略。
|
||||||
|
/// </summary>
|
||||||
|
private static ReflectionFallbackEmissionSpec CreateNamedFallbackEmission(
|
||||||
|
ImmutableArray<string> fallbackHandlerTypeMetadataNames)
|
||||||
|
{
|
||||||
|
return new ReflectionFallbackEmissionSpec(
|
||||||
|
[
|
||||||
|
new ReflectionFallbackAttributeEmissionSpec(
|
||||||
|
EmitDirectTypeReferences: false,
|
||||||
|
fallbackHandlerTypeMetadataNames)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 判断目标特性是否暴露了指定元素类型的 <c>params T[]</c> 构造函数。
|
||||||
|
/// </summary>
|
||||||
|
private static bool HasParamsArrayConstructor(INamedTypeSymbol attributeType, ITypeSymbol elementType)
|
||||||
|
{
|
||||||
|
foreach (var constructor in attributeType.InstanceConstructors)
|
||||||
|
{
|
||||||
|
if (constructor.Parameters.Length != 1)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var parameter = constructor.Parameters[0];
|
||||||
|
if (!parameter.IsParams)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (parameter.Type is IArrayTypeSymbol { Rank: 1 } arrayType &&
|
||||||
|
SymbolEqualityComparer.Default.Equals(arrayType.ElementType, elementType))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 判断目标特性的 <see cref="AttributeUsageAttribute" /> 是否允许在同一程序集上声明多个实例。
|
||||||
|
/// </summary>
|
||||||
|
private static bool SupportsMultipleAttributeInstances(INamedTypeSymbol attributeType)
|
||||||
|
{
|
||||||
|
foreach (var attribute in attributeType.GetAttributes())
|
||||||
|
{
|
||||||
|
if (!string.Equals(
|
||||||
|
attribute.AttributeClass?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),
|
||||||
|
"global::System.AttributeUsageAttribute",
|
||||||
|
StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var namedArgument in attribute.NamedArguments)
|
||||||
|
{
|
||||||
|
if (string.Equals(namedArgument.Key, "AllowMultiple", StringComparison.Ordinal) &&
|
||||||
|
namedArgument.Value.Value is bool allowMultiple)
|
||||||
|
{
|
||||||
|
return allowMultiple;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private static bool IsConcreteHandlerType(INamedTypeSymbol type)
|
private static bool IsConcreteHandlerType(INamedTypeSymbol type)
|
||||||
{
|
{
|
||||||
return type.TypeKind is TypeKind.Class or TypeKind.Struct &&
|
return type.TypeKind is TypeKind.Class or TypeKind.Struct &&
|
||||||
|
|||||||
@ -33,6 +33,7 @@
|
|||||||
- `Cqrs/CqrsHandlerRegistryGenerator.cs`
|
- `Cqrs/CqrsHandlerRegistryGenerator.cs`
|
||||||
|
|
||||||
它会在可以安全生成静态注册器时前移注册工作;对无法由生成代码直接引用的 handler,则通过 reflection fallback 元数据让运行时做定向补扫,而不是整程序集盲扫。
|
它会在可以安全生成静态注册器时前移注册工作;对无法由生成代码直接引用的 handler,则通过 reflection fallback 元数据让运行时做定向补扫,而不是整程序集盲扫。
|
||||||
|
当 fallback handler 本身仍可直接引用时,生成器会优先发射 `typeof(...)` 形式的 fallback 元数据;如果 runtime 允许同一程序集声明多个 fallback 特性实例,mixed 场景也会拆成 `Type` 元数据和字符串元数据两段,进一步减少运行时按类型名回查程序集的成本。
|
||||||
|
|
||||||
## 最小接入路径
|
## 最小接入路径
|
||||||
|
|
||||||
|
|||||||
@ -341,6 +341,64 @@ internal sealed class CqrsHandlerRegistrarTests
|
|||||||
generatedAssembly.Verify(static assembly => assembly.GetTypes(), Times.Never);
|
generatedAssembly.Verify(static assembly => assembly.GetTypes(), Times.Never);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 验证当程序集同时声明直接 <see cref="Type" /> fallback 与字符串名称 fallback 时,
|
||||||
|
/// 运行时会优先复用直接类型,并只对名称 fallback 做定向 <c>GetType(...)</c> 查找。
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void RegisterHandlers_Should_Use_Mixed_Fallback_Metadata_With_Targeted_Type_Lookups_Only_For_Named_Entries()
|
||||||
|
{
|
||||||
|
var generatedAssembly = new Mock<Assembly>();
|
||||||
|
generatedAssembly
|
||||||
|
.SetupGet(static assembly => assembly.FullName)
|
||||||
|
.Returns(ReflectionFallbackNotificationContainer.ReflectionOnlyHandlerType.Assembly.FullName);
|
||||||
|
generatedAssembly
|
||||||
|
.Setup(static assembly => assembly.GetCustomAttributes(typeof(CqrsHandlerRegistryAttribute), false))
|
||||||
|
.Returns([new CqrsHandlerRegistryAttribute(typeof(PartialGeneratedNotificationHandlerRegistry))]);
|
||||||
|
generatedAssembly
|
||||||
|
.Setup(static assembly => assembly.GetCustomAttributes(typeof(CqrsReflectionFallbackAttribute), false))
|
||||||
|
.Returns(
|
||||||
|
[
|
||||||
|
new CqrsReflectionFallbackAttribute(
|
||||||
|
ReflectionFallbackNotificationContainer.DirectFallbackHandlerType),
|
||||||
|
new CqrsReflectionFallbackAttribute(
|
||||||
|
ReflectionFallbackNotificationContainer.ReflectionOnlyHandlerType.FullName!)
|
||||||
|
]);
|
||||||
|
generatedAssembly
|
||||||
|
.Setup(static assembly => assembly.GetType(
|
||||||
|
ReflectionFallbackNotificationContainer.ReflectionOnlyHandlerType.FullName!,
|
||||||
|
false,
|
||||||
|
false))
|
||||||
|
.Returns(ReflectionFallbackNotificationContainer.ReflectionOnlyHandlerType);
|
||||||
|
|
||||||
|
var container = new MicrosoftDiContainer();
|
||||||
|
CqrsTestRuntime.RegisterHandlers(container, generatedAssembly.Object);
|
||||||
|
|
||||||
|
var registrations = container.GetServicesUnsafe
|
||||||
|
.Where(static descriptor =>
|
||||||
|
descriptor.ServiceType == typeof(INotificationHandler<GeneratedRegistryNotification>) &&
|
||||||
|
descriptor.ImplementationType is not null)
|
||||||
|
.Select(static descriptor => descriptor.ImplementationType!)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
Assert.That(
|
||||||
|
registrations,
|
||||||
|
Is.EqualTo(
|
||||||
|
[
|
||||||
|
typeof(GeneratedRegistryNotificationHandler),
|
||||||
|
ReflectionFallbackNotificationContainer.DirectFallbackHandlerType,
|
||||||
|
ReflectionFallbackNotificationContainer.ReflectionOnlyHandlerType
|
||||||
|
]));
|
||||||
|
|
||||||
|
generatedAssembly.Verify(
|
||||||
|
static assembly => assembly.GetType(
|
||||||
|
ReflectionFallbackNotificationContainer.ReflectionOnlyHandlerType.FullName!,
|
||||||
|
false,
|
||||||
|
false),
|
||||||
|
Times.Once);
|
||||||
|
generatedAssembly.Verify(static assembly => assembly.GetTypes(), Times.Never);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 验证同一程序集对象重复接入多个容器时,会复用已解析的 registry / fallback 元数据,
|
/// 验证同一程序集对象重复接入多个容器时,会复用已解析的 registry / fallback 元数据,
|
||||||
/// 而不是重复读取程序集级 attribute 或重复执行 type-name lookup。
|
/// 而不是重复读取程序集级 attribute 或重复执行 type-name lookup。
|
||||||
|
|||||||
@ -10,11 +10,34 @@ namespace GFramework.Cqrs.Tests.Cqrs;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed class ReflectionFallbackNotificationContainer
|
internal sealed class ReflectionFallbackNotificationContainer
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取可被直接引用、适合通过 <see cref="Type" /> 元数据补扫的处理器类型。
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// 可被生成注册器直接引用的 fallback 处理器类型,用于验证 runtime 会优先消费 <see cref="Type" /> 元数据。
|
||||||
|
/// </returns>
|
||||||
|
public static Type DirectFallbackHandlerType => typeof(DirectFallbackGeneratedRegistryNotificationHandler);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取仅能通过反射补扫接入的私有嵌套处理器类型。
|
/// 获取仅能通过反射补扫接入的私有嵌套处理器类型。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Type ReflectionOnlyHandlerType => typeof(ReflectionOnlyGeneratedRegistryNotificationHandler);
|
public static Type ReflectionOnlyHandlerType => typeof(ReflectionOnlyGeneratedRegistryNotificationHandler);
|
||||||
|
|
||||||
|
private sealed class DirectFallbackGeneratedRegistryNotificationHandler
|
||||||
|
: INotificationHandler<GeneratedRegistryNotification>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 处理测试通知。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="notification">通知实例。</param>
|
||||||
|
/// <param name="cancellationToken">取消令牌。</param>
|
||||||
|
/// <returns>已完成任务。</returns>
|
||||||
|
public ValueTask Handle(GeneratedRegistryNotification notification, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return ValueTask.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private sealed class ReflectionOnlyGeneratedRegistryNotificationHandler
|
private sealed class ReflectionOnlyGeneratedRegistryNotificationHandler
|
||||||
: INotificationHandler<GeneratedRegistryNotification>
|
: INotificationHandler<GeneratedRegistryNotification>
|
||||||
{
|
{
|
||||||
|
|||||||
@ -7,8 +7,10 @@ namespace GFramework.Cqrs;
|
|||||||
/// 该特性通常由源码生成器自动添加到消费端程序集。
|
/// 该特性通常由源码生成器自动添加到消费端程序集。
|
||||||
/// 当生成器只能安全生成部分 handler 映射时,运行时会先执行生成注册器,再补一次带去重的反射扫描,
|
/// 当生成器只能安全生成部分 handler 映射时,运行时会先执行生成注册器,再补一次带去重的反射扫描,
|
||||||
/// 以覆盖那些生成代码无法直接引用的 handler 类型。
|
/// 以覆盖那些生成代码无法直接引用的 handler 类型。
|
||||||
|
/// 允许同一程序集声明多个该特性实例,以便生成器把“可直接引用的 fallback handlers”
|
||||||
|
/// 和“仍需按名称恢复的 fallback handlers”拆成独立元数据块,进一步减少运行时字符串查找成本。
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[AttributeUsage(AttributeTargets.Assembly)]
|
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
|
||||||
public sealed class CqrsReflectionFallbackAttribute : Attribute
|
public sealed class CqrsReflectionFallbackAttribute : Attribute
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -1363,6 +1363,172 @@ public class CqrsHandlerRegistryGeneratorTests
|
|||||||
}
|
}
|
||||||
""";
|
""";
|
||||||
|
|
||||||
|
private const string AssemblyLevelDirectFallbackMetadataSource = """
|
||||||
|
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<TResponse> { }
|
||||||
|
public interface INotification { }
|
||||||
|
public interface IStreamRequest<TResponse> { }
|
||||||
|
|
||||||
|
public interface IRequestHandler<in TRequest, TResponse> where TRequest : IRequest<TResponse> { }
|
||||||
|
public interface INotificationHandler<in TNotification> where TNotification : INotification { }
|
||||||
|
public interface IStreamRequestHandler<in TRequest, out TResponse> where TRequest : IStreamRequest<TResponse> { }
|
||||||
|
}
|
||||||
|
|
||||||
|
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) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Assembly)]
|
||||||
|
public sealed class CqrsReflectionFallbackAttribute : Attribute
|
||||||
|
{
|
||||||
|
public CqrsReflectionFallbackAttribute(params string[] fallbackHandlerTypeNames) { }
|
||||||
|
|
||||||
|
public CqrsReflectionFallbackAttribute(params Type[] fallbackHandlerTypes) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace TestApp
|
||||||
|
{
|
||||||
|
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||||
|
|
||||||
|
public sealed class Container
|
||||||
|
{
|
||||||
|
private unsafe struct AlphaResponse
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe struct BetaResponse
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe sealed record AlphaRequest() : IRequest<delegate* unmanaged<AlphaResponse>>;
|
||||||
|
|
||||||
|
private unsafe sealed record BetaRequest() : IRequest<delegate* unmanaged<BetaResponse>>;
|
||||||
|
|
||||||
|
public unsafe sealed class BetaHandler : IRequestHandler<BetaRequest, delegate* unmanaged<BetaResponse>>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe sealed class AlphaHandler : IRequestHandler<AlphaRequest, delegate* unmanaged<AlphaResponse>>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
private const string AssemblyLevelMixedFallbackMetadataSource = """
|
||||||
|
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<TResponse> { }
|
||||||
|
public interface INotification { }
|
||||||
|
public interface IStreamRequest<TResponse> { }
|
||||||
|
|
||||||
|
public interface IRequestHandler<in TRequest, TResponse> where TRequest : IRequest<TResponse> { }
|
||||||
|
public interface INotificationHandler<in TNotification> where TNotification : INotification { }
|
||||||
|
public interface IStreamRequestHandler<in TRequest, out TResponse> where TRequest : IStreamRequest<TResponse> { }
|
||||||
|
}
|
||||||
|
|
||||||
|
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) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
|
||||||
|
public sealed class CqrsReflectionFallbackAttribute : Attribute
|
||||||
|
{
|
||||||
|
public CqrsReflectionFallbackAttribute(params string[] fallbackHandlerTypeNames) { }
|
||||||
|
|
||||||
|
public CqrsReflectionFallbackAttribute(params Type[] fallbackHandlerTypes) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace TestApp
|
||||||
|
{
|
||||||
|
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||||
|
|
||||||
|
public sealed class Container
|
||||||
|
{
|
||||||
|
private unsafe struct AlphaResponse
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe struct BetaResponse
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe sealed record AlphaRequest() : IRequest<delegate* unmanaged<AlphaResponse>>;
|
||||||
|
|
||||||
|
private unsafe sealed record BetaRequest() : IRequest<delegate* unmanaged<BetaResponse>>;
|
||||||
|
|
||||||
|
public unsafe sealed class AlphaHandler : IRequestHandler<AlphaRequest, delegate* unmanaged<AlphaResponse>>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe sealed class BetaHandler : IRequestHandler<BetaRequest, delegate* unmanaged<BetaResponse>>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 验证生成器会为当前程序集中的 request、notification 和 stream 处理器生成稳定顺序的注册器。
|
/// 验证生成器会为当前程序集中的 request、notification 和 stream 处理器生成稳定顺序的注册器。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -1690,6 +1856,116 @@ public class CqrsHandlerRegistryGeneratorTests
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 验证当所有 fallback handlers 本身都可直接引用,且 runtime 同时支持字符串与 <see cref="Type" /> 元数据承载时,
|
||||||
|
/// 生成器会优先发射直接 <c>typeof(...)</c> 的 fallback 特性,减少运行时按名称回查程序集类型。
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void
|
||||||
|
Emits_Direct_Type_Fallback_Metadata_When_All_Fallback_Handlers_Are_Referenceable_And_Runtime_Type_Contract_Is_Available()
|
||||||
|
{
|
||||||
|
var execution = ExecuteGenerator(
|
||||||
|
AssemblyLevelDirectFallbackMetadataSource,
|
||||||
|
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 generatedSource = execution.GeneratedSources[0].content;
|
||||||
|
|
||||||
|
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(
|
||||||
|
generatedSource,
|
||||||
|
Does.Contain(
|
||||||
|
"[assembly: global::GFramework.Cqrs.CqrsReflectionFallbackAttribute(typeof(global::TestApp.Container.AlphaHandler), typeof(global::TestApp.Container.BetaHandler))]"));
|
||||||
|
Assert.That(
|
||||||
|
generatedSource,
|
||||||
|
Does.Not.Contain(
|
||||||
|
"[assembly: global::GFramework.Cqrs.CqrsReflectionFallbackAttribute(\"TestApp.Container+AlphaHandler\", \"TestApp.Container+BetaHandler\")]"));
|
||||||
|
Assert.That(generatedSource, Does.Not.Contain("CqrsReflectionFallbackAttribute()"));
|
||||||
|
Assert.That(
|
||||||
|
CountOccurrences(
|
||||||
|
generatedSource,
|
||||||
|
"[assembly: global::GFramework.Cqrs.CqrsReflectionFallbackAttribute"),
|
||||||
|
Is.EqualTo(1));
|
||||||
|
Assert.That(
|
||||||
|
CountOccurrences(
|
||||||
|
generatedSource,
|
||||||
|
"[assembly: global::GFramework.Cqrs.CqrsReflectionFallbackAttribute(\""),
|
||||||
|
Is.Zero);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 验证当 runtime 允许多个 fallback 特性实例,且本轮 fallback 同时包含可直接引用与仅能按名称恢复的 handlers 时,
|
||||||
|
/// 生成器会拆分出直接 <see cref="Type" /> 与字符串两类元数据,避免 mixed 场景整体退回字符串 fallback。
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void
|
||||||
|
Emits_Mixed_Direct_Type_And_String_Fallback_Metadata_When_Runtime_Allows_Multiple_Fallback_Attributes()
|
||||||
|
{
|
||||||
|
var execution = ExecuteGenerator(
|
||||||
|
AssemblyLevelMixedFallbackMetadataSource,
|
||||||
|
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 generatedSource = execution.GeneratedSources[0].content;
|
||||||
|
|
||||||
|
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(
|
||||||
|
generatedSource,
|
||||||
|
Does.Contain(
|
||||||
|
"[assembly: global::GFramework.Cqrs.CqrsReflectionFallbackAttribute(typeof(global::TestApp.Container.AlphaHandler))]"));
|
||||||
|
Assert.That(
|
||||||
|
generatedSource,
|
||||||
|
Does.Contain(
|
||||||
|
"[assembly: global::GFramework.Cqrs.CqrsReflectionFallbackAttribute(\"TestApp.Container+BetaHandler\")]"));
|
||||||
|
Assert.That(generatedSource, Does.Not.Contain("CqrsReflectionFallbackAttribute()"));
|
||||||
|
Assert.That(
|
||||||
|
CountOccurrences(
|
||||||
|
generatedSource,
|
||||||
|
"[assembly: global::GFramework.Cqrs.CqrsReflectionFallbackAttribute"),
|
||||||
|
Is.EqualTo(2));
|
||||||
|
Assert.That(
|
||||||
|
CountOccurrences(
|
||||||
|
generatedSource,
|
||||||
|
"[assembly: global::GFramework.Cqrs.CqrsReflectionFallbackAttribute(\""),
|
||||||
|
Is.EqualTo(1));
|
||||||
|
Assert.That(
|
||||||
|
generatedSource,
|
||||||
|
Does.Contain(
|
||||||
|
"[assembly: global::GFramework.Cqrs.CqrsReflectionFallbackAttribute(typeof(global::TestApp.Container.AlphaHandler))]" +
|
||||||
|
Environment.NewLine +
|
||||||
|
"[assembly: global::GFramework.Cqrs.CqrsReflectionFallbackAttribute(\"TestApp.Container+BetaHandler\")]" +
|
||||||
|
Environment.NewLine +
|
||||||
|
"[assembly: global::GFramework.Cqrs.CqrsHandlerRegistryAttribute(typeof(global::GFramework.Generated.Cqrs.__GFrameworkGeneratedCqrsHandlerRegistry))]"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 验证日志字符串转义会覆盖换行、反斜杠和双引号,避免生成代码中的字符串字面量被意外截断。
|
/// 验证日志字符串转义会覆盖换行、反斜杠和双引号,避免生成代码中的字符串字面量被意外截断。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -1741,6 +2017,31 @@ public class CqrsHandlerRegistryGeneratorTests
|
|||||||
return execution.GeneratedSources[0].content;
|
return execution.GeneratedSources[0].content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 统计生成源码中某个固定片段的出现次数,用于锁定程序集级 fallback 特性的发射个数。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text">待统计的完整生成源码。</param>
|
||||||
|
/// <param name="value">需要计数的固定片段。</param>
|
||||||
|
/// <returns><paramref name="value" /> 在 <paramref name="text" /> 中出现的次数。</returns>
|
||||||
|
private static int CountOccurrences(string text, string value)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(value))
|
||||||
|
throw new ArgumentException("The search value must not be null or empty.", nameof(value));
|
||||||
|
|
||||||
|
var count = 0;
|
||||||
|
var startIndex = 0;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var nextIndex = text.IndexOf(value, startIndex, global::System.StringComparison.Ordinal);
|
||||||
|
if (nextIndex < 0)
|
||||||
|
return count;
|
||||||
|
|
||||||
|
count++;
|
||||||
|
startIndex = nextIndex + value.Length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 运行 CQRS handler registry generator,并返回生成输出及相关诊断。
|
/// 运行 CQRS handler registry generator,并返回生成输出及相关诊断。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -7,10 +7,14 @@ CQRS 迁移与收敛。
|
|||||||
|
|
||||||
## 当前恢复点
|
## 当前恢复点
|
||||||
|
|
||||||
- 恢复点编号:`CQRS-REWRITE-RP-050`
|
- 恢复点编号:`CQRS-REWRITE-RP-052`
|
||||||
- 当前阶段:`Phase 8`
|
- 当前阶段:`Phase 8`
|
||||||
- 当前焦点:
|
- 当前焦点:
|
||||||
- 当前功能历史已归档,active 跟踪仅保留 `Phase 8` 主线的恢复入口
|
- 当前功能历史已归档,active 跟踪仅保留 `Phase 8` 主线的恢复入口
|
||||||
|
- 已将 mixed fallback 场景进一步收敛:当 runtime 允许同一程序集声明多个 `CqrsReflectionFallbackAttribute` 实例时,generator 现会把可直接引用的 fallback handlers 与仅能按名称恢复的 fallback handlers 拆分发射
|
||||||
|
- `CqrsReflectionFallbackAttribute` 现允许多实例,以承载 `Type[]` 与字符串 fallback 元数据的组合输出
|
||||||
|
- 已将 generator 的程序集级 fallback 元数据进一步收敛:当全部 fallback handlers 都可直接引用且 runtime 暴露 `params Type[]` 合同时,生成器现优先发射 `typeof(...)` 形式的 fallback 元数据
|
||||||
|
- 当 runtime 不支持多实例 fallback 特性或缺少对应构造函数时,mixed fallback 场景仍会整体保守回退到字符串元数据,避免仅部分 handler 走 `Type[]` 时漏掉剩余需按名称恢复的 handlers
|
||||||
- 已完成 generated registry 激活路径收敛:`CqrsHandlerRegistrar` 现优先复用缓存工厂委托,避免重复 `ConstructorInfo.Invoke`
|
- 已完成 generated registry 激活路径收敛:`CqrsHandlerRegistrar` 现优先复用缓存工厂委托,避免重复 `ConstructorInfo.Invoke`
|
||||||
- 已补充私有无参构造 generated registry 的回归测试,确保兼容现有生成器产物
|
- 已补充私有无参构造 generated registry 的回归测试,确保兼容现有生成器产物
|
||||||
- 已修正 pointer / function pointer 泛型合同的错误覆盖:生成器不再为这两类类型发射 precise runtime type 重建代码
|
- 已修正 pointer / function pointer 泛型合同的错误覆盖:生成器不再为这两类类型发射 precise runtime type 重建代码
|
||||||
@ -51,6 +55,22 @@ CQRS 迁移与收敛。
|
|||||||
- `CqrsHandlerRegistrar` 现会在单次 reflection 注册流程开始时构建已注册 handler 映射索引
|
- `CqrsHandlerRegistrar` 现会在单次 reflection 注册流程开始时构建已注册 handler 映射索引
|
||||||
- 同一批注册中后续 duplicate handler mapping 不再重复线性扫描 `IServiceCollection`
|
- 同一批注册中后续 duplicate handler mapping 不再重复线性扫描 `IServiceCollection`
|
||||||
- `GFramework.Cqrs.Tests` 已补充“程序集返回重复 handler 类型时仍只注册一份映射”的回归
|
- `GFramework.Cqrs.Tests` 已补充“程序集返回重复 handler 类型时仍只注册一份映射”的回归
|
||||||
|
- `2026-04-29` 已完成一轮 generator fallback 元数据收敛:
|
||||||
|
- `CqrsHandlerRegistryGenerator` 现会探测 runtime 是否同时支持 `params string[]` 与 `params Type[]` 两类 `CqrsReflectionFallbackAttribute` 构造函数
|
||||||
|
- 当本轮 fallback handlers 全部可被生成代码直接引用时,生成器会优先发射 `typeof(...)` 形式的程序集级 fallback 元数据,减少运行时 `Assembly.GetType(...)` 回查
|
||||||
|
- 当 fallback handlers 中仍存在不能直接引用的实现类型时,生成器继续统一发射字符串元数据,避免 mixed 场景只恢复部分 handlers
|
||||||
|
- `GFramework.SourceGenerators.Tests` 已补充 runtime 同时暴露两类构造函数时优先选择直接 `Type` 元数据的回归
|
||||||
|
- `2026-04-29` 已完成一轮 mixed fallback 元数据拆分:
|
||||||
|
- `CqrsReflectionFallbackAttribute` 现显式允许 `AllowMultiple = true`
|
||||||
|
- `CqrsHandlerRegistryGenerator` 现会探测 runtime 是否允许多个 fallback 特性实例
|
||||||
|
- 当本轮 fallback 同时包含可直接引用与仅能按名称恢复的 handlers,且 runtime 同时支持 `Type[]`、`string[]` 和多实例特性时,生成器会拆分输出两段 fallback 元数据
|
||||||
|
- `GFramework.Cqrs.Tests` 已补充 mixed fallback metadata 回归,锁定 registrar 只对字符串条目执行定向 `Assembly.GetType(...)`
|
||||||
|
- `GFramework.SourceGenerators.Tests` 已补充 mixed fallback emission 回归,锁定 generator 会输出两个程序集级 fallback 特性实例而不是整体退回字符串
|
||||||
|
- `2026-04-29` 已重新执行 `$gframework-pr-review`:
|
||||||
|
- 当前分支对应 `PR #302`,状态为 `OPEN`
|
||||||
|
- latest reviewed commit 当前剩余 `3` 条 open AI review threads:`2` 条 Greptile、`1` 条 CodeRabbit
|
||||||
|
- 本地核对后确认 `dotnet-format` 仍只有 `Restore operation failed` 噪音,没有附带当前仍成立的文件级格式诊断
|
||||||
|
- 已按 review triage 修正 generator source preamble 的多实例 fallback 特性排版、移除死参数,并补强 mixed/direct fallback 发射回归断言与 XML 文档
|
||||||
- 当前主线优先级:
|
- 当前主线优先级:
|
||||||
- generator 覆盖面继续扩大
|
- generator 覆盖面继续扩大
|
||||||
- dispatch/invoker 反射占比继续下降
|
- dispatch/invoker 反射占比继续下降
|
||||||
@ -64,7 +84,6 @@ CQRS 迁移与收敛。
|
|||||||
|
|
||||||
## 活跃文档
|
## 活跃文档
|
||||||
|
|
||||||
- 模块拆分计划:`ai-plan/migration/CQRS_MODULE_SPLIT_PLAN.md`
|
|
||||||
- 历史跟踪归档:[cqrs-rewrite-history-through-rp043.md](../archive/todos/cqrs-rewrite-history-through-rp043.md)
|
- 历史跟踪归档:[cqrs-rewrite-history-through-rp043.md](../archive/todos/cqrs-rewrite-history-through-rp043.md)
|
||||||
- 历史 trace 归档:[cqrs-rewrite-history-through-rp043.md](../archive/traces/cqrs-rewrite-history-through-rp043.md)
|
- 历史 trace 归档:[cqrs-rewrite-history-through-rp043.md](../archive/traces/cqrs-rewrite-history-through-rp043.md)
|
||||||
|
|
||||||
@ -84,9 +103,39 @@ CQRS 迁移与收敛。
|
|||||||
- `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"`
|
- `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 去重路径
|
- 备注:`11/11` 测试通过;本轮覆盖 registrar 的 supported handler interface 缓存与 duplicate mapping 去重路径
|
||||||
|
- `dotnet build GFramework.Cqrs.SourceGenerators/GFramework.Cqrs.SourceGenerators.csproj -c Release`
|
||||||
|
- 结果:通过
|
||||||
|
- 备注:`0 warning / 0 error`
|
||||||
|
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests"`
|
||||||
|
- 结果:通过
|
||||||
|
- 备注:`17/17` 测试通过;本轮覆盖字符串 fallback 合同兼容路径与直接 `Type` fallback 元数据优先级
|
||||||
|
- `dotnet build GFramework.Cqrs/GFramework.Cqrs.csproj -c Release`
|
||||||
|
- 结果:通过
|
||||||
|
- 备注:`0 warning / 0 error`
|
||||||
|
- `dotnet build GFramework.Cqrs.SourceGenerators/GFramework.Cqrs.SourceGenerators.csproj -c Release`
|
||||||
|
- 结果:通过
|
||||||
|
- 备注:`0 warning / 0 error`
|
||||||
|
- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsHandlerRegistrarTests"`
|
||||||
|
- 结果:通过
|
||||||
|
- 备注:`13/13` 测试通过;本轮覆盖 mixed fallback metadata 的 registrar 消费路径
|
||||||
|
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests"`
|
||||||
|
- 结果:通过
|
||||||
|
- 备注:`18/18` 测试通过;本轮覆盖 mixed fallback metadata 的双特性发射路径
|
||||||
|
- `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --json-output /tmp/gframework-pr-review.json`
|
||||||
|
- 结果:通过
|
||||||
|
- 备注:确认当前分支对应 `PR #302`;latest head review 仍有 `3` 条 open AI threads,其中 MegaLinter 仅报告 `dotnet-format` restore failure 噪音
|
||||||
|
- `dotnet build GFramework.Cqrs.SourceGenerators/GFramework.Cqrs.SourceGenerators.csproj -c Release`
|
||||||
|
- 结果:通过
|
||||||
|
- 备注:`0 warning / 0 error`
|
||||||
|
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests"`
|
||||||
|
- 结果:通过
|
||||||
|
- 备注:`18/18` 测试通过;本轮直接覆盖 fallback preamble 排版与特性个数断言收紧
|
||||||
|
- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsHandlerRegistrarTests"`
|
||||||
|
- 结果:通过
|
||||||
|
- 备注:`13/13` 测试通过;本轮确认 mixed fallback metadata 的 registrar 消费路径未回归
|
||||||
|
|
||||||
## 下一步
|
## 下一步
|
||||||
|
|
||||||
1. 继续 `Phase 8` 主线,优先再找一个收益明确的 generator 覆盖缺口或 dispatch / invoker 反射收敛点继续推进
|
1. 继续 `Phase 8` 主线,优先再找一个收益明确的 generator 覆盖缺口,继续减少仍必须依赖字符串 fallback 元数据的 handler 类型形态
|
||||||
2. 若继续文档主线,优先再扫 `docs/zh-CN/api-reference` 与教程入口页,补齐仍过时的 CQRS API / 命名空间表述
|
2. 若继续文档主线,优先再扫 `docs/zh-CN/api-reference` 与教程入口页,补齐仍过时的 CQRS API / 命名空间表述
|
||||||
3. 若后续再出现新的 PR review 或 review thread 变化,再重新执行 `$gframework-pr-review` 作为独立验证步骤
|
3. 若后续再出现新的 PR review 或 review thread 变化,再重新执行 `$gframework-pr-review` 作为独立验证步骤
|
||||||
|
|||||||
@ -1,7 +1,84 @@
|
|||||||
# CQRS 重写迁移追踪
|
# CQRS 重写迁移追踪
|
||||||
|
|
||||||
|
## 2026-04-29
|
||||||
|
|
||||||
|
### 阶段:mixed fallback 元数据拆分(CQRS-REWRITE-RP-052)
|
||||||
|
|
||||||
|
- 延续 `gframework-batch-boot 50` 的 `Phase 8` 主线,本轮把上一批的“全部可直接引用 fallback handlers 走 `Type[]`”继续推进到 mixed 场景
|
||||||
|
- 先复核现状后确认:
|
||||||
|
- `CqrsHandlerRegistrar` 已天然支持读取多个 `CqrsReflectionFallbackAttribute` 实例
|
||||||
|
- 上一批真正阻止 mixed 场景继续收敛的点,是 runtime attribute 本身尚未开放多实例,以及 generator 只能二选一发射单个 fallback 特性
|
||||||
|
- 已在 `GFramework.Cqrs/CqrsReflectionFallbackAttribute.cs` 中将特性约束改为 `AllowMultiple = true`,并补充注释说明多个实例的用途
|
||||||
|
- 已在 `GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs` 中扩展 fallback 合同探测:
|
||||||
|
- 探测 runtime 是否支持 `params string[]`
|
||||||
|
- 探测 runtime 是否支持 `params Type[]`
|
||||||
|
- 探测 runtime 是否允许多个 `CqrsReflectionFallbackAttribute` 实例
|
||||||
|
- 已在 `CqrsHandlerRegistryGenerator.Models.cs` 与 `CqrsHandlerRegistryGenerator.SourceEmission.cs` 中重构 fallback 发射模型:
|
||||||
|
- fallback 元数据现在可表示为一个或多个程序集级特性实例
|
||||||
|
- 当 fallback handlers 全部可直接引用时,继续优先输出单个 `Type[]` 特性
|
||||||
|
- 当 fallback 同时包含可直接引用与仅能按名称恢复的 handlers,且 runtime 支持多实例时,拆分输出一条 `Type[]` 特性和一条字符串特性
|
||||||
|
- 若 runtime 不支持多实例或缺少相应构造函数,仍整体回退到字符串元数据,避免 mixed 场景漏注册
|
||||||
|
- 已补充 runtime 与 generator 双侧回归:
|
||||||
|
- `GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs` 新增 mixed fallback metadata 用例,锁定 registrar 只对字符串条目调用一次 `Assembly.GetType(...)`
|
||||||
|
- `GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs` 新增 mixed fallback emission 用例,锁定 generator 会输出两个程序集级 fallback 特性实例
|
||||||
|
- 同步更新:
|
||||||
|
- `GFramework.Cqrs.SourceGenerators/README.md`
|
||||||
|
- `docs/zh-CN/source-generators/cqrs-handler-registry-generator.md`
|
||||||
|
- 说明 mixed 场景现在会拆分 `Type` 元数据与字符串元数据
|
||||||
|
- 定向验证已通过:
|
||||||
|
- `dotnet build GFramework.Cqrs/GFramework.Cqrs.csproj -c Release`
|
||||||
|
- `dotnet build GFramework.Cqrs.SourceGenerators/GFramework.Cqrs.SourceGenerators.csproj -c Release`
|
||||||
|
- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsHandlerRegistrarTests"`
|
||||||
|
- `13/13` passed
|
||||||
|
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests"`
|
||||||
|
- `18/18` passed
|
||||||
|
- 随后按 `$gframework-pr-review` 重新拉取当前分支 PR 审查数据:
|
||||||
|
- 当前 worktree `feat/cqrs-optimization` 已对应 `PR #302`
|
||||||
|
- latest head commit 仍有 `3` 条 open AI review threads:Greptile 指向 generator preamble 的死参数与多实例 fallback 特性空行,CodeRabbit 指向 mixed/direct fallback 测试断言过宽
|
||||||
|
- MegaLinter 仍只暴露 `dotnet-format` 的 `Restore operation failed`,未给出本地仍成立的格式文件线索,因此按环境噪音处理
|
||||||
|
- 本轮已继续收口 `RP-052` 的 follow-up:
|
||||||
|
- 在 `GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.SourceEmission.cs` 中移除已不再参与判断的 `generationEnvironment` 透传参数
|
||||||
|
- 调整多实例 fallback 特性发射时的换行策略,避免最后一个 fallback 特性与 `CqrsHandlerRegistryAttribute` 之间保留多余空行
|
||||||
|
- 在 `GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs` 中补强 direct/mixed fallback 发射断言,锁定特性实例个数、拒绝空 marker,并确保 mixed 场景的程序集级 preamble 排版稳定
|
||||||
|
- 在 `GFramework.Cqrs.Tests/Cqrs/ReflectionFallbackNotificationContainer.cs` 中为 `DirectFallbackHandlerType` 补齐 `<returns>` XML 文档
|
||||||
|
- 本轮 review follow-up 验证已通过:
|
||||||
|
- `dotnet build GFramework.Cqrs.SourceGenerators/GFramework.Cqrs.SourceGenerators.csproj -c Release`
|
||||||
|
- `0 warning / 0 error`
|
||||||
|
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests"`
|
||||||
|
- `18/18` passed
|
||||||
|
- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsHandlerRegistrarTests"`
|
||||||
|
- `13/13` passed
|
||||||
|
|
||||||
## 2026-04-20
|
## 2026-04-20
|
||||||
|
|
||||||
|
### 阶段:direct fallback 元数据优先级收敛(CQRS-REWRITE-RP-051)
|
||||||
|
|
||||||
|
- 重新按 `gframework-batch-boot 50` 恢复 `Phase 8` 后,先复核当前 worktree 的恢复入口、`origin/main` 基线与分支规模:
|
||||||
|
- worktree 仍映射到 `cqrs-rewrite`
|
||||||
|
- 基线按批处理约定固定为 `origin/main`
|
||||||
|
- 本轮开始前分支累计 diff 为 `0 files / 0 lines`
|
||||||
|
- 结合当前代码热点与历史归档后,选择本轮批次目标为“继续收敛 generator fallback 元数据,进一步减少 runtime 按字符串类型名回查 handler 的场景”
|
||||||
|
- 已在 `GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs` 中新增 runtime fallback 合同探测:
|
||||||
|
- 识别 `CqrsReflectionFallbackAttribute` 是否支持 `params string[]`
|
||||||
|
- 识别 `CqrsReflectionFallbackAttribute` 是否支持 `params Type[]`
|
||||||
|
- 已在 `GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.Models.cs` 与
|
||||||
|
`CqrsHandlerRegistryGenerator.SourceEmission.cs` 中收敛 fallback 发射策略:
|
||||||
|
- 当本轮所有 fallback handlers 都可被生成代码直接引用,且 runtime 支持 `params Type[]` 时,生成器现优先发射 `typeof(...)` 形式的程序集级 fallback 元数据
|
||||||
|
- 当 fallback handlers 中仍存在不能直接引用的实现类型时,生成器继续整体回退到字符串元数据,避免 mixed 场景下部分 handler 走 `Type[]`、其余 handler 丢失恢复入口
|
||||||
|
- 已在 `GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs` 补充回归:
|
||||||
|
- 锁定 runtime 同时暴露字符串与 `Type` 两类 fallback 构造函数时,生成器优先选择直接 `Type` 元数据
|
||||||
|
- 保留现有字符串 fallback 合同测试,确保旧 contract 兼容路径不回退
|
||||||
|
- 同步更新:
|
||||||
|
- `GFramework.Cqrs.SourceGenerators/README.md`
|
||||||
|
- `docs/zh-CN/source-generators/cqrs-handler-registry-generator.md`
|
||||||
|
- 说明“可直接引用的 fallback handlers 会优先走 `typeof(...)` 元数据,减少运行时字符串回查”
|
||||||
|
- 定向验证已通过:
|
||||||
|
- `dotnet build GFramework.Cqrs.SourceGenerators/GFramework.Cqrs.SourceGenerators.csproj -c Release`
|
||||||
|
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests"`
|
||||||
|
- `17/17` passed
|
||||||
|
- 额外修正:
|
||||||
|
- active tracking 中原先引用的 `ai-plan/migration/CQRS_MODULE_SPLIT_PLAN.md` 在当前 worktree 已不存在;本轮已移除该失效路径,后续以 active tracking / trace 作为默认恢复入口
|
||||||
|
|
||||||
### 阶段:pointer / function pointer 泛型合同拒绝(CQRS-REWRITE-RP-050)
|
### 阶段:pointer / function pointer 泛型合同拒绝(CQRS-REWRITE-RP-050)
|
||||||
|
|
||||||
- 重新执行 `$gframework-pr-review` 后,确认当前分支对应 `PR #261`,状态仍为 `OPEN`
|
- 重新执行 `$gframework-pr-review` 后,确认当前分支对应 `PR #261`,状态仍为 `OPEN`
|
||||||
@ -72,6 +149,6 @@
|
|||||||
|
|
||||||
### 当前下一步
|
### 当前下一步
|
||||||
|
|
||||||
1. 回到 `Phase 8` 主线,优先选一个明确的 dispatch / invoker 反射缩减点继续推进
|
1. 回到 `Phase 8` 主线,优先再找一个 generator 覆盖缺口,继续减少仍需程序集级字符串 fallback 元数据的 handler 场景
|
||||||
2. 若继续文档主线,优先补齐 `docs/zh-CN/api-reference` 与教程入口页中仍过时的 CQRS API / 命名空间表述
|
2. 若继续文档主线,优先补齐 `docs/zh-CN/api-reference` 与教程入口页中仍过时的 CQRS API / 命名空间表述
|
||||||
3. 若后续 review thread 或 PR 状态再次变化,再重新执行 `$gframework-pr-review` 复核远端信号
|
3. 若后续 review thread 或 PR 状态再次变化,再重新执行 `$gframework-pr-review` 复核远端信号
|
||||||
|
|||||||
@ -32,6 +32,7 @@ runtime 在注册 handlers 时优先走静态注册表,而不是先扫描整
|
|||||||
- 程序集级 `CqrsReflectionFallbackAttribute`
|
- 程序集级 `CqrsReflectionFallbackAttribute`
|
||||||
|
|
||||||
这意味着运行时会先使用生成注册器完成可静态表达的映射,再只对剩余类型做补扫,而不是退回整程序集盲扫。
|
这意味着运行时会先使用生成注册器完成可静态表达的映射,再只对剩余类型做补扫,而不是退回整程序集盲扫。
|
||||||
|
如果这些 fallback handlers 本身仍可直接引用,生成器会优先发射 `typeof(...)` 形式的 fallback 元数据;当 runtime 允许同一程序集声明多个 fallback 特性实例时,mixed 场景也会拆成 `Type` 元数据和字符串元数据两段,进一步减少 runtime 再做字符串类型名回查的成本。
|
||||||
|
|
||||||
## 最小接入路径
|
## 最小接入路径
|
||||||
|
|
||||||
@ -108,6 +109,9 @@ RegisterCqrsHandlersFromAssemblies(
|
|||||||
- 能直接引用的 handler,生成直接注册语句
|
- 能直接引用的 handler,生成直接注册语句
|
||||||
- 实现类型不能直接引用、但服务接口还能精确表达时,生成反射实现类型查找
|
- 实现类型不能直接引用、但服务接口还能精确表达时,生成反射实现类型查找
|
||||||
- 服务接口本身也需要运行时解析时,生成精确 type lookup
|
- 服务接口本身也需要运行时解析时,生成精确 type lookup
|
||||||
|
- 当 fallback handlers 全部可直接引用且 runtime 暴露 `params Type[]` 合同时,优先发射直接 `Type` 元数据
|
||||||
|
- 当 mixed 场景同时包含可直接引用与仅能按名称恢复的 handlers,且 runtime 允许多个 fallback 特性实例时,拆分发射 `Type` 元数据和字符串元数据
|
||||||
|
- 其余场景统一回退到字符串元数据,避免 mixed 场景漏注册
|
||||||
- 只有在 runtime 提供 `CqrsReflectionFallbackAttribute` 合同时,才允许发射依赖 fallback 的结果
|
- 只有在 runtime 提供 `CqrsReflectionFallbackAttribute` 合同时,才允许发射依赖 fallback 的结果
|
||||||
|
|
||||||
如果当前编译环境缺少这个 fallback 合同,而某些 handler 又必须依赖它,生成器会报:
|
如果当前编译环境缺少这个 fallback 合同,而某些 handler 又必须依赖它,生成器会报:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user