feat(cqrs): 添加CQRS处理器自动注册功能

- 实现CqrsHandlerRegistrar类,支持扫描并注册CQRS请求/通知/流式处理器
- 添加源码生成注册器优先策略,减少冷启动时的反射开销
- 实现运行时反射扫描回退机制,确保处理器注册的完整性
- 添加CqrsReflectionFallbackAttribute特性,标记需要运行时补充扫描的程序集
- 创建完整的单元测试套件,验证处理器注册顺序与容错行为
- 实现CqrsHandlerRegistryGenerator源码生成器,自动生成处理器注册代码
- 添加详细的日志记录与诊断功能,便于调试注册过程
- 实现类型安全的处理器映射验证与重复注册检测机制
This commit is contained in:
GeWuYou 2026-04-16 11:11:29 +08:00
parent 21627c0381
commit 391e3e9813
5 changed files with 414 additions and 43 deletions

View File

@ -190,11 +190,11 @@ internal sealed class CqrsHandlerRegistrarTests
}
/// <summary>
/// 验证当生成注册器显式要求 reflection fallback 时,运行时会补扫剩余 handlers
/// 同时避免把已由生成注册器注册的映射重复写入服务集合
/// 验证当生成注册器提供精确 fallback 类型名时,运行时会定向补扫剩余 handlers
/// 而不是重新枚举整个程序集的类型列表
/// </summary>
[Test]
public void RegisterHandlers_Should_Combine_Generated_Registry_With_Reflection_Fallback_Without_Duplicates()
public void RegisterHandlers_Should_Use_Targeted_Type_Lookups_For_Reflection_Fallback_Without_Duplicates()
{
var generatedAssembly = new Mock<Assembly>();
generatedAssembly
@ -205,14 +205,17 @@ internal sealed class CqrsHandlerRegistrarTests
.Returns([new CqrsHandlerRegistryAttribute(typeof(PartialGeneratedNotificationHandlerRegistry))]);
generatedAssembly
.Setup(static assembly => assembly.GetCustomAttributes(typeof(CqrsReflectionFallbackAttribute), false))
.Returns([new CqrsReflectionFallbackAttribute()]);
generatedAssembly
.Setup(static assembly => assembly.GetTypes())
.Returns(
[
typeof(GeneratedRegistryNotificationHandler),
ReflectionFallbackNotificationContainer.ReflectionOnlyHandlerType
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);
@ -231,6 +234,14 @@ internal sealed class CqrsHandlerRegistrarTests
typeof(GeneratedRegistryNotificationHandler),
ReflectionFallbackNotificationContainer.ReflectionOnlyHandlerType
]));
generatedAssembly.Verify(
static assembly => assembly.GetType(
ReflectionFallbackNotificationContainer.ReflectionOnlyHandlerType.FullName!,
false,
false),
Times.Once);
generatedAssembly.Verify(static assembly => assembly.GetTypes(), Times.Never);
}
}

View File

@ -11,4 +11,26 @@ namespace GFramework.Cqrs;
[AttributeUsage(AttributeTargets.Assembly)]
public sealed class CqrsReflectionFallbackAttribute : Attribute
{
/// <summary>
/// 初始化 <see cref="CqrsReflectionFallbackAttribute" />。
/// </summary>
/// <param name="fallbackHandlerTypeNames">
/// 需要运行时补充反射注册的处理器类型全名。
/// 当该清单为空时,运行时会回退到整程序集扫描,以兼容旧版 marker 语义。
/// </param>
public CqrsReflectionFallbackAttribute(params string[] fallbackHandlerTypeNames)
{
ArgumentNullException.ThrowIfNull(fallbackHandlerTypeNames);
FallbackHandlerTypeNames = fallbackHandlerTypeNames
.Where(static typeName => !string.IsNullOrWhiteSpace(typeName))
.Distinct(StringComparer.Ordinal)
.OrderBy(static typeName => typeName, StringComparer.Ordinal)
.ToArray();
}
/// <summary>
/// 获取需要运行时补充反射注册的处理器类型全名集合。
/// </summary>
public IReadOnlyList<string> FallbackHandlerTypeNames { get; }
}

View File

@ -33,10 +33,14 @@ internal static class CqrsHandlerRegistrar
{
var generatedRegistrationResult =
TryRegisterGeneratedHandlers(container.GetServicesUnsafe, assembly, logger);
if (generatedRegistrationResult == GeneratedRegistrationResult.FullyHandled)
if (generatedRegistrationResult is { UsedGeneratedRegistry: true, RequiresReflectionFallback: false })
continue;
RegisterAssemblyHandlers(container.GetServicesUnsafe, assembly, logger);
RegisterAssemblyHandlers(
container.GetServicesUnsafe,
assembly,
logger,
generatedRegistrationResult.ReflectionFallbackTypeNames);
}
}
@ -66,7 +70,7 @@ internal static class CqrsHandlerRegistrar
.ToList();
if (registryTypes.Count == 0)
return GeneratedRegistrationResult.NoGeneratedRegistry;
return GeneratedRegistrationResult.NoGeneratedRegistry();
var registries = new List<ICqrsHandlerRegistry>(registryTypes.Count);
foreach (var registryType in registryTypes)
@ -75,21 +79,21 @@ internal static class CqrsHandlerRegistrar
{
logger.Warn(
$"Ignoring generated CQRS handler registry {registryType.FullName} in assembly {assemblyName} because it does not implement {typeof(ICqrsHandlerRegistry).FullName}.");
return GeneratedRegistrationResult.NoGeneratedRegistry;
return GeneratedRegistrationResult.NoGeneratedRegistry();
}
if (registryType.IsAbstract)
{
logger.Warn(
$"Ignoring generated CQRS handler registry {registryType.FullName} in assembly {assemblyName} because it is abstract.");
return GeneratedRegistrationResult.NoGeneratedRegistry;
return GeneratedRegistrationResult.NoGeneratedRegistry();
}
if (Activator.CreateInstance(registryType, nonPublic: true) is not ICqrsHandlerRegistry registry)
{
logger.Warn(
$"Ignoring generated CQRS handler registry {registryType.FullName} in assembly {assemblyName} because it could not be instantiated.");
return GeneratedRegistrationResult.NoGeneratedRegistry;
return GeneratedRegistrationResult.NoGeneratedRegistry();
}
registries.Add(registry);
@ -102,14 +106,24 @@ internal static class CqrsHandlerRegistrar
registry.Register(services, logger);
}
if (RequiresReflectionFallback(assembly))
var reflectionFallbackTypeNames = GetReflectionFallbackTypeNames(assembly);
if (reflectionFallbackTypeNames is not null)
{
if (reflectionFallbackTypeNames.Count > 0)
{
logger.Debug(
$"Generated CQRS registry for assembly {assemblyName} requested reflection fallback for unsupported handlers.");
return GeneratedRegistrationResult.RequiresReflectionFallback;
$"Generated CQRS registry for assembly {assemblyName} requested targeted reflection fallback for {reflectionFallbackTypeNames.Count} unsupported handler type(s).");
}
else
{
logger.Debug(
$"Generated CQRS registry for assembly {assemblyName} requested full reflection fallback for unsupported handlers.");
}
return GeneratedRegistrationResult.FullyHandled;
return GeneratedRegistrationResult.WithReflectionFallback(reflectionFallbackTypeNames);
}
return GeneratedRegistrationResult.FullyHandled();
}
catch (Exception exception)
{
@ -117,16 +131,21 @@ internal static class CqrsHandlerRegistrar
$"Generated CQRS handler registry discovery failed for assembly {assemblyName}. Falling back to reflection scan.");
logger.Warn(
$"Failed to use generated CQRS handler registry for assembly {assemblyName}: {exception.Message}");
return GeneratedRegistrationResult.NoGeneratedRegistry;
return GeneratedRegistrationResult.NoGeneratedRegistry();
}
}
/// <summary>
/// 注册单个程序集里的所有 CQRS 处理器映射。
/// </summary>
private static void RegisterAssemblyHandlers(IServiceCollection services, Assembly assembly, ILogger logger)
private static void RegisterAssemblyHandlers(
IServiceCollection services,
Assembly assembly,
ILogger logger,
IReadOnlyList<string>? reflectionFallbackTypeNames)
{
foreach (var implementationType in GetLoadableTypes(assembly, logger).Where(IsConcreteHandlerType))
foreach (var implementationType in GetCandidateHandlerTypes(assembly, logger, reflectionFallbackTypeNames)
.Where(IsConcreteHandlerType))
{
var handlerInterfaces = implementationType
.GetInterfaces()
@ -155,6 +174,58 @@ internal static class CqrsHandlerRegistrar
}
}
/// <summary>
/// 根据生成器提供的 fallback 清单或整程序集扫描结果,获取本轮要注册的候选处理器类型。
/// </summary>
private static IReadOnlyList<Type> GetCandidateHandlerTypes(
Assembly assembly,
ILogger logger,
IReadOnlyList<string>? reflectionFallbackTypeNames)
{
return reflectionFallbackTypeNames is { Count: > 0 }
? GetNamedFallbackTypes(assembly, reflectionFallbackTypeNames, logger)
: GetLoadableTypes(assembly, logger);
}
/// <summary>
/// 根据生成器记录的类型全名,精确解析仍需运行时补充注册的处理器类型。
/// </summary>
private static IReadOnlyList<Type> GetNamedFallbackTypes(
Assembly assembly,
IReadOnlyList<string> reflectionFallbackTypeNames,
ILogger logger)
{
var assemblyName = GetAssemblySortKey(assembly);
var resolvedTypes = new List<Type>(reflectionFallbackTypeNames.Count);
foreach (var typeName in reflectionFallbackTypeNames
.Where(static name => !string.IsNullOrWhiteSpace(name))
.Distinct(StringComparer.Ordinal)
.OrderBy(static name => name, StringComparer.Ordinal))
{
try
{
var type = assembly.GetType(typeName, throwOnError: false, ignoreCase: false);
if (type is null)
{
logger.Warn(
$"Generated CQRS reflection fallback type {typeName} could not be resolved in assembly {assemblyName}. Skipping targeted fallback entry.");
continue;
}
resolvedTypes.Add(type);
}
catch (Exception exception)
{
logger.Warn(
$"Generated CQRS reflection fallback type {typeName} failed to load in assembly {assemblyName}: {exception.Message}");
}
}
return resolvedTypes
.OrderBy(GetTypeSortKey, StringComparer.Ordinal)
.ToList();
}
/// <summary>
/// 安全获取程序集中的可加载类型,并在部分类型加载失败时保留其余处理器注册能力。
/// </summary>
@ -221,11 +292,24 @@ internal static class CqrsHandlerRegistrar
}
/// <summary>
/// 判断生成注册器是否要求运行时继续补充反射扫描
/// 获取生成注册器要求运行时继续补充反射扫描的 handler 类型名清单
/// </summary>
private static bool RequiresReflectionFallback(Assembly assembly)
private static IReadOnlyList<string>? GetReflectionFallbackTypeNames(Assembly assembly)
{
return assembly.GetCustomAttributes(typeof(CqrsReflectionFallbackAttribute), inherit: false)?.Length > 0;
var fallbackAttributes = assembly
.GetCustomAttributes(typeof(CqrsReflectionFallbackAttribute), inherit: false)
.OfType<CqrsReflectionFallbackAttribute>()
.ToList();
if (fallbackAttributes.Count == 0)
return null;
return fallbackAttributes
.SelectMany(static attribute => attribute.FallbackHandlerTypeNames)
.Where(static typeName => !string.IsNullOrWhiteSpace(typeName))
.Distinct(StringComparer.Ordinal)
.OrderBy(static typeName => typeName, StringComparer.Ordinal)
.ToArray();
}
/// <summary>
@ -259,10 +343,36 @@ internal static class CqrsHandlerRegistrar
return type.FullName ?? type.Name;
}
private enum GeneratedRegistrationResult
private readonly record struct GeneratedRegistrationResult(
bool UsedGeneratedRegistry,
bool RequiresReflectionFallback,
IReadOnlyList<string>? ReflectionFallbackTypeNames)
{
NoGeneratedRegistry,
FullyHandled,
RequiresReflectionFallback
public static GeneratedRegistrationResult NoGeneratedRegistry()
{
return new GeneratedRegistrationResult(
UsedGeneratedRegistry: false,
RequiresReflectionFallback: false,
ReflectionFallbackTypeNames: null);
}
public static GeneratedRegistrationResult FullyHandled()
{
return new GeneratedRegistrationResult(
UsedGeneratedRegistry: true,
RequiresReflectionFallback: false,
ReflectionFallbackTypeNames: null);
}
public static GeneratedRegistrationResult WithReflectionFallback(
IReadOnlyList<string> reflectionFallbackTypeNames)
{
ArgumentNullException.ThrowIfNull(reflectionFallbackTypeNames);
return new GeneratedRegistrationResult(
UsedGeneratedRegistry: true,
RequiresReflectionFallback: true,
ReflectionFallbackTypeNames: reflectionFallbackTypeNames);
}
}
}

View File

@ -65,6 +65,7 @@ public class CqrsHandlerRegistryGeneratorTests
[AttributeUsage(AttributeTargets.Assembly)]
public sealed class CqrsReflectionFallbackAttribute : Attribute
{
public CqrsReflectionFallbackAttribute(params string[] fallbackHandlerTypeNames) { }
}
}
@ -180,6 +181,116 @@ public class CqrsHandlerRegistryGeneratorTests
[AttributeUsage(AttributeTargets.Assembly)]
public sealed class CqrsReflectionFallbackAttribute : Attribute
{
public CqrsReflectionFallbackAttribute(params string[] fallbackHandlerTypeNames) { }
}
}
namespace TestApp
{
using GFramework.Cqrs.Abstractions.Cqrs;
public sealed record VisibleRequest() : IRequest<string>;
public sealed class Container
{
private sealed record HiddenRequest() : IRequest<string>;
private sealed class HiddenHandler : IRequestHandler<HiddenRequest, string> { }
}
public sealed class VisibleHandler : IRequestHandler<VisibleRequest, string> { }
}
""";
const string expected = """
// <auto-generated />
#nullable enable
[assembly: global::GFramework.Cqrs.CqrsHandlerRegistryAttribute(typeof(global::GFramework.Generated.Cqrs.__GFrameworkGeneratedCqrsHandlerRegistry))]
[assembly: global::GFramework.Cqrs.CqrsReflectionFallbackAttribute("TestApp.Container+HiddenHandler")]
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));
global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddTransient(
services,
typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<global::TestApp.VisibleRequest, string>),
typeof(global::TestApp.VisibleHandler));
logger.Debug("Registered CQRS handler TestApp.VisibleHandler as GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<TestApp.VisibleRequest, string>.");
}
}
""";
await GeneratorTest<CqrsHandlerRegistryGenerator>.RunAsync(
source,
("CqrsHandlerRegistry.g.cs", expected));
}
/// <summary>
/// 验证当 runtime 仅支持旧版无参 fallback marker 时,生成器会退回旧语义,
/// 只输出 marker 而不输出精确类型名。
/// </summary>
[Test]
public async Task Generates_Legacy_Fallback_Marker_When_Runtime_Does_Not_Support_Type_Name_List()
{
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<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() { }
}
}

View File

@ -59,7 +59,8 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
return new GenerationEnvironment(
generationEnabled,
compilation.GetTypeByMetadataName(CqrsReflectionFallbackAttributeMetadataName) is not null);
GetReflectionFallbackEmissionMode(
compilation.GetTypeByMetadataName(CqrsReflectionFallbackAttributeMetadataName)));
}
private static bool IsHandlerCandidate(SyntaxNode node)
@ -96,7 +97,8 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
return new HandlerCandidateAnalysis(
implementationTypeDisplayName,
ImmutableArray<HandlerRegistrationSpec>.Empty,
true);
true,
GetReflectionFallbackTypeName(type));
}
var implementationLogName = GetLogDisplayName(type);
@ -113,7 +115,8 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
return new HandlerCandidateAnalysis(
implementationTypeDisplayName,
registrations.MoveToImmutable(),
false);
false,
null);
}
private static void Execute(SourceProductionContext context, GenerationEnvironment generationEnvironment,
@ -122,27 +125,37 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
if (!generationEnvironment.GenerationEnabled)
return;
var registrations = CollectRegistrations(candidates, out var hasUnsupportedConcreteHandler);
var registrations = CollectRegistrations(
candidates,
out var hasUnsupportedConcreteHandler,
out var reflectionFallbackTypeNames);
if (registrations.Count == 0)
return;
// If the runtime contract does not yet expose the reflection fallback marker,
// keep the previous all-or-nothing behavior so unsupported handlers are not silently dropped.
if (hasUnsupportedConcreteHandler && !generationEnvironment.SupportsReflectionFallbackMarker)
if (hasUnsupportedConcreteHandler &&
generationEnvironment.ReflectionFallbackEmissionMode == ReflectionFallbackEmissionMode.Disabled)
return;
context.AddSource(
HintName,
GenerateSource(registrations, hasUnsupportedConcreteHandler));
GenerateSource(
registrations,
hasUnsupportedConcreteHandler,
generationEnvironment.ReflectionFallbackEmissionMode,
reflectionFallbackTypeNames));
}
private static List<HandlerRegistrationSpec> CollectRegistrations(
ImmutableArray<HandlerCandidateAnalysis?> candidates,
out bool hasUnsupportedConcreteHandler)
out bool hasUnsupportedConcreteHandler,
out IReadOnlyList<string> reflectionFallbackTypeNames)
{
var registrations = new List<HandlerRegistrationSpec>();
hasUnsupportedConcreteHandler = false;
var fallbackTypeNames = new SortedSet<string>(StringComparer.Ordinal);
// Partial declarations surface the same symbol through multiple syntax nodes.
// Collapse them by implementation type so generated registrations stay stable and duplicate-free.
@ -156,6 +169,13 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
if (candidate.Value.HasUnsupportedConcreteHandler)
{
hasUnsupportedConcreteHandler = true;
var reflectionFallbackTypeName = candidate.Value.ReflectionFallbackTypeName;
if (reflectionFallbackTypeName is not null &&
!string.IsNullOrWhiteSpace(reflectionFallbackTypeName))
{
fallbackTypeNames.Add(reflectionFallbackTypeName);
}
continue;
}
@ -178,9 +198,30 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
: StringComparer.Ordinal.Compare(left.HandlerInterfaceLogName, right.HandlerInterfaceLogName);
});
reflectionFallbackTypeNames = fallbackTypeNames.ToArray();
return registrations;
}
private static ReflectionFallbackEmissionMode GetReflectionFallbackEmissionMode(INamedTypeSymbol? attributeType)
{
if (attributeType is null)
return ReflectionFallbackEmissionMode.Disabled;
foreach (var constructor in attributeType.InstanceConstructors)
{
if (constructor.Parameters.Length != 1)
continue;
if (constructor.Parameters[0].Type is IArrayTypeSymbol arrayType &&
arrayType.ElementType.SpecialType == SpecialType.System_String)
{
return ReflectionFallbackEmissionMode.PreciseTypeNames;
}
}
return ReflectionFallbackEmissionMode.MarkerOnly;
}
private static bool IsConcreteHandlerType(INamedTypeSymbol type)
{
return type.TypeKind is TypeKind.Class or TypeKind.Struct &&
@ -272,6 +313,34 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
return builder.ToString();
}
private static string GetReflectionFallbackTypeName(INamedTypeSymbol type)
{
var nestedTypes = new Stack<string>();
for (var current = type; current is not null; current = current.ContainingType)
{
nestedTypes.Push(current.MetadataName);
}
var builder = new StringBuilder();
if (!type.ContainingNamespace.IsGlobalNamespace)
{
builder.Append(type.ContainingNamespace.ToDisplayString());
builder.Append('.');
}
var isFirstType = true;
while (nestedTypes.Count > 0)
{
if (!isFirstType)
builder.Append('+');
builder.Append(nestedTypes.Pop());
isFirstType = false;
}
return builder.ToString();
}
private static string GetTypeSortKey(ITypeSymbol type)
{
return type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
@ -284,7 +353,9 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
private static string GenerateSource(
IReadOnlyList<HandlerRegistrationSpec> registrations,
bool emitReflectionFallbackAttribute)
bool emitReflectionFallbackAttribute,
ReflectionFallbackEmissionMode reflectionFallbackEmissionMode,
IReadOnlyList<string> reflectionFallbackTypeNames)
{
var builder = new StringBuilder();
builder.AppendLine("// <auto-generated />");
@ -297,11 +368,10 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
builder.Append('.');
builder.Append(GeneratedTypeName);
builder.AppendLine("))]");
if (emitReflectionFallbackAttribute)
if (emitReflectionFallbackAttribute &&
reflectionFallbackEmissionMode != ReflectionFallbackEmissionMode.Disabled)
{
builder.Append("[assembly: global::");
builder.Append(CqrsRuntimeNamespace);
builder.AppendLine(".CqrsReflectionFallbackAttribute()]");
AppendReflectionFallbackAttribute(builder, reflectionFallbackEmissionMode, reflectionFallbackTypeNames);
}
builder.AppendLine();
@ -349,6 +419,36 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
return builder.ToString();
}
private static void AppendReflectionFallbackAttribute(
StringBuilder builder,
ReflectionFallbackEmissionMode reflectionFallbackEmissionMode,
IReadOnlyList<string> reflectionFallbackTypeNames)
{
builder.Append("[assembly: global::");
builder.Append(CqrsRuntimeNamespace);
builder.Append(".CqrsReflectionFallbackAttribute");
if (reflectionFallbackEmissionMode == ReflectionFallbackEmissionMode.PreciseTypeNames &&
reflectionFallbackTypeNames.Count > 0)
{
builder.Append('(');
for (var index = 0; index < reflectionFallbackTypeNames.Count; index++)
{
if (index > 0)
builder.Append(", ");
builder.Append('"');
builder.Append(EscapeStringLiteral(reflectionFallbackTypeNames[index]));
builder.Append('"');
}
builder.AppendLine(")]");
return;
}
builder.AppendLine("()]");
}
private static string EscapeStringLiteral(string value)
{
return value.Replace("\\", "\\\\")
@ -368,11 +468,13 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
public HandlerCandidateAnalysis(
string implementationTypeDisplayName,
ImmutableArray<HandlerRegistrationSpec> registrations,
bool hasUnsupportedConcreteHandler)
bool hasUnsupportedConcreteHandler,
string? reflectionFallbackTypeName)
{
ImplementationTypeDisplayName = implementationTypeDisplayName;
Registrations = registrations;
HasUnsupportedConcreteHandler = hasUnsupportedConcreteHandler;
ReflectionFallbackTypeName = reflectionFallbackTypeName;
}
public string ImplementationTypeDisplayName { get; }
@ -381,11 +483,15 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
public bool HasUnsupportedConcreteHandler { get; }
public string? ReflectionFallbackTypeName { get; }
public bool Equals(HandlerCandidateAnalysis other)
{
if (!string.Equals(ImplementationTypeDisplayName, other.ImplementationTypeDisplayName,
StringComparison.Ordinal) ||
HasUnsupportedConcreteHandler != other.HasUnsupportedConcreteHandler ||
!string.Equals(ReflectionFallbackTypeName, other.ReflectionFallbackTypeName,
StringComparison.Ordinal) ||
Registrations.Length != other.Registrations.Length)
{
return false;
@ -411,6 +517,10 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
{
var hashCode = StringComparer.Ordinal.GetHashCode(ImplementationTypeDisplayName);
hashCode = (hashCode * 397) ^ HasUnsupportedConcreteHandler.GetHashCode();
hashCode = (hashCode * 397) ^
(ReflectionFallbackTypeName is null
? 0
: StringComparer.Ordinal.GetHashCode(ReflectionFallbackTypeName));
foreach (var registration in Registrations)
{
hashCode = (hashCode * 397) ^ registration.GetHashCode();
@ -423,5 +533,12 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
private readonly record struct GenerationEnvironment(
bool GenerationEnabled,
bool SupportsReflectionFallbackMarker);
ReflectionFallbackEmissionMode ReflectionFallbackEmissionMode);
private enum ReflectionFallbackEmissionMode
{
Disabled,
MarkerOnly,
PreciseTypeNames
}
}