diff --git a/GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs b/GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs index 318f6d21..95afa92f 100644 --- a/GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs +++ b/GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs @@ -190,11 +190,11 @@ internal sealed class CqrsHandlerRegistrarTests } /// - /// 验证当生成注册器显式要求 reflection fallback 时,运行时会补扫剩余 handlers, - /// 同时避免把已由生成注册器注册的映射重复写入服务集合。 + /// 验证当生成注册器提供精确 fallback 类型名时,运行时会定向补扫剩余 handlers, + /// 而不是重新枚举整个程序集的类型列表。 /// [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(); 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); } } diff --git a/GFramework.Cqrs/CqrsReflectionFallbackAttribute.cs b/GFramework.Cqrs/CqrsReflectionFallbackAttribute.cs index f18a7344..9d3c21bf 100644 --- a/GFramework.Cqrs/CqrsReflectionFallbackAttribute.cs +++ b/GFramework.Cqrs/CqrsReflectionFallbackAttribute.cs @@ -11,4 +11,26 @@ namespace GFramework.Cqrs; [AttributeUsage(AttributeTargets.Assembly)] public sealed class CqrsReflectionFallbackAttribute : Attribute { + /// + /// 初始化 。 + /// + /// + /// 需要运行时补充反射注册的处理器类型全名。 + /// 当该清单为空时,运行时会回退到整程序集扫描,以兼容旧版 marker 语义。 + /// + 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(); + } + + /// + /// 获取需要运行时补充反射注册的处理器类型全名集合。 + /// + public IReadOnlyList FallbackHandlerTypeNames { get; } } diff --git a/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs b/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs index 867c6887..968424b7 100644 --- a/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs +++ b/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs @@ -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(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) { - logger.Debug( - $"Generated CQRS registry for assembly {assemblyName} requested reflection fallback for unsupported handlers."); - return GeneratedRegistrationResult.RequiresReflectionFallback; + if (reflectionFallbackTypeNames.Count > 0) + { + logger.Debug( + $"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.WithReflectionFallback(reflectionFallbackTypeNames); } - return GeneratedRegistrationResult.FullyHandled; + 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(); } } /// /// 注册单个程序集里的所有 CQRS 处理器映射。 /// - private static void RegisterAssemblyHandlers(IServiceCollection services, Assembly assembly, ILogger logger) + private static void RegisterAssemblyHandlers( + IServiceCollection services, + Assembly assembly, + ILogger logger, + IReadOnlyList? 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 } } + /// + /// 根据生成器提供的 fallback 清单或整程序集扫描结果,获取本轮要注册的候选处理器类型。 + /// + private static IReadOnlyList GetCandidateHandlerTypes( + Assembly assembly, + ILogger logger, + IReadOnlyList? reflectionFallbackTypeNames) + { + return reflectionFallbackTypeNames is { Count: > 0 } + ? GetNamedFallbackTypes(assembly, reflectionFallbackTypeNames, logger) + : GetLoadableTypes(assembly, logger); + } + + /// + /// 根据生成器记录的类型全名,精确解析仍需运行时补充注册的处理器类型。 + /// + private static IReadOnlyList GetNamedFallbackTypes( + Assembly assembly, + IReadOnlyList reflectionFallbackTypeNames, + ILogger logger) + { + var assemblyName = GetAssemblySortKey(assembly); + var resolvedTypes = new List(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(); + } + /// /// 安全获取程序集中的可加载类型,并在部分类型加载失败时保留其余处理器注册能力。 /// @@ -221,11 +292,24 @@ internal static class CqrsHandlerRegistrar } /// - /// 判断生成注册器是否要求运行时继续补充反射扫描。 + /// 获取生成注册器要求运行时继续补充反射扫描的 handler 类型名清单。 /// - private static bool RequiresReflectionFallback(Assembly assembly) + private static IReadOnlyList? GetReflectionFallbackTypeNames(Assembly assembly) { - return assembly.GetCustomAttributes(typeof(CqrsReflectionFallbackAttribute), inherit: false)?.Length > 0; + var fallbackAttributes = assembly + .GetCustomAttributes(typeof(CqrsReflectionFallbackAttribute), inherit: false) + .OfType() + .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(); } /// @@ -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? 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 reflectionFallbackTypeNames) + { + ArgumentNullException.ThrowIfNull(reflectionFallbackTypeNames); + + return new GeneratedRegistrationResult( + UsedGeneratedRegistry: true, + RequiresReflectionFallback: true, + ReflectionFallbackTypeNames: reflectionFallbackTypeNames); + } } } diff --git a/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs b/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs index 0cd91844..e1ec1546 100644 --- a/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs +++ b/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs @@ -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; + + public sealed class Container + { + private sealed record HiddenRequest() : IRequest; + + private sealed class HiddenHandler : IRequestHandler { } + } + + public sealed class VisibleHandler : IRequestHandler { } + } + """; + + const string expected = """ + // + #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), + typeof(global::TestApp.VisibleHandler)); + logger.Debug("Registered CQRS handler TestApp.VisibleHandler as GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler."); + } + } + + """; + + await GeneratorTest.RunAsync( + source, + ("CqrsHandlerRegistry.g.cs", expected)); + } + + /// + /// 验证当 runtime 仅支持旧版无参 fallback marker 时,生成器会退回旧语义, + /// 只输出 marker 而不输出精确类型名。 + /// + [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 { } + public interface INotification { } + public interface IStreamRequest { } + + public interface IRequestHandler where TRequest : IRequest { } + public interface INotificationHandler where TNotification : INotification { } + public interface IStreamRequestHandler where TRequest : IStreamRequest { } + } + + namespace GFramework.Cqrs + { + public interface ICqrsHandlerRegistry + { + void Register(Microsoft.Extensions.DependencyInjection.IServiceCollection services, GFramework.Core.Abstractions.Logging.ILogger logger); + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class CqrsHandlerRegistryAttribute : Attribute + { + public CqrsHandlerRegistryAttribute(Type registryType) { } + } + + [AttributeUsage(AttributeTargets.Assembly)] + public sealed class CqrsReflectionFallbackAttribute : Attribute + { + public CqrsReflectionFallbackAttribute() { } } } diff --git a/GFramework.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs b/GFramework.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs index 80561248..1e260e32 100644 --- a/GFramework.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs +++ b/GFramework.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs @@ -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.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 CollectRegistrations( ImmutableArray candidates, - out bool hasUnsupportedConcreteHandler) + out bool hasUnsupportedConcreteHandler, + out IReadOnlyList reflectionFallbackTypeNames) { var registrations = new List(); hasUnsupportedConcreteHandler = false; + var fallbackTypeNames = new SortedSet(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(); + 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 registrations, - bool emitReflectionFallbackAttribute) + bool emitReflectionFallbackAttribute, + ReflectionFallbackEmissionMode reflectionFallbackEmissionMode, + IReadOnlyList reflectionFallbackTypeNames) { var builder = new StringBuilder(); builder.AppendLine("// "); @@ -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 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 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 + } }