fix(source-generators): 收口PR269生成器评审修复

- 重构 CqrsHandlerRegistryGenerator 为按职责拆分的 partial 生成器文件,保留现有注册输出与 fallback 契约
- 修复 ContextAwareGenerator 生成字段命名冲突并为 SetContextProvider 补充运行时 null 校验与异常文档
- 补充 Option<T> 的 XML remarks 契约说明与 ContextAwareGenerator 字段冲突快照测试
- 更新 analyzer-warning-reduction 跟踪与 trace,记录 PR #269 review follow-up 与验证结果
This commit is contained in:
gewuyou 2026-04-22 12:54:58 +08:00
parent 0f8bf077e4
commit 6d4f9f2f94
11 changed files with 1675 additions and 1415 deletions

View File

@ -107,7 +107,7 @@ public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase
sb.AppendLine( sb.AppendLine(
"/// 已缓存的实例上下文需要通过 <see cref=\"GFramework.Core.Abstractions.Rule.IContextAware.SetContext(GFramework.Core.Abstractions.Architectures.IArchitectureContext)\" /> 显式覆盖。"); "/// 已缓存的实例上下文需要通过 <see cref=\"GFramework.Core.Abstractions.Rule.IContextAware.SetContext(GFramework.Core.Abstractions.Architectures.IArchitectureContext)\" /> 显式覆盖。");
sb.AppendLine( sb.AppendLine(
"/// 与手动继承 <see cref=\"global::GFramework.Core.Rule.ContextAwareBase\" /> 的路径相比,生成实现会使用 <c>_contextSync</c> 协调惰性初始化、provider 切换和显式上下文注入;"); "/// 与手动继承 <see cref=\"global::GFramework.Core.Rule.ContextAwareBase\" /> 的路径相比,生成实现会使用 <c>_gFrameworkContextAwareSync</c> 协调惰性初始化、provider 切换和显式上下文注入;");
sb.AppendLine( sb.AppendLine(
"/// <see cref=\"global::GFramework.Core.Rule.ContextAwareBase\" /> 则保持无锁的实例级缓存语义,更适合已经由调用方线程模型保证串行访问的简单场景。"); "/// <see cref=\"global::GFramework.Core.Rule.ContextAwareBase\" /> 则保持无锁的实例级缓存语义,更适合已经由调用方线程模型保证串行访问的简单场景。");
sb.AppendLine("/// </remarks>"); sb.AppendLine("/// </remarks>");
@ -151,10 +151,11 @@ public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase
/// <param name="sb">字符串构建器。</param> /// <param name="sb">字符串构建器。</param>
private static void GenerateContextBackingFields(StringBuilder sb) private static void GenerateContextBackingFields(StringBuilder sb)
{ {
sb.AppendLine(" private global::GFramework.Core.Abstractions.Architectures.IArchitectureContext? _context;");
sb.AppendLine( sb.AppendLine(
" private static global::GFramework.Core.Abstractions.Architectures.IArchitectureContextProvider? _contextProvider;"); " private global::GFramework.Core.Abstractions.Architectures.IArchitectureContext? _gFrameworkContextAwareContext;");
sb.AppendLine(" private static readonly object _contextSync = new();"); sb.AppendLine(
" private static global::GFramework.Core.Abstractions.Architectures.IArchitectureContextProvider? _gFrameworkContextAwareProvider;");
sb.AppendLine(" private static readonly object _gFrameworkContextAwareSync = new();");
sb.AppendLine(); sb.AppendLine();
} }
@ -177,7 +178,7 @@ public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase
sb.AppendLine( sb.AppendLine(
" /// 或 <see cref=\"ResetContextProvider\" /> 不会自动清除此缓存;如需覆盖,请显式调用 <c>IContextAware.SetContext(...)</c>。"); " /// 或 <see cref=\"ResetContextProvider\" /> 不会自动清除此缓存;如需覆盖,请显式调用 <c>IContextAware.SetContext(...)</c>。");
sb.AppendLine( sb.AppendLine(
" /// 当前实现还假设 <see cref=\"GFramework.Core.Abstractions.Architectures.IArchitectureContextProvider.GetContext\" /> 可在持有 <c>_contextSync</c> 时安全执行;"); " /// 当前实现还假设 <see cref=\"GFramework.Core.Abstractions.Architectures.IArchitectureContextProvider.GetContext\" /> 可在持有 <c>_gFrameworkContextAwareSync</c> 时安全执行;");
sb.AppendLine( sb.AppendLine(
" /// 自定义 provider 不应在该调用链内重新进入当前类型的 provider 配置 API且应避免引入与外部全局锁相互等待的锁顺序。"); " /// 自定义 provider 不应在该调用链内重新进入当前类型的 provider 配置 API且应避免引入与外部全局锁相互等待的锁顺序。");
sb.AppendLine(" /// </remarks>"); sb.AppendLine(" /// </remarks>");
@ -185,7 +186,7 @@ public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase
sb.AppendLine(" {"); sb.AppendLine(" {");
sb.AppendLine(" get"); sb.AppendLine(" get");
sb.AppendLine(" {"); sb.AppendLine(" {");
sb.AppendLine(" var context = _context;"); sb.AppendLine(" var context = _gFrameworkContextAwareContext;");
sb.AppendLine(" if (context is not null)"); sb.AppendLine(" if (context is not null)");
sb.AppendLine(" {"); sb.AppendLine(" {");
sb.AppendLine(" return context;"); sb.AppendLine(" return context;");
@ -193,13 +194,13 @@ public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase
sb.AppendLine(); sb.AppendLine();
sb.AppendLine(" // 在同一个同步域内协调懒加载与 provider 切换,避免读取到被并发重置的空提供者。"); sb.AppendLine(" // 在同一个同步域内协调懒加载与 provider 切换,避免读取到被并发重置的空提供者。");
sb.AppendLine( sb.AppendLine(
" // provider 的 GetContext() 会在持有 _contextSync 时执行;自定义 provider 必须避免在该调用链内回调 SetContextProvider/ResetContextProvider 或形成反向锁顺序。"); " // provider 的 GetContext() 会在持有 _gFrameworkContextAwareSync 时执行;自定义 provider 必须避免在该调用链内回调 SetContextProvider/ResetContextProvider 或形成反向锁顺序。");
sb.AppendLine(" lock (_contextSync)"); sb.AppendLine(" lock (_gFrameworkContextAwareSync)");
sb.AppendLine(" {"); sb.AppendLine(" {");
sb.AppendLine( sb.AppendLine(
" _contextProvider ??= new global::GFramework.Core.Architectures.GameContextProvider();"); " _gFrameworkContextAwareProvider ??= new global::GFramework.Core.Architectures.GameContextProvider();");
sb.AppendLine(" _context ??= _contextProvider.GetContext();"); sb.AppendLine(" _gFrameworkContextAwareContext ??= _gFrameworkContextAwareProvider.GetContext();");
sb.AppendLine(" return _context;"); sb.AppendLine(" return _gFrameworkContextAwareContext;");
sb.AppendLine(" }"); sb.AppendLine(" }");
sb.AppendLine(" }"); sb.AppendLine(" }");
sb.AppendLine(" }"); sb.AppendLine(" }");
@ -216,6 +217,8 @@ public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase
sb.AppendLine(" /// 配置当前生成类型共享的上下文提供者。"); sb.AppendLine(" /// 配置当前生成类型共享的上下文提供者。");
sb.AppendLine(" /// </summary>"); sb.AppendLine(" /// </summary>");
sb.AppendLine(" /// <param name=\"provider\">后续懒加载上下文时要使用的提供者实例。</param>"); sb.AppendLine(" /// <param name=\"provider\">后续懒加载上下文时要使用的提供者实例。</param>");
sb.AppendLine(
" /// <exception cref=\"global::System.ArgumentNullException\">当 <paramref name=\"provider\" /> 为 null 时抛出。</exception>");
sb.AppendLine(" /// <remarks>"); sb.AppendLine(" /// <remarks>");
sb.AppendLine(" /// 该方法使用与 <see cref=\"Context\" /> 相同的同步锁,避免提供者切换与惰性初始化交错。"); sb.AppendLine(" /// 该方法使用与 <see cref=\"Context\" /> 相同的同步锁,避免提供者切换与惰性初始化交错。");
sb.AppendLine( sb.AppendLine(
@ -225,9 +228,10 @@ public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase
sb.AppendLine( sb.AppendLine(
" public static void SetContextProvider(global::GFramework.Core.Abstractions.Architectures.IArchitectureContextProvider provider)"); " public static void SetContextProvider(global::GFramework.Core.Abstractions.Architectures.IArchitectureContextProvider provider)");
sb.AppendLine(" {"); sb.AppendLine(" {");
sb.AppendLine(" lock (_contextSync)"); sb.AppendLine(" global::System.ArgumentNullException.ThrowIfNull(provider);");
sb.AppendLine(" lock (_gFrameworkContextAwareSync)");
sb.AppendLine(" {"); sb.AppendLine(" {");
sb.AppendLine(" _contextProvider = provider;"); sb.AppendLine(" _gFrameworkContextAwareProvider = provider;");
sb.AppendLine(" }"); sb.AppendLine(" }");
sb.AppendLine(" }"); sb.AppendLine(" }");
sb.AppendLine(); sb.AppendLine();
@ -242,9 +246,9 @@ public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase
sb.AppendLine(" /// </remarks>"); sb.AppendLine(" /// </remarks>");
sb.AppendLine(" public static void ResetContextProvider()"); sb.AppendLine(" public static void ResetContextProvider()");
sb.AppendLine(" {"); sb.AppendLine(" {");
sb.AppendLine(" lock (_contextSync)"); sb.AppendLine(" lock (_gFrameworkContextAwareSync)");
sb.AppendLine(" {"); sb.AppendLine(" {");
sb.AppendLine(" _contextProvider = null;"); sb.AppendLine(" _gFrameworkContextAwareProvider = null;");
sb.AppendLine(" }"); sb.AppendLine(" }");
sb.AppendLine(" }"); sb.AppendLine(" }");
sb.AppendLine(); sb.AppendLine();
@ -316,9 +320,9 @@ public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase
{ {
case "SetContext": case "SetContext":
sb.AppendLine(" // 与 Context getter 共享同一同步协议,避免显式注入被并发懒加载覆盖。"); sb.AppendLine(" // 与 Context getter 共享同一同步协议,避免显式注入被并发懒加载覆盖。");
sb.AppendLine(" lock (_contextSync)"); sb.AppendLine(" lock (_gFrameworkContextAwareSync)");
sb.AppendLine(" {"); sb.AppendLine(" {");
sb.AppendLine(" _context = context;"); sb.AppendLine(" _gFrameworkContextAwareContext = context;");
sb.AppendLine(" }"); sb.AppendLine(" }");
break; break;

View File

@ -16,6 +16,21 @@ namespace GFramework.Core.Functional;
/// <summary> /// <summary>
/// 表示可能存在或不存在的值,用于替代 null 引用的函数式编程类型 /// 表示可能存在或不存在的值,用于替代 null 引用的函数式编程类型
/// </summary> /// </summary>
/// <remarks>
/// <para>
/// <see cref="Option{T}" /> 只表示两种显式状态:通过 <see cref="Some(T)" /> 创建的有值状态,以及
/// <see cref="None" /> 表示的无值状态;调用方不应把 <see cref="None" /> 当作 <see langword="null" /> 的别名。
/// </para>
/// <para>
/// <see cref="Some(T)" /> 会拒绝 <see langword="null" />,因此引用类型和可空引用类型参数都必须包装真实值;访问方应优先通过
/// <see cref="IsSome" />、<see cref="IsNone" />、模式匹配或 <c>Match</c>/<c>Map</c> 等函数式 API 消费结果,而不是假设默认值
/// 与无值状态等价。
/// </para>
/// <para>
/// 该结构体是不可变值类型;一旦创建,其状态与内部值不会再改变。但在 <see cref="IsNone" /> 为 <see langword="true" /> 时,
/// 调用需要真实值的方法仍应遵守各成员自身的契约与异常说明。
/// </para>
/// </remarks>
/// <typeparam name="T">值的类型</typeparam> /// <typeparam name="T">值的类型</typeparam>
public readonly struct Option<T> : IEquatable<Option<T>> public readonly struct Option<T> : IEquatable<Option<T>>
{ {

View File

@ -0,0 +1,254 @@
namespace GFramework.Cqrs.SourceGenerators.Cqrs;
/// <summary>
/// 为当前编译程序集生成 CQRS 处理器注册器,以减少运行时的程序集反射扫描成本。
/// </summary>
public sealed partial class CqrsHandlerRegistryGenerator
{
private readonly record struct HandlerRegistrationSpec(
string HandlerInterfaceDisplayName,
string ImplementationTypeDisplayName,
string HandlerInterfaceLogName,
string ImplementationLogName);
private readonly record struct ReflectedImplementationRegistrationSpec(
string HandlerInterfaceDisplayName,
string HandlerInterfaceLogName);
private readonly record struct OrderedRegistrationSpec(
string HandlerInterfaceLogName,
OrderedRegistrationKind Kind,
int Index);
private readonly record struct GeneratedRegistrySourceShape(
bool HasReflectedImplementationRegistrations,
bool HasPreciseReflectedRegistrations,
bool HasReflectionTypeLookups,
bool HasExternalAssemblyTypeLookups)
{
public bool RequiresRegistryAssemblyVariable =>
HasReflectedImplementationRegistrations ||
HasPreciseReflectedRegistrations ||
HasReflectionTypeLookups;
}
private enum OrderedRegistrationKind
{
Direct,
ReflectedImplementation,
PreciseReflected
}
private sealed record RuntimeTypeReferenceSpec(
string? TypeDisplayName,
string? ReflectionTypeMetadataName,
string? ReflectionAssemblyName,
RuntimeTypeReferenceSpec? ArrayElementTypeReference,
int ArrayRank,
RuntimeTypeReferenceSpec? PointerElementTypeReference,
RuntimeTypeReferenceSpec? GenericTypeDefinitionReference,
ImmutableArray<RuntimeTypeReferenceSpec> GenericTypeArguments)
{
public static RuntimeTypeReferenceSpec FromDirectReference(string typeDisplayName)
{
return new RuntimeTypeReferenceSpec(
typeDisplayName,
null,
null,
null,
0,
null,
null,
ImmutableArray<RuntimeTypeReferenceSpec>.Empty);
}
public static RuntimeTypeReferenceSpec FromReflectionLookup(string reflectionTypeMetadataName)
{
return new RuntimeTypeReferenceSpec(
null,
reflectionTypeMetadataName,
null,
null,
0,
null,
null,
ImmutableArray<RuntimeTypeReferenceSpec>.Empty);
}
public static RuntimeTypeReferenceSpec FromExternalReflectionLookup(
string reflectionAssemblyName,
string reflectionTypeMetadataName)
{
return new RuntimeTypeReferenceSpec(
null,
reflectionTypeMetadataName,
reflectionAssemblyName,
null,
0,
null,
null,
ImmutableArray<RuntimeTypeReferenceSpec>.Empty);
}
public static RuntimeTypeReferenceSpec FromArray(RuntimeTypeReferenceSpec elementTypeReference, int arrayRank)
{
return new RuntimeTypeReferenceSpec(
null,
null,
null,
elementTypeReference,
arrayRank,
null,
null,
ImmutableArray<RuntimeTypeReferenceSpec>.Empty);
}
public static RuntimeTypeReferenceSpec FromConstructedGeneric(
RuntimeTypeReferenceSpec genericTypeDefinitionReference,
ImmutableArray<RuntimeTypeReferenceSpec> genericTypeArguments)
{
return new RuntimeTypeReferenceSpec(
null,
null,
null,
null,
0,
null,
genericTypeDefinitionReference,
genericTypeArguments);
}
}
private readonly record struct PreciseReflectedRegistrationSpec(
string OpenHandlerTypeDisplayName,
string HandlerInterfaceLogName,
ImmutableArray<RuntimeTypeReferenceSpec> ServiceTypeArguments);
private readonly record struct ImplementationRegistrationSpec(
string ImplementationTypeDisplayName,
string ImplementationLogName,
ImmutableArray<HandlerRegistrationSpec> DirectRegistrations,
ImmutableArray<ReflectedImplementationRegistrationSpec> ReflectedImplementationRegistrations,
ImmutableArray<PreciseReflectedRegistrationSpec> PreciseReflectedRegistrations,
string? ReflectionTypeMetadataName,
string? ReflectionFallbackHandlerTypeMetadataName);
private readonly struct HandlerCandidateAnalysis : IEquatable<HandlerCandidateAnalysis>
{
public HandlerCandidateAnalysis(
string implementationTypeDisplayName,
string implementationLogName,
ImmutableArray<HandlerRegistrationSpec> registrations,
ImmutableArray<ReflectedImplementationRegistrationSpec> reflectedImplementationRegistrations,
ImmutableArray<PreciseReflectedRegistrationSpec> preciseReflectedRegistrations,
string? reflectionTypeMetadataName,
string? reflectionFallbackHandlerTypeMetadataName)
{
ImplementationTypeDisplayName = implementationTypeDisplayName;
ImplementationLogName = implementationLogName;
Registrations = registrations;
ReflectedImplementationRegistrations = reflectedImplementationRegistrations;
PreciseReflectedRegistrations = preciseReflectedRegistrations;
ReflectionTypeMetadataName = reflectionTypeMetadataName;
ReflectionFallbackHandlerTypeMetadataName = reflectionFallbackHandlerTypeMetadataName;
}
public string ImplementationTypeDisplayName { get; }
public string ImplementationLogName { get; }
public ImmutableArray<HandlerRegistrationSpec> Registrations { get; }
public ImmutableArray<ReflectedImplementationRegistrationSpec> ReflectedImplementationRegistrations { get; }
public ImmutableArray<PreciseReflectedRegistrationSpec> PreciseReflectedRegistrations { get; }
public string? ReflectionTypeMetadataName { get; }
public string? ReflectionFallbackHandlerTypeMetadataName { get; }
public bool Equals(HandlerCandidateAnalysis other)
{
if (!string.Equals(ImplementationTypeDisplayName, other.ImplementationTypeDisplayName,
StringComparison.Ordinal) ||
!string.Equals(ImplementationLogName, other.ImplementationLogName, StringComparison.Ordinal) ||
!string.Equals(ReflectionTypeMetadataName, other.ReflectionTypeMetadataName,
StringComparison.Ordinal) ||
!string.Equals(
ReflectionFallbackHandlerTypeMetadataName,
other.ReflectionFallbackHandlerTypeMetadataName,
StringComparison.Ordinal) ||
Registrations.Length != other.Registrations.Length ||
ReflectedImplementationRegistrations.Length != other.ReflectedImplementationRegistrations.Length ||
PreciseReflectedRegistrations.Length != other.PreciseReflectedRegistrations.Length)
{
return false;
}
for (var index = 0; index < Registrations.Length; index++)
{
if (!Registrations[index].Equals(other.Registrations[index]))
return false;
}
for (var index = 0; index < ReflectedImplementationRegistrations.Length; index++)
{
if (!ReflectedImplementationRegistrations[index].Equals(
other.ReflectedImplementationRegistrations[index]))
{
return false;
}
}
for (var index = 0; index < PreciseReflectedRegistrations.Length; index++)
{
if (!PreciseReflectedRegistrations[index].Equals(other.PreciseReflectedRegistrations[index]))
return false;
}
return true;
}
public override bool Equals(object? obj)
{
return obj is HandlerCandidateAnalysis other && Equals(other);
}
public override int GetHashCode()
{
unchecked
{
var hashCode = StringComparer.Ordinal.GetHashCode(ImplementationTypeDisplayName);
hashCode = (hashCode * 397) ^ StringComparer.Ordinal.GetHashCode(ImplementationLogName);
hashCode = (hashCode * 397) ^
(ReflectionTypeMetadataName is null
? 0
: StringComparer.Ordinal.GetHashCode(ReflectionTypeMetadataName));
hashCode = (hashCode * 397) ^
(ReflectionFallbackHandlerTypeMetadataName is null
? 0
: StringComparer.Ordinal.GetHashCode(ReflectionFallbackHandlerTypeMetadataName));
foreach (var registration in Registrations)
{
hashCode = (hashCode * 397) ^ registration.GetHashCode();
}
foreach (var reflectedImplementationRegistration in ReflectedImplementationRegistrations)
{
hashCode = (hashCode * 397) ^ reflectedImplementationRegistration.GetHashCode();
}
foreach (var preciseReflectedRegistration in PreciseReflectedRegistrations)
{
hashCode = (hashCode * 397) ^ preciseReflectedRegistration.GetHashCode();
}
return hashCode;
}
}
}
private readonly record struct GenerationEnvironment(
bool GenerationEnabled,
bool SupportsReflectionFallbackAttribute);
}

View File

@ -0,0 +1,283 @@
namespace GFramework.Cqrs.SourceGenerators.Cqrs;
/// <summary>
/// 为当前编译程序集生成 CQRS 处理器注册器,以减少运行时的程序集反射扫描成本。
/// </summary>
public sealed partial class CqrsHandlerRegistryGenerator
{
/// <summary>
/// 为无法直接在生成代码中书写的关闭处理器接口构造精确的运行时注册描述。
/// </summary>
/// <param name="compilation">
/// 当前生成轮次对应的编译上下文,用于判断类型是否属于当前程序集,从而决定是生成直接类型引用还是延迟到运行时反射解析。
/// </param>
/// <param name="handlerInterface">
/// 需要注册的关闭处理器接口。调用方应保证它来自受支持的 CQRS 处理器接口定义,并且其泛型参数顺序与运行时注册约定一致。
/// </param>
/// <param name="registration">
/// 当方法返回 <see langword="true" /> 时,包含开放泛型处理器类型和每个运行时类型实参的精确描述;
/// 当方法返回 <see langword="false" /> 时,为默认值,调用方应回退到基于实现类型的宽松反射发现路径。
/// </param>
/// <returns>
/// 当接口上的所有运行时类型引用都能在生成阶段被稳定描述时返回 <see langword="true" />
/// 只要任一泛型实参无法安全编码到生成输出中,就返回 <see langword="false" />。
/// </returns>
private static bool TryCreatePreciseReflectedRegistration(
Compilation compilation,
INamedTypeSymbol handlerInterface,
out PreciseReflectedRegistrationSpec registration)
{
var openHandlerTypeDisplayName = handlerInterface.OriginalDefinition
.ConstructUnboundGenericType()
.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
var typeArguments =
ImmutableArray.CreateBuilder<RuntimeTypeReferenceSpec>(handlerInterface.TypeArguments.Length);
foreach (var typeArgument in handlerInterface.TypeArguments)
{
if (!TryCreateRuntimeTypeReference(compilation, typeArgument, out var runtimeTypeReference))
{
registration = default;
return false;
}
typeArguments.Add(runtimeTypeReference!);
}
registration = new PreciseReflectedRegistrationSpec(
openHandlerTypeDisplayName,
GetLogDisplayName(handlerInterface),
typeArguments.ToImmutable());
return true;
}
/// <summary>
/// 将 Roslyn 类型符号转换为生成注册器可消费的运行时类型引用描述。
/// </summary>
/// <param name="compilation">
/// 当前编译上下文,用于区分可直接引用的外部可访问类型与必须通过当前程序集运行时反射查找的内部类型。
/// </param>
/// <param name="type">
/// 需要转换的类型符号。该方法会递归处理数组元素类型和已构造泛型的类型实参,但不会为未绑定泛型或类型参数生成引用。
/// </param>
/// <param name="runtimeTypeReference">
/// 当方法返回 <see langword="true" /> 时,包含可直接引用、数组、已构造泛型或反射查找中的一种运行时表示;
/// 当方法返回 <see langword="false" /> 时为 <see langword="null" />,调用方应回退到更宽泛的实现类型反射扫描策略。
/// </param>
/// <returns>
/// 当 <paramref name="type" /> 及其递归子结构都能映射为稳定的运行时引用时返回 <see langword="true" />
/// 若遇到类型参数、无法访问的运行时结构,或任一递归分支无法表示,则返回 <see langword="false" />。
/// </returns>
private static bool TryCreateRuntimeTypeReference(
Compilation compilation,
ITypeSymbol type,
out RuntimeTypeReferenceSpec? runtimeTypeReference)
{
// CLR forbids pointer and function-pointer types from being used as generic arguments.
// CQRS handler contracts are generic interfaces, so emitting runtime reconstruction code for these
// shapes would only defer the failure to MakeGenericType(...) at runtime.
if (type is IPointerTypeSymbol or IFunctionPointerTypeSymbol)
{
runtimeTypeReference = null;
return false;
}
if (CanReferenceFromGeneratedRegistry(compilation, type))
{
runtimeTypeReference = RuntimeTypeReferenceSpec.FromDirectReference(
type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat));
return true;
}
if (type is IArrayTypeSymbol arrayType &&
TryCreateRuntimeTypeReference(compilation, arrayType.ElementType, out var elementTypeReference))
{
runtimeTypeReference = RuntimeTypeReferenceSpec.FromArray(elementTypeReference!, arrayType.Rank);
return true;
}
if (type is INamedTypeSymbol genericNamedType &&
genericNamedType.IsGenericType &&
!genericNamedType.IsUnboundGenericType)
{
return TryCreateConstructedGenericRuntimeTypeReference(
compilation,
genericNamedType,
out runtimeTypeReference);
}
if (type is INamedTypeSymbol namedType)
{
runtimeTypeReference = CreateNamedRuntimeTypeReference(compilation, namedType);
return true;
}
runtimeTypeReference = null;
return false;
}
/// <summary>
/// 为已构造泛型类型构造运行时类型引用,并递归验证每个泛型实参都可以稳定编码到生成输出中。
/// </summary>
/// <param name="compilation">当前生成轮次的编译上下文。</param>
/// <param name="genericNamedType">需要表示的已构造泛型类型。</param>
/// <param name="runtimeTypeReference">
/// 当方法返回 <see langword="true" /> 时,包含泛型定义和泛型实参的运行时重建描述。
/// </param>
/// <returns>当泛型定义和全部泛型实参都能表达时返回 <see langword="true" />。</returns>
private static bool TryCreateConstructedGenericRuntimeTypeReference(
Compilation compilation,
INamedTypeSymbol genericNamedType,
out RuntimeTypeReferenceSpec? runtimeTypeReference)
{
if (!TryCreateGenericTypeDefinitionReference(
compilation,
genericNamedType,
out var genericTypeDefinitionReference))
{
runtimeTypeReference = null;
return false;
}
var genericTypeArguments =
ImmutableArray.CreateBuilder<RuntimeTypeReferenceSpec>(genericNamedType.TypeArguments.Length);
foreach (var typeArgument in genericNamedType.TypeArguments)
{
if (!TryCreateRuntimeTypeReference(compilation, typeArgument, out var genericTypeArgumentReference))
{
runtimeTypeReference = null;
return false;
}
genericTypeArguments.Add(genericTypeArgumentReference!);
}
runtimeTypeReference = RuntimeTypeReferenceSpec.FromConstructedGeneric(
genericTypeDefinitionReference!,
genericTypeArguments.ToImmutable());
return true;
}
/// <summary>
/// 为无法直接书写的命名类型选择当前程序集反射查找或外部程序集反射查找表示。
/// </summary>
/// <param name="compilation">当前生成轮次的编译上下文。</param>
/// <param name="namedType">需要在运行时解析的命名类型。</param>
/// <returns>适合写入生成注册器的命名类型运行时引用。</returns>
private static RuntimeTypeReferenceSpec CreateNamedRuntimeTypeReference(
Compilation compilation,
INamedTypeSymbol namedType)
{
if (SymbolEqualityComparer.Default.Equals(namedType.ContainingAssembly, compilation.Assembly))
return RuntimeTypeReferenceSpec.FromReflectionLookup(GetReflectionTypeMetadataName(namedType));
return RuntimeTypeReferenceSpec.FromExternalReflectionLookup(
namedType.ContainingAssembly.Identity.ToString(),
GetReflectionTypeMetadataName(namedType));
}
/// <summary>
/// 为已构造泛型类型解析其泛型定义的运行时引用描述。
/// </summary>
/// <param name="compilation">
/// 当前编译上下文,用于判断泛型定义是否应以内联类型引用形式生成,或在运行时通过当前程序集反射解析。
/// </param>
/// <param name="genericNamedType">
/// 已构造的泛型类型。该方法只处理其原始泛型定义,不负责递归解析类型实参。
/// </param>
/// <param name="genericTypeDefinitionReference">
/// 当方法返回 <see langword="true" /> 时,包含泛型定义的直接引用或反射查找描述;
/// 当方法返回 <see langword="false" /> 时为 <see langword="null" />,调用方应停止精确构造并回退到更保守的注册路径。
/// </param>
/// <returns>
/// 当泛型定义能够以稳定方式编码到生成输出中时返回 <see langword="true" />
/// 若泛型定义既不能直接引用,也不属于当前程序集可供反射查找,则返回 <see langword="false" />。
/// </returns>
private static bool TryCreateGenericTypeDefinitionReference(
Compilation compilation,
INamedTypeSymbol genericNamedType,
out RuntimeTypeReferenceSpec? genericTypeDefinitionReference)
{
var genericTypeDefinition = genericNamedType.OriginalDefinition;
if (CanReferenceFromGeneratedRegistry(compilation, genericTypeDefinition))
{
genericTypeDefinitionReference = RuntimeTypeReferenceSpec.FromDirectReference(
genericTypeDefinition
.ConstructUnboundGenericType()
.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat));
return true;
}
if (SymbolEqualityComparer.Default.Equals(genericTypeDefinition.ContainingAssembly, compilation.Assembly))
{
genericTypeDefinitionReference = RuntimeTypeReferenceSpec.FromReflectionLookup(
GetReflectionTypeMetadataName(genericTypeDefinition));
return true;
}
genericTypeDefinitionReference = RuntimeTypeReferenceSpec.FromExternalReflectionLookup(
genericTypeDefinition.ContainingAssembly.Identity.ToString(),
GetReflectionTypeMetadataName(genericTypeDefinition));
return true;
}
private static bool CanReferenceFromGeneratedRegistry(Compilation compilation, ITypeSymbol type)
{
switch (type)
{
case IArrayTypeSymbol arrayType:
return CanReferenceFromGeneratedRegistry(compilation, arrayType.ElementType);
case INamedTypeSymbol namedType:
if (!compilation.IsSymbolAccessibleWithin(namedType, compilation.Assembly, throughType: null))
return false;
foreach (var typeArgument in namedType.TypeArguments)
{
if (!CanReferenceFromGeneratedRegistry(compilation, typeArgument))
return false;
}
return true;
case IPointerTypeSymbol:
case IFunctionPointerTypeSymbol:
return false;
case ITypeParameterSymbol:
return false;
default:
// Treat other Roslyn type kinds, such as dynamic or unresolved error types, as referenceable for now.
// If a real-world case proves unsafe, tighten this branch instead of broadening the named-type path above.
return true;
}
}
private static bool ContainsExternalAssemblyTypeLookup(RuntimeTypeReferenceSpec runtimeTypeReference)
{
if (!string.IsNullOrWhiteSpace(runtimeTypeReference.ReflectionAssemblyName))
return true;
if (runtimeTypeReference.ArrayElementTypeReference is not null &&
ContainsExternalAssemblyTypeLookup(runtimeTypeReference.ArrayElementTypeReference))
{
return true;
}
if (runtimeTypeReference.PointerElementTypeReference is not null &&
ContainsExternalAssemblyTypeLookup(runtimeTypeReference.PointerElementTypeReference))
{
return true;
}
if (runtimeTypeReference.GenericTypeDefinitionReference is not null &&
ContainsExternalAssemblyTypeLookup(runtimeTypeReference.GenericTypeDefinitionReference))
{
return true;
}
foreach (var genericTypeArgument in runtimeTypeReference.GenericTypeArguments)
{
if (ContainsExternalAssemblyTypeLookup(genericTypeArgument))
return true;
}
return false;
}
}

View File

@ -0,0 +1,851 @@
namespace GFramework.Cqrs.SourceGenerators.Cqrs;
/// <summary>
/// 为当前编译程序集生成 CQRS 处理器注册器,以减少运行时的程序集反射扫描成本。
/// </summary>
public sealed partial class CqrsHandlerRegistryGenerator
{
/// <summary>
/// 生成程序集级 CQRS handler 注册器源码。
/// </summary>
/// <param name="generationEnvironment">
/// 当前轮次的生成环境,用于决定 runtime 是否提供 <c>CqrsReflectionFallbackAttribute</c> 契约,以及是否需要在输出中发射对应的程序集级元数据。
/// </param>
/// <param name="registrations">
/// 已整理并排序的 handler 注册描述。方法会据此生成 <c>CqrsHandlerRegistry.g.cs</c>,其中包含直接注册、实现类型反射注册、精确运行时类型查找等分支。
/// </param>
/// <param name="fallbackHandlerTypeMetadataNames">
/// 仍需依赖程序集级 reflection fallback 元数据恢复的 handler 元数据名称集合。
/// 调用方必须先确保:若该集合非空,则 <paramref name="generationEnvironment" /> 已声明支持对应的 fallback attribute 契约;
/// 否则应在进入本方法前报告诊断并放弃生成,而不是输出会静默漏注册的半成品注册器。
/// </param>
/// <returns>完整的注册器源代码文本。</returns>
/// <remarks>
/// 当 <paramref name="fallbackHandlerTypeMetadataNames" /> 为空时,输出只包含程序集级 <c>CqrsHandlerRegistryAttribute</c> 和注册器实现。
/// 当其非空且 runtime 合同可用时,输出还会附带程序集级 <c>CqrsReflectionFallbackAttribute</c>,让运行时补齐生成阶段无法精确表达的剩余 handler。
/// 该方法本身不报告诊断“fallback 必需但 runtime 契约缺失”的错误由调用方在进入本方法前处理。
/// </remarks>
private static string GenerateSource(
GenerationEnvironment generationEnvironment,
IReadOnlyList<ImplementationRegistrationSpec> registrations,
IReadOnlyList<string> fallbackHandlerTypeMetadataNames)
{
var sourceShape = CreateGeneratedRegistrySourceShape(registrations);
var builder = new StringBuilder();
AppendGeneratedSourcePreamble(builder, generationEnvironment, fallbackHandlerTypeMetadataNames);
AppendGeneratedRegistryType(builder, registrations, sourceShape);
return builder.ToString();
}
/// <summary>
/// 预先计算生成注册器需要的辅助分支,让主源码发射流程保持线性且避免重复扫描注册集合。
/// </summary>
/// <param name="registrations">已整理并排序的 handler 注册描述。</param>
/// <returns>当前生成输出需要启用的结构分支。</returns>
private static GeneratedRegistrySourceShape CreateGeneratedRegistrySourceShape(
IReadOnlyList<ImplementationRegistrationSpec> registrations)
{
var hasReflectedImplementationRegistrations = registrations.Any(static registration =>
!registration.ReflectedImplementationRegistrations.IsDefaultOrEmpty);
var hasPreciseReflectedRegistrations = registrations.Any(static registration =>
!registration.PreciseReflectedRegistrations.IsDefaultOrEmpty);
var hasReflectionTypeLookups = registrations.Any(static registration =>
!string.IsNullOrWhiteSpace(registration.ReflectionTypeMetadataName));
var hasExternalAssemblyTypeLookups = registrations.Any(static registration =>
registration.PreciseReflectedRegistrations.Any(static preciseRegistration =>
preciseRegistration.ServiceTypeArguments.Any(ContainsExternalAssemblyTypeLookup)));
return new GeneratedRegistrySourceShape(
hasReflectedImplementationRegistrations,
hasPreciseReflectedRegistrations,
hasReflectionTypeLookups,
hasExternalAssemblyTypeLookups);
}
/// <summary>
/// 发射生成文件头、nullable 指令以及注册器所需的程序集级元数据特性。
/// </summary>
/// <param name="builder">生成源码构造器。</param>
/// <param name="generationEnvironment">当前轮次的生成环境。</param>
/// <param name="fallbackHandlerTypeMetadataNames">需要程序集级 reflection fallback 的 handler 元数据名称。</param>
private static void AppendGeneratedSourcePreamble(
StringBuilder builder,
GenerationEnvironment generationEnvironment,
IReadOnlyList<string> fallbackHandlerTypeMetadataNames)
{
builder.AppendLine("// <auto-generated />");
builder.AppendLine("#nullable enable");
builder.AppendLine();
if (generationEnvironment.SupportsReflectionFallbackAttribute && fallbackHandlerTypeMetadataNames.Count > 0)
{
AppendReflectionFallbackAttribute(builder, fallbackHandlerTypeMetadataNames);
builder.AppendLine();
}
builder.Append("[assembly: global::");
builder.Append(CqrsRuntimeNamespace);
builder.Append(".CqrsHandlerRegistryAttribute(typeof(global::");
builder.Append(GeneratedNamespace);
builder.Append('.');
builder.Append(GeneratedTypeName);
builder.AppendLine("))]");
}
/// <summary>
/// 发射程序集级 reflection fallback 元数据特性,供运行时补齐生成阶段无法精确表达的 handler。
/// </summary>
/// <param name="builder">生成源码构造器。</param>
/// <param name="fallbackHandlerTypeMetadataNames">需要写入特性的 handler 元数据名称。</param>
private static void AppendReflectionFallbackAttribute(
StringBuilder builder,
IReadOnlyList<string> fallbackHandlerTypeMetadataNames)
{
builder.Append("[assembly: global::");
builder.Append(CqrsRuntimeNamespace);
builder.Append(".CqrsReflectionFallbackAttribute(");
for (var index = 0; index < fallbackHandlerTypeMetadataNames.Count; index++)
{
if (index > 0)
builder.Append(", ");
builder.Append('"');
builder.Append(EscapeStringLiteral(fallbackHandlerTypeMetadataNames[index]));
builder.Append('"');
}
builder.AppendLine(")]");
}
/// <summary>
/// 发射生成注册器类型本体,包括 <c>Register</c> 方法和运行时反射辅助方法。
/// </summary>
/// <param name="builder">生成源码构造器。</param>
/// <param name="registrations">已排序的 handler 注册描述。</param>
/// <param name="sourceShape">当前输出需要启用的结构分支。</param>
private static void AppendGeneratedRegistryType(
StringBuilder builder,
IReadOnlyList<ImplementationRegistrationSpec> registrations,
GeneratedRegistrySourceShape sourceShape)
{
builder.AppendLine();
builder.Append("namespace ");
builder.Append(GeneratedNamespace);
builder.AppendLine(";");
builder.AppendLine();
builder.Append("internal sealed class ");
builder.Append(GeneratedTypeName);
builder.Append(" : global::");
builder.Append(CqrsRuntimeNamespace);
builder.AppendLine(".ICqrsHandlerRegistry");
builder.AppendLine("{");
AppendRegisterMethod(builder, registrations, sourceShape);
if (sourceShape.HasExternalAssemblyTypeLookups)
{
builder.AppendLine();
AppendReflectionHelpers(builder, sourceShape.HasExternalAssemblyTypeLookups);
}
builder.AppendLine("}");
}
/// <summary>
/// 发射注册器的 <c>Register</c> 方法,保持直接注册和反射注册之间的原始稳定排序。
/// </summary>
/// <param name="builder">生成源码构造器。</param>
/// <param name="registrations">已排序的 handler 注册描述。</param>
/// <param name="sourceShape">当前输出需要启用的结构分支。</param>
private static void AppendRegisterMethod(
StringBuilder builder,
IReadOnlyList<ImplementationRegistrationSpec> registrations,
GeneratedRegistrySourceShape sourceShape)
{
builder.Append(
" public void Register(global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, global::");
builder.Append(LoggingNamespace);
builder.AppendLine(".ILogger logger)");
builder.AppendLine(" {");
builder.AppendLine(" if (services is null)");
builder.AppendLine(" throw new global::System.ArgumentNullException(nameof(services));");
builder.AppendLine(" if (logger is null)");
builder.AppendLine(" throw new global::System.ArgumentNullException(nameof(logger));");
if (sourceShape.RequiresRegistryAssemblyVariable)
{
builder.AppendLine();
builder.Append(" var registryAssembly = typeof(global::");
builder.Append(GeneratedNamespace);
builder.Append('.');
builder.Append(GeneratedTypeName);
builder.AppendLine(").Assembly;");
}
if (registrations.Count > 0)
builder.AppendLine();
for (var registrationIndex = 0; registrationIndex < registrations.Count; registrationIndex++)
{
var registration = registrations[registrationIndex];
if (!registration.ReflectedImplementationRegistrations.IsDefaultOrEmpty ||
!registration.PreciseReflectedRegistrations.IsDefaultOrEmpty)
{
AppendOrderedImplementationRegistrations(builder, registration, registrationIndex);
}
else if (!registration.DirectRegistrations.IsDefaultOrEmpty)
{
AppendDirectRegistrations(builder, registration);
}
}
builder.AppendLine(" }");
}
private static void AppendDirectRegistrations(
StringBuilder builder,
ImplementationRegistrationSpec registration)
{
foreach (var directRegistration in registration.DirectRegistrations)
{
AppendServiceRegistration(
builder,
$"typeof({directRegistration.HandlerInterfaceDisplayName})",
$"typeof({directRegistration.ImplementationTypeDisplayName})",
" ");
AppendRegistrationLog(
builder,
directRegistration.ImplementationLogName,
directRegistration.HandlerInterfaceLogName,
" ");
}
}
/// <summary>
/// 发射 <c>AddTransient</c> 调用,调用方负责传入已经按当前分支解析好的 service 和 implementation 表达式。
/// </summary>
/// <param name="builder">生成源码构造器。</param>
/// <param name="serviceTypeExpression">生成代码中的服务类型表达式。</param>
/// <param name="implementationTypeExpression">生成代码中的实现类型表达式。</param>
/// <param name="indent">当前生成语句的缩进。</param>
private static void AppendServiceRegistration(
StringBuilder builder,
string serviceTypeExpression,
string implementationTypeExpression,
string indent)
{
builder.Append(indent);
builder.AppendLine("global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddTransient(");
builder.Append(indent);
builder.AppendLine(" services,");
builder.Append(indent);
builder.Append(" ");
builder.Append(serviceTypeExpression);
builder.AppendLine(",");
builder.Append(indent);
builder.Append(" ");
builder.Append(implementationTypeExpression);
builder.AppendLine(");");
}
/// <summary>
/// 发射与注册语句配套的调试日志,保持所有生成注册路径的日志文本完全一致。
/// </summary>
/// <param name="builder">生成源码构造器。</param>
/// <param name="implementationLogName">实现类型日志名。</param>
/// <param name="handlerInterfaceLogName">handler 接口日志名。</param>
/// <param name="indent">当前生成语句的缩进。</param>
private static void AppendRegistrationLog(
StringBuilder builder,
string implementationLogName,
string handlerInterfaceLogName,
string indent)
{
builder.Append(indent);
builder.Append("logger.Debug(\"Registered CQRS handler ");
builder.Append(EscapeStringLiteral(implementationLogName));
builder.Append(" as ");
builder.Append(EscapeStringLiteral(handlerInterfaceLogName));
builder.AppendLine(".\");");
}
private static void AppendOrderedImplementationRegistrations(
StringBuilder builder,
ImplementationRegistrationSpec registration,
int registrationIndex)
{
var orderedRegistrations = CreateOrderedRegistrations(registration);
var implementationVariableName = $"implementationType{registrationIndex}";
AppendImplementationTypeVariable(builder, registration, implementationVariableName);
builder.Append(" if (");
builder.Append(implementationVariableName);
builder.AppendLine(" is not null)");
builder.AppendLine(" {");
foreach (var orderedRegistration in orderedRegistrations)
{
AppendOrderedRegistration(
builder,
registration,
orderedRegistration,
registrationIndex,
implementationVariableName);
}
builder.AppendLine(" }");
}
/// <summary>
/// 合并直接注册、实现类型反射注册和精确反射注册,并按 handler 接口日志名排序以保持生成输出稳定。
/// </summary>
/// <param name="registration">单个实现类型聚合后的注册描述。</param>
/// <returns>带有来源类型和原始索引的有序注册列表。</returns>
private static List<OrderedRegistrationSpec> CreateOrderedRegistrations(ImplementationRegistrationSpec registration)
{
var orderedRegistrations = new List<OrderedRegistrationSpec>(
registration.DirectRegistrations.Length +
registration.ReflectedImplementationRegistrations.Length +
registration.PreciseReflectedRegistrations.Length);
for (var directIndex = 0; directIndex < registration.DirectRegistrations.Length; directIndex++)
{
orderedRegistrations.Add(new OrderedRegistrationSpec(
registration.DirectRegistrations[directIndex].HandlerInterfaceLogName,
OrderedRegistrationKind.Direct,
directIndex));
}
for (var reflectedIndex = 0;
reflectedIndex < registration.ReflectedImplementationRegistrations.Length;
reflectedIndex++)
{
orderedRegistrations.Add(new OrderedRegistrationSpec(
registration.ReflectedImplementationRegistrations[reflectedIndex].HandlerInterfaceLogName,
OrderedRegistrationKind.ReflectedImplementation,
reflectedIndex));
}
for (var preciseIndex = 0;
preciseIndex < registration.PreciseReflectedRegistrations.Length;
preciseIndex++)
{
orderedRegistrations.Add(new OrderedRegistrationSpec(
registration.PreciseReflectedRegistrations[preciseIndex].HandlerInterfaceLogName,
OrderedRegistrationKind.PreciseReflected,
preciseIndex));
}
orderedRegistrations.Sort(static (left, right) =>
StringComparer.Ordinal.Compare(left.HandlerInterfaceLogName, right.HandlerInterfaceLogName));
return orderedRegistrations;
}
/// <summary>
/// 发射实现类型变量。公开类型直接使用 <c>typeof</c>,不可直接引用的实现类型则从当前程序集反射解析。
/// </summary>
/// <param name="builder">生成源码构造器。</param>
/// <param name="registration">单个实现类型聚合后的注册描述。</param>
/// <param name="implementationVariableName">生成代码中的实现类型变量名。</param>
private static void AppendImplementationTypeVariable(
StringBuilder builder,
ImplementationRegistrationSpec registration,
string implementationVariableName)
{
if (string.IsNullOrWhiteSpace(registration.ReflectionTypeMetadataName))
{
builder.Append(" var ");
builder.Append(implementationVariableName);
builder.Append(" = typeof(");
builder.Append(registration.ImplementationTypeDisplayName);
builder.AppendLine(");");
}
else
{
builder.Append(" var ");
builder.Append(implementationVariableName);
builder.Append(" = registryAssembly.GetType(\"");
builder.Append(EscapeStringLiteral(registration.ReflectionTypeMetadataName!));
builder.AppendLine("\", throwOnError: false, ignoreCase: false);");
}
}
/// <summary>
/// 根据注册来源发射单条有序注册,确保混合直接和反射路径时仍按 handler 接口名稳定输出。
/// </summary>
/// <param name="builder">生成源码构造器。</param>
/// <param name="registration">单个实现类型聚合后的注册描述。</param>
/// <param name="orderedRegistration">带来源类型和原始索引的排序项。</param>
/// <param name="registrationIndex">实现类型在整体注册列表中的索引,用于生成稳定变量名。</param>
/// <param name="implementationVariableName">生成代码中的实现类型变量名。</param>
private static void AppendOrderedRegistration(
StringBuilder builder,
ImplementationRegistrationSpec registration,
OrderedRegistrationSpec orderedRegistration,
int registrationIndex,
string implementationVariableName)
{
switch (orderedRegistration.Kind)
{
case OrderedRegistrationKind.Direct:
AppendOrderedDirectRegistration(
builder,
registration,
registration.DirectRegistrations[orderedRegistration.Index],
implementationVariableName);
break;
case OrderedRegistrationKind.ReflectedImplementation:
AppendOrderedReflectedImplementationRegistration(
builder,
registration,
registration.ReflectedImplementationRegistrations[orderedRegistration.Index],
implementationVariableName);
break;
case OrderedRegistrationKind.PreciseReflected:
AppendOrderedPreciseReflectedRegistration(
builder,
registration,
registration.PreciseReflectedRegistrations[orderedRegistration.Index],
registrationIndex,
orderedRegistration.Index,
implementationVariableName);
break;
default:
throw new InvalidOperationException(
$"Unsupported ordered CQRS registration kind {orderedRegistration.Kind}.");
}
}
/// <summary>
/// 发射实现类型已通过变量解析、handler 接口可直接引用的直接注册语句。
/// </summary>
/// <param name="builder">生成源码构造器。</param>
/// <param name="registration">单个实现类型聚合后的注册描述。</param>
/// <param name="directRegistration">当前直接注册项。</param>
/// <param name="implementationVariableName">生成代码中的实现类型变量名。</param>
private static void AppendOrderedDirectRegistration(
StringBuilder builder,
ImplementationRegistrationSpec registration,
HandlerRegistrationSpec directRegistration,
string implementationVariableName)
{
AppendServiceRegistration(
builder,
$"typeof({directRegistration.HandlerInterfaceDisplayName})",
implementationVariableName,
" ");
AppendRegistrationLog(
builder,
registration.ImplementationLogName,
directRegistration.HandlerInterfaceLogName,
" ");
}
/// <summary>
/// 发射实现类型需要反射解析、handler 接口可直接引用的注册语句。
/// </summary>
/// <param name="builder">生成源码构造器。</param>
/// <param name="registration">单个实现类型聚合后的注册描述。</param>
/// <param name="reflectedRegistration">当前实现类型反射注册项。</param>
/// <param name="implementationVariableName">生成代码中的实现类型变量名。</param>
private static void AppendOrderedReflectedImplementationRegistration(
StringBuilder builder,
ImplementationRegistrationSpec registration,
ReflectedImplementationRegistrationSpec reflectedRegistration,
string implementationVariableName)
{
AppendServiceRegistration(
builder,
$"typeof({reflectedRegistration.HandlerInterfaceDisplayName})",
implementationVariableName,
" ");
AppendRegistrationLog(
builder,
registration.ImplementationLogName,
reflectedRegistration.HandlerInterfaceLogName,
" ");
}
/// <summary>
/// 发射 handler 接口需要运行时精确构造的注册语句。
/// </summary>
/// <param name="builder">生成源码构造器。</param>
/// <param name="registration">单个实现类型聚合后的注册描述。</param>
/// <param name="preciseRegistration">当前精确反射注册项。</param>
/// <param name="registrationIndex">实现类型在整体注册列表中的索引。</param>
/// <param name="orderedRegistrationIndex">当前注册项在原始精确反射注册集合中的索引。</param>
/// <param name="implementationVariableName">生成代码中的实现类型变量名。</param>
private static void AppendOrderedPreciseReflectedRegistration(
StringBuilder builder,
ImplementationRegistrationSpec registration,
PreciseReflectedRegistrationSpec preciseRegistration,
int registrationIndex,
int orderedRegistrationIndex,
string implementationVariableName)
{
var registrationVariablePrefix = $"serviceType{registrationIndex}_{orderedRegistrationIndex}";
AppendPreciseReflectedTypeResolution(
builder,
preciseRegistration.ServiceTypeArguments,
registrationVariablePrefix,
implementationVariableName,
preciseRegistration.OpenHandlerTypeDisplayName,
registration.ImplementationLogName,
preciseRegistration.HandlerInterfaceLogName,
3);
}
private static void AppendPreciseReflectedTypeResolution(
StringBuilder builder,
ImmutableArray<RuntimeTypeReferenceSpec> serviceTypeArguments,
string registrationVariablePrefix,
string implementationVariableName,
string openHandlerTypeDisplayName,
string implementationLogName,
string handlerInterfaceLogName,
int indentLevel)
{
var indent = new string(' ', indentLevel * 4);
var reflectedArgumentNames = new List<string>();
var resolvedArgumentNames = AppendServiceTypeArgumentResolutions(
builder,
serviceTypeArguments,
registrationVariablePrefix,
reflectedArgumentNames,
indent);
if (reflectedArgumentNames.Count > 0)
indent = AppendReflectedArgumentGuardStart(builder, reflectedArgumentNames, indent);
AppendClosedGenericServiceTypeCreation(
builder,
registrationVariablePrefix,
openHandlerTypeDisplayName,
resolvedArgumentNames,
indent);
AppendServiceRegistration(builder, registrationVariablePrefix, implementationVariableName, indent);
AppendRegistrationLog(builder, implementationLogName, handlerInterfaceLogName, indent);
if (reflectedArgumentNames.Count > 0)
{
builder.Append(new string(' ', indentLevel * 4));
builder.AppendLine("}");
}
}
/// <summary>
/// 递归发射每个 handler 泛型实参的运行时类型解析表达式。
/// </summary>
/// <param name="builder">生成源码构造器。</param>
/// <param name="serviceTypeArguments">handler 服务类型的运行时泛型实参描述。</param>
/// <param name="registrationVariablePrefix">当前注册项的稳定变量名前缀。</param>
/// <param name="reflectedArgumentNames">需要空值检查的反射解析变量集合。</param>
/// <param name="indent">当前生成语句的缩进。</param>
/// <returns>可传给 <c>MakeGenericType</c> 的实参表达式。</returns>
private static string[] AppendServiceTypeArgumentResolutions(
StringBuilder builder,
ImmutableArray<RuntimeTypeReferenceSpec> serviceTypeArguments,
string registrationVariablePrefix,
ICollection<string> reflectedArgumentNames,
string indent)
{
var resolvedArgumentNames = new string[serviceTypeArguments.Length];
for (var argumentIndex = 0; argumentIndex < serviceTypeArguments.Length; argumentIndex++)
{
resolvedArgumentNames[argumentIndex] = AppendRuntimeTypeReferenceResolution(
builder,
serviceTypeArguments[argumentIndex],
$"{registrationVariablePrefix}Argument{argumentIndex}",
reflectedArgumentNames,
indent);
}
return resolvedArgumentNames;
}
/// <summary>
/// 为运行时反射解析出的泛型实参发射空值保护块,避免生成注册器注册无法完整构造的服务类型。
/// </summary>
/// <param name="builder">生成源码构造器。</param>
/// <param name="reflectedArgumentNames">需要参与空值检查的变量名。</param>
/// <param name="indent">保护块开始前的缩进。</param>
/// <returns>保护块内部应使用的下一层缩进。</returns>
private static string AppendReflectedArgumentGuardStart(
StringBuilder builder,
IReadOnlyList<string> reflectedArgumentNames,
string indent)
{
builder.Append(indent);
builder.Append("if (");
for (var index = 0; index < reflectedArgumentNames.Count; index++)
{
if (index > 0)
builder.Append(" && ");
builder.Append(reflectedArgumentNames[index]);
builder.Append(" is not null");
}
builder.AppendLine(")");
builder.Append(indent);
builder.AppendLine("{");
return $"{indent} ";
}
/// <summary>
/// 发射关闭 handler 服务类型的 <c>MakeGenericType</c> 构造语句。
/// </summary>
/// <param name="builder">生成源码构造器。</param>
/// <param name="registrationVariablePrefix">生成代码中的服务类型变量名。</param>
/// <param name="openHandlerTypeDisplayName">开放 handler 接口类型显示名。</param>
/// <param name="resolvedArgumentNames">已解析的泛型实参表达式。</param>
/// <param name="indent">当前生成语句的缩进。</param>
private static void AppendClosedGenericServiceTypeCreation(
StringBuilder builder,
string registrationVariablePrefix,
string openHandlerTypeDisplayName,
IReadOnlyList<string> resolvedArgumentNames,
string indent)
{
builder.Append(indent);
builder.Append("var ");
builder.Append(registrationVariablePrefix);
builder.Append(" = typeof(");
builder.Append(openHandlerTypeDisplayName);
builder.Append(").MakeGenericType(");
for (var index = 0; index < resolvedArgumentNames.Count; index++)
{
if (index > 0)
builder.Append(", ");
builder.Append(resolvedArgumentNames[index]);
}
builder.AppendLine(");");
}
private static string AppendRuntimeTypeReferenceResolution(
StringBuilder builder,
RuntimeTypeReferenceSpec runtimeTypeReference,
string variableBaseName,
ICollection<string> reflectedArgumentNames,
string indent)
{
if (!string.IsNullOrWhiteSpace(runtimeTypeReference.TypeDisplayName))
return $"typeof({runtimeTypeReference.TypeDisplayName})";
if (runtimeTypeReference.ArrayElementTypeReference is not null)
return AppendArrayRuntimeTypeReferenceResolution(
builder,
runtimeTypeReference,
variableBaseName,
reflectedArgumentNames,
indent);
if (runtimeTypeReference.PointerElementTypeReference is not null)
return AppendPointerRuntimeTypeReferenceResolution(
builder,
runtimeTypeReference,
variableBaseName,
reflectedArgumentNames,
indent);
if (runtimeTypeReference.GenericTypeDefinitionReference is not null)
return AppendConstructedGenericRuntimeTypeReferenceResolution(
builder,
runtimeTypeReference,
variableBaseName,
reflectedArgumentNames,
indent);
return AppendReflectionRuntimeTypeReferenceResolution(
builder,
runtimeTypeReference,
variableBaseName,
reflectedArgumentNames,
indent);
}
/// <summary>
/// 发射数组类型引用的运行时重建表达式。
/// </summary>
/// <param name="builder">生成源码构造器。</param>
/// <param name="runtimeTypeReference">数组类型引用描述。</param>
/// <param name="variableBaseName">用于递归生成变量名的稳定前缀。</param>
/// <param name="reflectedArgumentNames">需要空值检查的反射解析变量集合。</param>
/// <param name="indent">当前生成语句的缩进。</param>
/// <returns>数组类型表达式。</returns>
private static string AppendArrayRuntimeTypeReferenceResolution(
StringBuilder builder,
RuntimeTypeReferenceSpec runtimeTypeReference,
string variableBaseName,
ICollection<string> reflectedArgumentNames,
string indent)
{
var elementExpression = AppendRuntimeTypeReferenceResolution(
builder,
runtimeTypeReference.ArrayElementTypeReference!,
$"{variableBaseName}Element",
reflectedArgumentNames,
indent);
return runtimeTypeReference.ArrayRank == 1
? $"{elementExpression}.MakeArrayType()"
: $"{elementExpression}.MakeArrayType({runtimeTypeReference.ArrayRank})";
}
/// <summary>
/// 发射指针类型引用的运行时重建表达式。
/// </summary>
/// <param name="builder">生成源码构造器。</param>
/// <param name="runtimeTypeReference">指针类型引用描述。</param>
/// <param name="variableBaseName">用于递归生成变量名的稳定前缀。</param>
/// <param name="reflectedArgumentNames">需要空值检查的反射解析变量集合。</param>
/// <param name="indent">当前生成语句的缩进。</param>
/// <returns>指针类型表达式。</returns>
private static string AppendPointerRuntimeTypeReferenceResolution(
StringBuilder builder,
RuntimeTypeReferenceSpec runtimeTypeReference,
string variableBaseName,
ICollection<string> reflectedArgumentNames,
string indent)
{
var pointedAtExpression = AppendRuntimeTypeReferenceResolution(
builder,
runtimeTypeReference.PointerElementTypeReference!,
$"{variableBaseName}PointedAt",
reflectedArgumentNames,
indent);
return $"{pointedAtExpression}.MakePointerType()";
}
/// <summary>
/// 发射已构造泛型类型引用的运行时重建表达式。
/// </summary>
/// <param name="builder">生成源码构造器。</param>
/// <param name="runtimeTypeReference">已构造泛型类型引用描述。</param>
/// <param name="variableBaseName">用于递归生成变量名的稳定前缀。</param>
/// <param name="reflectedArgumentNames">需要空值检查的反射解析变量集合。</param>
/// <param name="indent">当前生成语句的缩进。</param>
/// <returns>已构造泛型类型表达式。</returns>
private static string AppendConstructedGenericRuntimeTypeReferenceResolution(
StringBuilder builder,
RuntimeTypeReferenceSpec runtimeTypeReference,
string variableBaseName,
ICollection<string> reflectedArgumentNames,
string indent)
{
var genericTypeDefinitionExpression = AppendRuntimeTypeReferenceResolution(
builder,
runtimeTypeReference.GenericTypeDefinitionReference!,
$"{variableBaseName}GenericDefinition",
reflectedArgumentNames,
indent);
var genericArgumentExpressions = new string[runtimeTypeReference.GenericTypeArguments.Length];
for (var argumentIndex = 0;
argumentIndex < runtimeTypeReference.GenericTypeArguments.Length;
argumentIndex++)
{
genericArgumentExpressions[argumentIndex] = AppendRuntimeTypeReferenceResolution(
builder,
runtimeTypeReference.GenericTypeArguments[argumentIndex],
$"{variableBaseName}GenericArgument{argumentIndex}",
reflectedArgumentNames,
indent);
}
return $"{genericTypeDefinitionExpression}.MakeGenericType({string.Join(", ", genericArgumentExpressions)})";
}
/// <summary>
/// 发射命名类型的运行时反射查找语句,并返回后续服务类型构造应引用的变量名。
/// </summary>
/// <param name="builder">生成源码构造器。</param>
/// <param name="runtimeTypeReference">反射查找类型引用描述。</param>
/// <param name="variableBaseName">生成代码中的反射变量名。</param>
/// <param name="reflectedArgumentNames">需要空值检查的反射解析变量集合。</param>
/// <param name="indent">当前生成语句的缩进。</param>
/// <returns>生成代码中的反射变量名。</returns>
private static string AppendReflectionRuntimeTypeReferenceResolution(
StringBuilder builder,
RuntimeTypeReferenceSpec runtimeTypeReference,
string variableBaseName,
ICollection<string> reflectedArgumentNames,
string indent)
{
reflectedArgumentNames.Add(variableBaseName);
builder.Append(indent);
builder.Append("var ");
builder.Append(variableBaseName);
if (string.IsNullOrWhiteSpace(runtimeTypeReference.ReflectionAssemblyName))
{
builder.Append(" = registryAssembly.GetType(\"");
builder.Append(EscapeStringLiteral(runtimeTypeReference.ReflectionTypeMetadataName!));
builder.AppendLine("\", throwOnError: false, ignoreCase: false);");
}
else
{
builder.Append(" = ResolveReferencedAssemblyType(\"");
builder.Append(EscapeStringLiteral(runtimeTypeReference.ReflectionAssemblyName!));
builder.Append("\", \"");
builder.Append(EscapeStringLiteral(runtimeTypeReference.ReflectionTypeMetadataName!));
builder.AppendLine("\");");
}
return variableBaseName;
}
private static void AppendReflectionHelpers(
StringBuilder builder,
bool includeExternalAssemblyTypeLookupHelpers)
{
if (includeExternalAssemblyTypeLookupHelpers)
{
builder.AppendLine(
" private static global::System.Type? ResolveReferencedAssemblyType(string assemblyIdentity, string typeMetadataName)");
builder.AppendLine(" {");
builder.AppendLine(" var assembly = ResolveReferencedAssembly(assemblyIdentity);");
builder.AppendLine(
" return assembly?.GetType(typeMetadataName, throwOnError: false, ignoreCase: false);");
builder.AppendLine(" }");
builder.AppendLine();
builder.AppendLine(
" private static global::System.Reflection.Assembly? ResolveReferencedAssembly(string assemblyIdentity)");
builder.AppendLine(" {");
builder.AppendLine(" global::System.Reflection.AssemblyName targetAssemblyName;");
builder.AppendLine(" try");
builder.AppendLine(" {");
builder.AppendLine(
" targetAssemblyName = new global::System.Reflection.AssemblyName(assemblyIdentity);");
builder.AppendLine(" }");
builder.AppendLine(" catch");
builder.AppendLine(" {");
builder.AppendLine(" return null;");
builder.AppendLine(" }");
builder.AppendLine();
builder.AppendLine(
" foreach (var assembly in global::System.AppDomain.CurrentDomain.GetAssemblies())");
builder.AppendLine(" {");
builder.AppendLine(
" if (global::System.Reflection.AssemblyName.ReferenceMatchesDefinition(targetAssemblyName, assembly.GetName()))");
builder.AppendLine(" return assembly;");
builder.AppendLine(" }");
builder.AppendLine();
builder.AppendLine(" try");
builder.AppendLine(" {");
builder.AppendLine(
" return global::System.Reflection.Assembly.Load(targetAssemblyName);");
builder.AppendLine(" }");
builder.AppendLine(" catch");
builder.AppendLine(" {");
builder.AppendLine(" return null;");
builder.AppendLine(" }");
builder.AppendLine(" }");
}
}
private static string EscapeStringLiteral(string value)
{
return value.Replace("\\", "\\\\")
.Replace("\"", "\\\"")
.Replace("\n", "\\n")
.Replace("\r", "\\r");
}
}

View File

@ -89,6 +89,80 @@ public class ContextAwareGeneratorSnapshotTests
GetSnapshotFolder()); GetSnapshotFolder());
} }
/// <summary>
/// 验证生成器在用户 partial 类型已经声明常见上下文字段名时仍能生成可编译代码。
/// </summary>
/// <returns>异步任务,无返回值。</returns>
[Test]
public async Task Snapshot_ContextAwareGenerator_With_User_Field_Name_Collisions()
{
const string source = """
using System;
namespace GFramework.Core.SourceGenerators.Abstractions.Rule
{
[AttributeUsage(AttributeTargets.Class)]
public sealed class ContextAwareAttribute : Attribute { }
}
namespace GFramework.Core.Abstractions.Rule
{
public interface IContextAware
{
void SetContext(
GFramework.Core.Abstractions.Architectures.IArchitectureContext context);
GFramework.Core.Abstractions.Architectures.IArchitectureContext GetContext();
}
}
namespace GFramework.Core.Abstractions.Architectures
{
public interface IArchitectureContext { }
public interface IArchitectureContextProvider
{
IArchitectureContext GetContext();
bool TryGetContext<T>(out T? context) where T : class, IArchitectureContext;
}
}
namespace GFramework.Core.Architectures
{
using GFramework.Core.Abstractions.Architectures;
public sealed class GameContextProvider : IArchitectureContextProvider
{
public IArchitectureContext GetContext() => null;
public bool TryGetContext<T>(out T? context) where T : class, IArchitectureContext
{
context = null;
return false;
}
}
}
namespace TestApp
{
using GFramework.Core.SourceGenerators.Abstractions.Rule;
using GFramework.Core.Abstractions.Rule;
using GFramework.Core.Abstractions.Architectures;
[ContextAware]
public partial class CollisionProneRule : IContextAware
{
private readonly string _context = "user-field";
private static readonly string _contextProvider = "user-provider";
private static readonly object _contextSync = new();
}
}
""";
await GeneratorSnapshotTest<ContextAwareGenerator>.RunAsync(
source,
GetSnapshotFolder());
}
/// <summary> /// <summary>
/// 将运行时测试目录映射回仓库内已提交的上下文感知生成器快照目录。 /// 将运行时测试目录映射回仓库内已提交的上下文感知生成器快照目录。
/// </summary> /// </summary>

View File

@ -0,0 +1,103 @@
// <auto-generated/>
#nullable enable
namespace TestApp;
/// <summary>
/// 为当前规则类型补充自动生成的架构上下文访问实现。
/// </summary>
/// <remarks>
/// 生成代码会在实例级缓存首次解析到的上下文,并在未显式配置提供者时回退到 <see cref="GFramework.Core.Architectures.GameContextProvider" />。
/// 同一生成类型的所有实例共享一个静态上下文提供者;切换或重置提供者只会影响尚未缓存上下文的新实例或未初始化实例,
/// 已缓存的实例上下文需要通过 <see cref="GFramework.Core.Abstractions.Rule.IContextAware.SetContext(GFramework.Core.Abstractions.Architectures.IArchitectureContext)" /> 显式覆盖。
/// 与手动继承 <see cref="global::GFramework.Core.Rule.ContextAwareBase" /> 的路径相比,生成实现会使用 <c>_gFrameworkContextAwareSync</c> 协调惰性初始化、provider 切换和显式上下文注入;
/// <see cref="global::GFramework.Core.Rule.ContextAwareBase" /> 则保持无锁的实例级缓存语义,更适合已经由调用方线程模型保证串行访问的简单场景。
/// </remarks>
partial class CollisionProneRule : global::GFramework.Core.Abstractions.Rule.IContextAware
{
private global::GFramework.Core.Abstractions.Architectures.IArchitectureContext? _gFrameworkContextAwareContext;
private static global::GFramework.Core.Abstractions.Architectures.IArchitectureContextProvider? _gFrameworkContextAwareProvider;
private static readonly object _gFrameworkContextAwareSync = new();
/// <summary>
/// 获取当前实例绑定的架构上下文。
/// </summary>
/// <remarks>
/// 该属性会先返回通过 <c>IContextAware.SetContext(...)</c> 显式注入的实例上下文;若尚未设置,则在同一个同步域内惰性初始化共享提供者。
/// 当静态提供者尚未配置时,生成代码会回退到 <see cref="GFramework.Core.Architectures.GameContextProvider" />。
/// 一旦某个实例成功缓存上下文,后续 <see cref="SetContextProvider(GFramework.Core.Abstractions.Architectures.IArchitectureContextProvider)" />
/// 或 <see cref="ResetContextProvider" /> 不会自动清除此缓存;如需覆盖,请显式调用 <c>IContextAware.SetContext(...)</c>。
/// 当前实现还假设 <see cref="GFramework.Core.Abstractions.Architectures.IArchitectureContextProvider.GetContext" /> 可在持有 <c>_gFrameworkContextAwareSync</c> 时安全执行;
/// 自定义 provider 不应在该调用链内重新进入当前类型的 provider 配置 API且应避免引入与外部全局锁相互等待的锁顺序。
/// </remarks>
protected global::GFramework.Core.Abstractions.Architectures.IArchitectureContext Context
{
get
{
var context = _gFrameworkContextAwareContext;
if (context is not null)
{
return context;
}
// 在同一个同步域内协调懒加载与 provider 切换,避免读取到被并发重置的空提供者。
// provider 的 GetContext() 会在持有 _gFrameworkContextAwareSync 时执行;自定义 provider 必须避免在该调用链内回调 SetContextProvider/ResetContextProvider 或形成反向锁顺序。
lock (_gFrameworkContextAwareSync)
{
_gFrameworkContextAwareProvider ??= new global::GFramework.Core.Architectures.GameContextProvider();
_gFrameworkContextAwareContext ??= _gFrameworkContextAwareProvider.GetContext();
return _gFrameworkContextAwareContext;
}
}
}
/// <summary>
/// 配置当前生成类型共享的上下文提供者。
/// </summary>
/// <param name="provider">后续懒加载上下文时要使用的提供者实例。</param>
/// <exception cref="global::System.ArgumentNullException">当 <paramref name="provider" /> 为 null 时抛出。</exception>
/// <remarks>
/// 该方法使用与 <see cref="Context" /> 相同的同步锁,避免提供者切换与惰性初始化交错。
/// 已经缓存上下文的实例不会因为提供者切换而自动失效;该变更仅影响尚未初始化上下文的新实例或未缓存实例。
/// 如需覆盖已有实例的上下文,请显式调用 <c>IContextAware.SetContext(...)</c>。
/// </remarks>
public static void SetContextProvider(global::GFramework.Core.Abstractions.Architectures.IArchitectureContextProvider provider)
{
global::System.ArgumentNullException.ThrowIfNull(provider);
lock (_gFrameworkContextAwareSync)
{
_gFrameworkContextAwareProvider = provider;
}
}
/// <summary>
/// 重置共享上下文提供者,使后续懒加载回退到默认提供者。
/// </summary>
/// <remarks>
/// 该方法主要用于测试清理或跨用例恢复默认行为。
/// 它不会清除已经缓存到实例字段中的上下文;只有后续尚未初始化上下文的实例会重新回退到 <see cref="GFramework.Core.Architectures.GameContextProvider" />。
/// 如需覆盖已有实例的上下文,请显式调用 <c>IContextAware.SetContext(...)</c>。
/// </remarks>
public static void ResetContextProvider()
{
lock (_gFrameworkContextAwareSync)
{
_gFrameworkContextAwareProvider = null;
}
}
void global::GFramework.Core.Abstractions.Rule.IContextAware.SetContext(global::GFramework.Core.Abstractions.Architectures.IArchitectureContext context)
{
// 与 Context getter 共享同一同步协议,避免显式注入被并发懒加载覆盖。
lock (_gFrameworkContextAwareSync)
{
_gFrameworkContextAwareContext = context;
}
}
global::GFramework.Core.Abstractions.Architectures.IArchitectureContext global::GFramework.Core.Abstractions.Rule.IContextAware.GetContext()
{
return Context;
}
}

View File

@ -10,14 +10,14 @@ namespace TestApp;
/// 生成代码会在实例级缓存首次解析到的上下文,并在未显式配置提供者时回退到 <see cref="GFramework.Core.Architectures.GameContextProvider" />。 /// 生成代码会在实例级缓存首次解析到的上下文,并在未显式配置提供者时回退到 <see cref="GFramework.Core.Architectures.GameContextProvider" />。
/// 同一生成类型的所有实例共享一个静态上下文提供者;切换或重置提供者只会影响尚未缓存上下文的新实例或未初始化实例, /// 同一生成类型的所有实例共享一个静态上下文提供者;切换或重置提供者只会影响尚未缓存上下文的新实例或未初始化实例,
/// 已缓存的实例上下文需要通过 <see cref="GFramework.Core.Abstractions.Rule.IContextAware.SetContext(GFramework.Core.Abstractions.Architectures.IArchitectureContext)" /> 显式覆盖。 /// 已缓存的实例上下文需要通过 <see cref="GFramework.Core.Abstractions.Rule.IContextAware.SetContext(GFramework.Core.Abstractions.Architectures.IArchitectureContext)" /> 显式覆盖。
/// 与手动继承 <see cref="global::GFramework.Core.Rule.ContextAwareBase" /> 的路径相比,生成实现会使用 <c>_contextSync</c> 协调惰性初始化、provider 切换和显式上下文注入; /// 与手动继承 <see cref="global::GFramework.Core.Rule.ContextAwareBase" /> 的路径相比,生成实现会使用 <c>_gFrameworkContextAwareSync</c> 协调惰性初始化、provider 切换和显式上下文注入;
/// <see cref="global::GFramework.Core.Rule.ContextAwareBase" /> 则保持无锁的实例级缓存语义,更适合已经由调用方线程模型保证串行访问的简单场景。 /// <see cref="global::GFramework.Core.Rule.ContextAwareBase" /> 则保持无锁的实例级缓存语义,更适合已经由调用方线程模型保证串行访问的简单场景。
/// </remarks> /// </remarks>
partial class MyRule : global::GFramework.Core.Abstractions.Rule.IContextAware partial class MyRule : global::GFramework.Core.Abstractions.Rule.IContextAware
{ {
private global::GFramework.Core.Abstractions.Architectures.IArchitectureContext? _context; private global::GFramework.Core.Abstractions.Architectures.IArchitectureContext? _gFrameworkContextAwareContext;
private static global::GFramework.Core.Abstractions.Architectures.IArchitectureContextProvider? _contextProvider; private static global::GFramework.Core.Abstractions.Architectures.IArchitectureContextProvider? _gFrameworkContextAwareProvider;
private static readonly object _contextSync = new(); private static readonly object _gFrameworkContextAwareSync = new();
/// <summary> /// <summary>
/// 获取当前实例绑定的架构上下文。 /// 获取当前实例绑定的架构上下文。
@ -27,26 +27,26 @@ partial class MyRule : global::GFramework.Core.Abstractions.Rule.IContextAware
/// 当静态提供者尚未配置时,生成代码会回退到 <see cref="GFramework.Core.Architectures.GameContextProvider" />。 /// 当静态提供者尚未配置时,生成代码会回退到 <see cref="GFramework.Core.Architectures.GameContextProvider" />。
/// 一旦某个实例成功缓存上下文,后续 <see cref="SetContextProvider(GFramework.Core.Abstractions.Architectures.IArchitectureContextProvider)" /> /// 一旦某个实例成功缓存上下文,后续 <see cref="SetContextProvider(GFramework.Core.Abstractions.Architectures.IArchitectureContextProvider)" />
/// 或 <see cref="ResetContextProvider" /> 不会自动清除此缓存;如需覆盖,请显式调用 <c>IContextAware.SetContext(...)</c>。 /// 或 <see cref="ResetContextProvider" /> 不会自动清除此缓存;如需覆盖,请显式调用 <c>IContextAware.SetContext(...)</c>。
/// 当前实现还假设 <see cref="GFramework.Core.Abstractions.Architectures.IArchitectureContextProvider.GetContext" /> 可在持有 <c>_contextSync</c> 时安全执行; /// 当前实现还假设 <see cref="GFramework.Core.Abstractions.Architectures.IArchitectureContextProvider.GetContext" /> 可在持有 <c>_gFrameworkContextAwareSync</c> 时安全执行;
/// 自定义 provider 不应在该调用链内重新进入当前类型的 provider 配置 API且应避免引入与外部全局锁相互等待的锁顺序。 /// 自定义 provider 不应在该调用链内重新进入当前类型的 provider 配置 API且应避免引入与外部全局锁相互等待的锁顺序。
/// </remarks> /// </remarks>
protected global::GFramework.Core.Abstractions.Architectures.IArchitectureContext Context protected global::GFramework.Core.Abstractions.Architectures.IArchitectureContext Context
{ {
get get
{ {
var context = _context; var context = _gFrameworkContextAwareContext;
if (context is not null) if (context is not null)
{ {
return context; return context;
} }
// 在同一个同步域内协调懒加载与 provider 切换,避免读取到被并发重置的空提供者。 // 在同一个同步域内协调懒加载与 provider 切换,避免读取到被并发重置的空提供者。
// provider 的 GetContext() 会在持有 _contextSync 时执行;自定义 provider 必须避免在该调用链内回调 SetContextProvider/ResetContextProvider 或形成反向锁顺序。 // provider 的 GetContext() 会在持有 _gFrameworkContextAwareSync 时执行;自定义 provider 必须避免在该调用链内回调 SetContextProvider/ResetContextProvider 或形成反向锁顺序。
lock (_contextSync) lock (_gFrameworkContextAwareSync)
{ {
_contextProvider ??= new global::GFramework.Core.Architectures.GameContextProvider(); _gFrameworkContextAwareProvider ??= new global::GFramework.Core.Architectures.GameContextProvider();
_context ??= _contextProvider.GetContext(); _gFrameworkContextAwareContext ??= _gFrameworkContextAwareProvider.GetContext();
return _context; return _gFrameworkContextAwareContext;
} }
} }
} }
@ -55,6 +55,7 @@ partial class MyRule : global::GFramework.Core.Abstractions.Rule.IContextAware
/// 配置当前生成类型共享的上下文提供者。 /// 配置当前生成类型共享的上下文提供者。
/// </summary> /// </summary>
/// <param name="provider">后续懒加载上下文时要使用的提供者实例。</param> /// <param name="provider">后续懒加载上下文时要使用的提供者实例。</param>
/// <exception cref="global::System.ArgumentNullException">当 <paramref name="provider" /> 为 null 时抛出。</exception>
/// <remarks> /// <remarks>
/// 该方法使用与 <see cref="Context" /> 相同的同步锁,避免提供者切换与惰性初始化交错。 /// 该方法使用与 <see cref="Context" /> 相同的同步锁,避免提供者切换与惰性初始化交错。
/// 已经缓存上下文的实例不会因为提供者切换而自动失效;该变更仅影响尚未初始化上下文的新实例或未缓存实例。 /// 已经缓存上下文的实例不会因为提供者切换而自动失效;该变更仅影响尚未初始化上下文的新实例或未缓存实例。
@ -62,9 +63,10 @@ partial class MyRule : global::GFramework.Core.Abstractions.Rule.IContextAware
/// </remarks> /// </remarks>
public static void SetContextProvider(global::GFramework.Core.Abstractions.Architectures.IArchitectureContextProvider provider) public static void SetContextProvider(global::GFramework.Core.Abstractions.Architectures.IArchitectureContextProvider provider)
{ {
lock (_contextSync) global::System.ArgumentNullException.ThrowIfNull(provider);
lock (_gFrameworkContextAwareSync)
{ {
_contextProvider = provider; _gFrameworkContextAwareProvider = provider;
} }
} }
@ -78,18 +80,18 @@ partial class MyRule : global::GFramework.Core.Abstractions.Rule.IContextAware
/// </remarks> /// </remarks>
public static void ResetContextProvider() public static void ResetContextProvider()
{ {
lock (_contextSync) lock (_gFrameworkContextAwareSync)
{ {
_contextProvider = null; _gFrameworkContextAwareProvider = null;
} }
} }
void global::GFramework.Core.Abstractions.Rule.IContextAware.SetContext(global::GFramework.Core.Abstractions.Architectures.IArchitectureContext context) void global::GFramework.Core.Abstractions.Rule.IContextAware.SetContext(global::GFramework.Core.Abstractions.Architectures.IArchitectureContext context)
{ {
// 与 Context getter 共享同一同步协议,避免显式注入被并发懒加载覆盖。 // 与 Context getter 共享同一同步协议,避免显式注入被并发懒加载覆盖。
lock (_contextSync) lock (_gFrameworkContextAwareSync)
{ {
_context = context; _gFrameworkContextAwareContext = context;
} }
} }

View File

@ -7,8 +7,8 @@
## 当前恢复点 ## 当前恢复点
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-020` - 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-021`
- 当前阶段:`Phase 20` - 当前阶段:`Phase 21`
- 当前焦点: - 当前焦点:
- 已完成 `GFramework.Core` 当前 `MA0016` / `MA0002` / `MA0015` / `MA0077` 低风险收口批次 - 已完成 `GFramework.Core` 当前 `MA0016` / `MA0002` / `MA0015` / `MA0077` 低风险收口批次
- 已复核 `net10.0` 下的 `MA0158` 基线:`GFramework.Core` / `GFramework.Cqrs` 当前共有 `16` 个 object lock - 已复核 `net10.0` 下的 `MA0158` 基线:`GFramework.Core` / `GFramework.Cqrs` 当前共有 `16` 个 object lock
@ -19,6 +19,8 @@
`StringComparison.Ordinal` `StringComparison.Ordinal`
- 已完成 `SchemaConfigGenerator.cs` 的第一批 `MA0051` 结构拆分schema 入口解析、属性解析、schema 遍历、数组属性解析、 - 已完成 `SchemaConfigGenerator.cs` 的第一批 `MA0051` 结构拆分schema 入口解析、属性解析、schema 遍历、数组属性解析、
约束文档生成与若干生成代码发射 helper 已拆出语义阶段 约束文档生成与若干生成代码发射 helper 已拆出语义阶段
- 已完成当前 PR #269 review follow-up`CqrsHandlerRegistryGenerator` 按职责拆分为 partial 生成器文件,
`ContextAwareGenerator` 改用稳定前缀字段并补上 provider null 防御,`Option<T>` 补齐 `<remarks>` 契约说明
- `LoggingConfiguration``FilterConfiguration``CollectionExtensions` 已改用集合抽象接口,并保留内部具体集合默认值 - `LoggingConfiguration``FilterConfiguration``CollectionExtensions` 已改用集合抽象接口,并保留内部具体集合默认值
- `CoroutineScheduler` 的 tag/group 字典已显式使用 `StringComparer.Ordinal`,保持既有区分大小写语义 - `CoroutineScheduler` 的 tag/group 字典已显式使用 `StringComparer.Ordinal`,保持既有区分大小写语义
- `EasyEvents.AddEvent<T>()` 的重复注册路径已改为状态冲突异常,避免把泛型类型参数伪装成方法参数名 - `EasyEvents.AddEvent<T>()` 的重复注册路径已改为状态冲突异常,避免把泛型类型参数伪装成方法参数名
@ -51,6 +53,8 @@
- 已完成当前 `GFramework.Core` `net8.0` 剩余低风险 analyzer warning 批次warnings-only 基线已降到 `0` - 已完成当前 `GFramework.Core` `net8.0` 剩余低风险 analyzer warning 批次warnings-only 基线已降到 `0`
- 已完成 `GFramework.Core.SourceGenerators``ContextAwareGenerator` 的剩余 `MA0051` 收口warnings-only 基线已降到 `0` - 已完成 `GFramework.Core.SourceGenerators``ContextAwareGenerator` 的剩余 `MA0051` 收口warnings-only 基线已降到 `0`
- 已完成 `GFramework.Cqrs.SourceGenerators``CqrsHandlerRegistryGenerator` 的剩余 `MA0051` 收口warnings-only 基线已降到 `0` - 已完成 `GFramework.Cqrs.SourceGenerators``CqrsHandlerRegistryGenerator` 的剩余 `MA0051` 收口warnings-only 基线已降到 `0`
- 已完成当前 PR #269 的 review follow-up收口 `ContextAwareGenerator` 的字段命名冲突 / provider null 契约、
`Option<T>` 的 XML 文档缺口,以及 `CqrsHandlerRegistryGenerator` 的超大文件拆分
- 已完成 `GFramework.Game.SourceGenerators``SchemaConfigGenerator` 的第一批 `MA0051` 收口warnings-only 基线剩余 `9` - 已完成 `GFramework.Game.SourceGenerators``SchemaConfigGenerator` 的第一批 `MA0051` 收口warnings-only 基线剩余 `9`
`MA0051` `MA0051`
@ -93,6 +97,9 @@
通过 schema 类型比较 helper 与显式 `StringComparison.Ordinal` 清空当前项目的 `MA0006` 通过 schema 类型比较 helper 与显式 `StringComparison.Ordinal` 清空当前项目的 `MA0006`
- `RP-020` 继续拆分 `SchemaConfigGenerator.cs``MA0051` 热点,将当前项目 warnings-only 基线从 `19` 条降到 `9` 条, - `RP-020` 继续拆分 `SchemaConfigGenerator.cs``MA0051` 热点,将当前项目 warnings-only 基线从 `19` 条降到 `9` 条,
并用 focused schema generator tests 验证 50 个用例通过 并用 focused schema generator tests 验证 50 个用例通过
- `RP-021` 使用 `$gframework-pr-review` 复核当前分支 PR #269 后,修复仍在本地成立的 4 个项:将
`CqrsHandlerRegistryGenerator` 拆分为职责清晰的 partial 文件、为 `ContextAwareGenerator` 生成字段增加稳定前缀并补上
`SetContextProvider` 的运行时 null 校验、为 `Option<T>` 补齐 `<remarks>`,并新增字段重名场景的生成器快照测试
- 当前工作树分支 `fix/analyzer-warning-reduction-batch` 已在 `ai-plan/public/README.md` 建立 topic 映射 - 当前工作树分支 `fix/analyzer-warning-reduction-batch` 已在 `ai-plan/public/README.md` 建立 topic 映射
## 当前风险 ## 当前风险
@ -226,6 +233,15 @@
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore --filter FullyQualifiedName~SchemaConfigGenerator -m:1 -p:RestoreFallbackFolders= -nologo` - `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore --filter FullyQualifiedName~SchemaConfigGenerator -m:1 -p:RestoreFallbackFolders= -nologo`
- 结果:`50 Passed``0 Failed` - 结果:`50 Passed``0 Failed`
- 说明:测试项目构建仍显示既有 source generator test analyzer warning不属于本轮写集 - 说明:测试项目构建仍显示既有 source generator test analyzer warning不属于本轮写集
- `RP-021` 的验证结果:
- `dotnet build GFramework.Cqrs.SourceGenerators/GFramework.Cqrs.SourceGenerators.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:RestoreFallbackFolders= -nologo -clp:"Summary;WarningsOnly"`
- 结果:`0 Warning(s)``0 Error(s)`;拆分后最大单文件已降到 `851` 行,满足仓库 800-1000 行上限
- `dotnet build GFramework.Core.SourceGenerators/GFramework.Core.SourceGenerators.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:RestoreFallbackFolders= -nologo -clp:"Summary;WarningsOnly"`
- 结果:`0 Warning(s)``0 Error(s)``ContextAwareGenerator` 的字段命名与 provider 契约修复未引入新的 generator warning
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore --filter "FullyQualifiedName~ContextAwareGeneratorSnapshotTests|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests" -m:1 -p:RestoreFallbackFolders= -nologo`
- 结果:先并行运行两条 `dotnet test` 时触发共享输出文件锁冲突;改为串行重跑后 `ContextAwareGeneratorSnapshotTests=2 Passed`
`CqrsHandlerRegistryGeneratorTests=14 Passed`
- 说明:失败来自测试宿主并行写入同一 build 输出,不是代码回归;串行重跑后快照新增的字段重名场景和 CQRS 快照均通过
- active 跟踪文件只保留当前恢复点、活跃事实、风险与下一步,不再重复保存已完成阶段的长篇历史 - active 跟踪文件只保留当前恢复点、活跃事实、风险与下一步,不再重复保存已完成阶段的长篇历史
## 下一步 ## 下一步

View File

@ -1,5 +1,40 @@
# Analyzer Warning Reduction 追踪 # Analyzer Warning Reduction 追踪
## 2026-04-22 — RP-021
### 阶段PR #269 review follow-up 收口RP-021
- 启动复核:
- 使用 `$gframework-pr-review` 读取当前分支 PR #269 的 CodeRabbit outside-diff 与 nitpick 汇总
- 本地复核后确认仍成立的 4 个项分别是:`CqrsHandlerRegistryGenerator.cs` 超过仓库文件大小上限、
`ContextAwareGenerator` 生成字段名可能与用户 partial 类型冲突、`SetContextProvider` 缺少运行时 null 防御、
`Option<T>` 缺少 `<remarks>` 契约说明
- 决策:
- `CqrsHandlerRegistryGenerator` 继续采用既有 partial helper 风格,按“主流程 / 运行时类型引用 / 源码发射 / 模型”四个文件拆分,
保持生成顺序、日志文本、fallback 契约和快照输出不变
- `ContextAwareGenerator` 只收口仍成立的 review 项,不引入未被本地证实的 `Volatile.Read/Write` 变更
- 为字段命名冲突新增生成器快照场景,避免后续回退到 `_context` / `_contextProvider` / `_contextSync`
- 实施调整:
- 将 `GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs` 拆为 4 个 partial 文件,分别承载主生成管线、
runtime type reference 构造、source emission helper 与嵌套 specs/models
- 将 `ContextAwareGenerator` 生成字段统一改为 `_gFrameworkContextAware*` 前缀,同步更新 XML 文档、注释和显式接口实现
- 为 `SetContextProvider(...)` 增加 `ArgumentNullException.ThrowIfNull(provider)` 与 XML `<exception>` 说明
- 为 `Option<T>` 补充 `<remarks>`,明确 `Some/None``null` 约束、不可变语义与推荐使用方式
- 新增 `CollisionProneRule.ContextAware.g.cs` 快照,覆盖用户字段名与生成字段名冲突场景
- 验证结果:
- `dotnet build GFramework.Cqrs.SourceGenerators/GFramework.Cqrs.SourceGenerators.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:RestoreFallbackFolders= -nologo -clp:"Summary;WarningsOnly"`
- 结果:`0 Warning(s)``0 Error(s)`;拆分后 `CqrsHandlerRegistryGenerator` 最大单文件为 `851`
- `dotnet build GFramework.Core.SourceGenerators/GFramework.Core.SourceGenerators.csproj -c Release -t:Rebuild --no-restore -p:UseSharedCompilation=false -p:RestoreFallbackFolders= -nologo -clp:"Summary;WarningsOnly"`
- 结果:`0 Warning(s)``0 Error(s)`
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore --filter FullyQualifiedName~CqrsHandlerRegistryGeneratorTests -m:1 -p:RestoreFallbackFolders= -nologo`
- 结果:`14 Passed``0 Failed`
- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --no-restore --filter FullyQualifiedName~ContextAwareGeneratorSnapshotTests -m:1 -p:RestoreFallbackFolders= -nologo`
- 结果:`2 Passed``0 Failed`
- 说明:最初并行跑两个 `dotnet test` 命令时触发共享输出文件锁冲突;串行重跑后确认是测试宿主环境噪音而非代码回归
- 下一步建议:
- 若本轮验证通过,可继续回到 `SchemaConfigGenerator` 剩余 `MA0051`
- 若 review 再次聚焦 `ContextAwareGenerator` 并发可见性问题,需要先补最小复现测试,再决定是否引入 `Volatile` 语义
## 2026-04-22 — RP-020 ## 2026-04-22 — RP-020
### 阶段:`SchemaConfigGenerator` 第一批 `MA0051` 结构拆分RP-020 ### 阶段:`SchemaConfigGenerator` 第一批 `MA0051` 结构拆分RP-020