diff --git a/GFramework.Core.SourceGenerators/Enums/EnumExtensionsGenerator.cs b/GFramework.Core.SourceGenerators/Enums/EnumExtensionsGenerator.cs index 32a4715a..c89f18ca 100644 --- a/GFramework.Core.SourceGenerators/Enums/EnumExtensionsGenerator.cs +++ b/GFramework.Core.SourceGenerators/Enums/EnumExtensionsGenerator.cs @@ -205,7 +205,8 @@ public sealed class EnumExtensionsGenerator : AttributeEnumGeneratorBase builder.AppendLine(" /// 判断给定值是否属于指定候选集合。"); builder.AppendLine(" /// "); builder.AppendLine(" /// 要检查的枚举值。"); - builder.AppendLine(" /// 用于匹配的候选枚举值集合。"); + builder.AppendLine( + " /// 用于匹配的候选枚举值集合;当为 时返回 。"); builder.AppendLine( " /// 命中任一候选值时返回 ;否则返回 "); builder.AppendLine( diff --git a/GFramework.Core.SourceGenerators/Rule/ContextAwareGenerator.cs b/GFramework.Core.SourceGenerators/Rule/ContextAwareGenerator.cs index 0fd15fb7..86a0c0de 100644 --- a/GFramework.Core.SourceGenerators/Rule/ContextAwareGenerator.cs +++ b/GFramework.Core.SourceGenerators/Rule/ContextAwareGenerator.cs @@ -99,6 +99,14 @@ public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase sb.AppendLine("/// "); sb.AppendLine("/// 为当前规则类型补充自动生成的架构上下文访问实现。"); sb.AppendLine("/// "); + sb.AppendLine("/// "); + sb.AppendLine( + "/// 生成代码会在实例级缓存首次解析到的上下文,并在未显式配置提供者时回退到 。"); + sb.AppendLine( + "/// 同一生成类型的所有实例共享一个静态上下文提供者;切换或重置提供者只会影响尚未缓存上下文的新实例或未初始化实例,"); + sb.AppendLine( + "/// 已缓存的实例上下文需要通过 显式覆盖。"); + sb.AppendLine("/// "); sb.AppendLine($"partial class {symbol.Name} : {interfaceName}"); sb.AppendLine("{"); @@ -134,8 +142,18 @@ public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase sb.AppendLine(" private static readonly object _contextSync = new();"); sb.AppendLine(); sb.AppendLine(" /// "); - sb.AppendLine(" /// 自动获取的架构上下文(懒加载,默认使用 GameContextProvider)"); + sb.AppendLine(" /// 获取当前实例绑定的架构上下文。"); sb.AppendLine(" /// "); + sb.AppendLine(" /// "); + sb.AppendLine( + " /// 该属性会先返回通过 IContextAware.SetContext(...) 显式注入的实例上下文;若尚未设置,则在同一个同步域内惰性初始化共享提供者。"); + sb.AppendLine( + " /// 当静态提供者尚未配置时,生成代码会回退到 。"); + sb.AppendLine( + " /// 一旦某个实例成功缓存上下文,后续 "); + sb.AppendLine( + " /// 或 不会自动清除此缓存;如需覆盖,请显式调用 IContextAware.SetContext(...)。"); + sb.AppendLine(" /// "); sb.AppendLine(" protected global::GFramework.Core.Abstractions.Architectures.IArchitectureContext Context"); sb.AppendLine(" {"); sb.AppendLine(" get"); @@ -158,9 +176,15 @@ public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase sb.AppendLine(" }"); sb.AppendLine(); sb.AppendLine(" /// "); - sb.AppendLine(" /// 配置上下文提供者(用于测试或多架构场景)"); + sb.AppendLine(" /// 配置当前生成类型共享的上下文提供者。"); sb.AppendLine(" /// "); - sb.AppendLine(" /// 上下文提供者实例"); + sb.AppendLine(" /// 后续懒加载上下文时要使用的提供者实例。"); + sb.AppendLine(" /// "); + sb.AppendLine(" /// 该方法使用与 相同的同步锁,避免提供者切换与惰性初始化交错。"); + sb.AppendLine( + " /// 已经缓存上下文的实例不会因为提供者切换而自动失效;该变更仅影响尚未初始化上下文的新实例或未缓存实例。"); + sb.AppendLine(" /// 如需覆盖已有实例的上下文,请显式调用 IContextAware.SetContext(...)。"); + sb.AppendLine(" /// "); sb.AppendLine( " public static void SetContextProvider(global::GFramework.Core.Abstractions.Architectures.IArchitectureContextProvider provider)"); sb.AppendLine(" {"); @@ -171,8 +195,14 @@ public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase sb.AppendLine(" }"); sb.AppendLine(); sb.AppendLine(" /// "); - sb.AppendLine(" /// 重置上下文提供者为默认值(用于测试清理)"); + sb.AppendLine(" /// 重置共享上下文提供者,使后续懒加载回退到默认提供者。"); sb.AppendLine(" /// "); + sb.AppendLine(" /// "); + sb.AppendLine(" /// 该方法主要用于测试清理或跨用例恢复默认行为。"); + sb.AppendLine( + " /// 它不会清除已经缓存到实例字段中的上下文;只有后续尚未初始化上下文的实例会重新回退到 。"); + sb.AppendLine(" /// 如需覆盖已有实例的上下文,请显式调用 IContextAware.SetContext(...)。"); + sb.AppendLine(" /// "); sb.AppendLine(" public static void ResetContextProvider()"); sb.AppendLine(" {"); sb.AppendLine(" lock (_contextSync)"); @@ -248,7 +278,11 @@ public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase switch (method.Name) { case "SetContext": - sb.AppendLine(" _context = context;"); + sb.AppendLine(" // 与 Context getter 共享同一同步协议,避免显式注入被并发懒加载覆盖。"); + sb.AppendLine(" lock (_contextSync)"); + sb.AppendLine(" {"); + sb.AppendLine(" _context = context;"); + sb.AppendLine(" }"); break; case "GetContext": diff --git a/GFramework.Cqrs.SourceGenerators/AnalyzerReleases.Shipped.md b/GFramework.Cqrs.SourceGenerators/AnalyzerReleases.Shipped.md new file mode 100644 index 00000000..9c6fa74f --- /dev/null +++ b/GFramework.Cqrs.SourceGenerators/AnalyzerReleases.Shipped.md @@ -0,0 +1,2 @@ +; Shipped analyzer releases +; https://github.com/dotnet/roslyn/blob/main/src/RoslynAnalyzers/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md diff --git a/GFramework.Cqrs.SourceGenerators/AnalyzerReleases.Unshipped.md b/GFramework.Cqrs.SourceGenerators/AnalyzerReleases.Unshipped.md new file mode 100644 index 00000000..72d71316 --- /dev/null +++ b/GFramework.Cqrs.SourceGenerators/AnalyzerReleases.Unshipped.md @@ -0,0 +1,8 @@ +; Unshipped analyzer release +; https://github.com/dotnet/roslyn/blob/main/src/RoslynAnalyzers/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md + +### New Rules + + Rule ID | Category | Severity | Notes +-------------|----------------------------------|----------|------------------------------ + GF_Cqrs_001 | GFramework.Cqrs.SourceGenerators | Error | CqrsHandlerRegistryGenerator diff --git a/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs b/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs index a1cb10a6..a337f9ac 100644 --- a/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs +++ b/GFramework.Cqrs.SourceGenerators/Cqrs/CqrsHandlerRegistryGenerator.cs @@ -28,6 +28,14 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator private const string GeneratedTypeName = "__GFrameworkGeneratedCqrsHandlerRegistry"; private const string HintName = "CqrsHandlerRegistry.g.cs"; + private static readonly DiagnosticDescriptor MissingReflectionFallbackContractDiagnostic = new( + "GF_Cqrs_001", + "Cannot emit CQRS registry without reflection fallback contract", + "Cannot generate CQRS handler registry because fallback metadata is required for handler(s): {0}, but runtime contract '{1}' is unavailable", + "GFramework.Cqrs.SourceGenerators", + DiagnosticSeverity.Error, + true); + /// public void Initialize(IncrementalGeneratorInitializationContext context) { @@ -169,6 +177,9 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator generationEnvironment.SupportsReflectionFallbackAttribute, fallbackHandlerTypeMetadataNames.Length)) { + ReportMissingReflectionFallbackContractDiagnostic( + context, + fallbackHandlerTypeMetadataNames); return; } @@ -197,6 +208,25 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator return fallbackHandlerTypeCount == 0 || supportsReflectionFallbackAttribute; } + /// + /// 报告当前轮次因缺少 fallback 元数据承载契约而无法安全生成注册器的诊断。 + /// + /// 源生产上下文。 + /// 需要通过程序集级 reflection fallback 元数据恢复的 handler 元数据名称。 + private static void ReportMissingReflectionFallbackContractDiagnostic( + SourceProductionContext context, + IReadOnlyList fallbackHandlerTypeMetadataNames) + { + var handlerList = string.Join( + ", ", + fallbackHandlerTypeMetadataNames.OrderBy(static name => name, StringComparer.Ordinal)); + context.ReportDiagnostic(Diagnostic.Create( + MissingReflectionFallbackContractDiagnostic, + Location.None, + handlerList, + CqrsReflectionFallbackAttributeMetadataName)); + } + private static List CollectRegistrations( ImmutableArray candidates) { diff --git a/GFramework.SourceGenerators.Tests/Core/GeneratorSnapshotTest.cs b/GFramework.SourceGenerators.Tests/Core/GeneratorSnapshotTest.cs index d8726a2f..fe8b9bff 100644 --- a/GFramework.SourceGenerators.Tests/Core/GeneratorSnapshotTest.cs +++ b/GFramework.SourceGenerators.Tests/Core/GeneratorSnapshotTest.cs @@ -10,12 +10,19 @@ public static class GeneratorSnapshotTest where TGenerator : new() { /// - /// 运行源代码生成器的快照测试 + /// 运行指定源生成器的端到端快照测试。 /// - /// 输入的源代码字符串 - /// 快照文件存储的文件夹路径 + /// 输入的源代码字符串。 + /// 用于存放已提交快照文件的根目录。 /// 将生成文件名映射为快照文件名的规则;为空时使用原始生成文件名。 - /// 异步任务 + /// 当所有生成输出都通过快照校验后完成的异步任务。 + /// + /// 该辅助器会手动构建 Roslyn 编译并执行生成器,然后依次验证生成器自身诊断、更新后编译诊断、生成输出数量和快照内容。 + /// 若生成器报告错误、生成后的编译出现错误、生成器没有任何输出,或首次运行缺少快照文件,测试都会失败。 + /// 首次缺少快照时,本方法会先将当前输出写入 ,再通过断言中断测试,提示调用方提交快照资产。 + /// 的返回值还必须保持在 根目录之内,否则会抛出异常。 + /// + /// 当快照文件名映射结果为空、为绝对路径,或逃逸出快照根目录时抛出。 public static async Task RunAsync( string source, string snapshotFolder, @@ -33,7 +40,16 @@ public static class GeneratorSnapshotTest driver = driver.RunGeneratorsAndUpdateCompilation( compilation, out var updatedCompilation, - out _); + out var generatorDiagnostics); + + var generatorErrors = generatorDiagnostics + .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error) + .ToArray(); + Assert.That( + generatorErrors, + Is.Empty, + () => + $"执行生成器时出现错误:{Environment.NewLine}{string.Join(Environment.NewLine, generatorErrors.Select(static diagnostic => diagnostic.ToString()))}"); var compilationErrors = updatedCompilation.GetDiagnostics() .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error) diff --git a/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs b/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs index 50483a08..a49025a5 100644 --- a/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs +++ b/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs @@ -1169,28 +1169,95 @@ public class CqrsHandlerRegistryGeneratorTests } /// - /// 验证当某轮生成仍然需要程序集级 reflection fallback 元数据时, - /// 若 runtime 合同未提供对应特性契约,生成器会放弃输出注册器以避免静默漏注册。 + /// 验证当某轮生成仍然需要程序集级 reflection fallback 元数据,且 runtime 合同缺少承载该元数据的特性时, + /// 生成器会给出明确诊断并停止输出注册器。 /// [Test] public void - Rejects_Registry_Emission_When_Fallback_Metadata_Is_Required_But_Runtime_Contract_Lacks_Fallback_Attribute() + Reports_Diagnostic_And_Skips_Registry_When_Fallback_Metadata_Is_Required_But_Runtime_Contract_Lacks_Fallback_Attribute() { - var method = typeof(CqrsHandlerRegistryGenerator).GetMethod( - "CanEmitGeneratedRegistry", - BindingFlags.NonPublic | BindingFlags.Static); + const string source = """ + using System; - Assert.That(method, Is.Not.Null); + namespace Microsoft.Extensions.DependencyInjection + { + public interface IServiceCollection { } - var canEmitWithoutFallbackRequirement = (bool?)method!.Invoke(null, [false, 0]); - var canEmitWithSupportedFallbackAttribute = (bool?)method.Invoke(null, [true, 1]); - var canEmitWithoutSupportedFallbackAttribute = (bool?)method.Invoke(null, [false, 1]); + 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) { } + } + } + + namespace TestApp + { + using GFramework.Cqrs.Abstractions.Cqrs; + + public sealed class Container + { + private unsafe struct HiddenResponse + { + } + + private unsafe sealed record HiddenRequest() : IRequest; + + public unsafe sealed class HiddenHandler : IRequestHandler + { + } + } + } + """; + + var execution = ExecuteGenerator(source); + var generatorErrors = execution.GeneratorDiagnostics + .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error) + .ToArray(); + var missingContractDiagnostic = + generatorErrors.SingleOrDefault(static diagnostic => diagnostic.Id == "GF_Cqrs_001"); Assert.Multiple(() => { - Assert.That(canEmitWithoutFallbackRequirement, Is.True); - Assert.That(canEmitWithSupportedFallbackAttribute, Is.True); - Assert.That(canEmitWithoutSupportedFallbackAttribute, Is.False); + Assert.That(execution.GeneratedSources, Is.Empty); + Assert.That(missingContractDiagnostic, Is.Not.Null); + Assert.That( + missingContractDiagnostic!.GetMessage(), + Does.Contain("TestApp.Container+HiddenHandler")); + Assert.That( + missingContractDiagnostic.GetMessage(), + Does.Contain("GFramework.Cqrs.CqrsReflectionFallbackAttribute")); }); } @@ -1219,6 +1286,40 @@ public class CqrsHandlerRegistryGeneratorTests private static string RunGenerator( string source, params MetadataReference[] additionalReferences) + { + var execution = ExecuteGenerator( + source, + additionalReferences); + var generatorErrors = execution.GeneratorDiagnostics + .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error) + .ToArray(); + Assert.That( + generatorErrors, + Is.Empty, + () => + $"执行生成器时出现错误:{Environment.NewLine}{string.Join(Environment.NewLine, generatorErrors.Select(static diagnostic => diagnostic.ToString()))}"); + var compilationErrors = execution.CompilationDiagnostics + .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error) + .ToArray(); + Assert.That( + compilationErrors, + Is.Empty, + () => + $"编译生成的代码时出现错误:{Environment.NewLine}{string.Join(Environment.NewLine, compilationErrors.Select(static diagnostic => diagnostic.ToString()))}"); + Assert.That(execution.GeneratedSources, Has.Length.EqualTo(1)); + + return execution.GeneratedSources[0].content; + } + + /// + /// 运行 CQRS handler registry generator,并返回生成输出及相关诊断。 + /// + /// 输入源码。 + /// 附加元数据引用,用于构造跨程序集场景。 + /// 包含生成源、生成器诊断和更新后编译诊断的执行结果。 + private static GeneratorExecutionResult ExecuteGenerator( + string source, + params MetadataReference[] additionalReferences) { var syntaxTree = CSharpSyntaxTree.ParseText(source); var compilation = CSharpCompilation.Create( @@ -1233,21 +1334,28 @@ public class CqrsHandlerRegistryGeneratorTests driver = driver.RunGeneratorsAndUpdateCompilation( compilation, out var updatedCompilation, - out _); - - var compilationErrors = updatedCompilation.GetDiagnostics() - .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error) - .ToArray(); - Assert.That( - compilationErrors, - Is.Empty, - () => - $"编译生成的代码时出现错误:{Environment.NewLine}{string.Join(Environment.NewLine, compilationErrors.Select(static diagnostic => diagnostic.ToString()))}"); + out var generatorDiagnostics); var runResult = driver.GetRunResult(); Assert.That(runResult.Results, Has.Length.EqualTo(1)); - Assert.That(runResult.Results[0].GeneratedSources, Has.Length.EqualTo(1)); - - return runResult.Results[0].GeneratedSources[0].SourceText.ToString(); + var generatedSources = runResult.Results[0].GeneratedSources + .Select(static sourceResult => + (filename: sourceResult.HintName, content: sourceResult.SourceText.ToString())) + .ToArray(); + return new GeneratorExecutionResult( + generatedSources, + generatorDiagnostics.ToArray(), + updatedCompilation.GetDiagnostics().ToArray()); } + + /// + /// 封装 CQRS handler registry generator 的单次执行结果。 + /// + /// 本轮生成产生的源文件集合。 + /// 生成器自身报告的诊断集合。 + /// 将生成结果并回编译后的编译诊断集合。 + private sealed record GeneratorExecutionResult( + (string filename, string content)[] GeneratedSources, + Diagnostic[] GeneratorDiagnostics, + Diagnostic[] CompilationDiagnostics); } diff --git a/GFramework.SourceGenerators.Tests/Enums/snapshots/EnumExtensionsGenerator/BasicEnum_IsInMethod/Status.EnumExtensions.g.txt b/GFramework.SourceGenerators.Tests/Enums/snapshots/EnumExtensionsGenerator/BasicEnum_IsInMethod/Status.EnumExtensions.g.txt index 89db6bdb..105fdecb 100644 --- a/GFramework.SourceGenerators.Tests/Enums/snapshots/EnumExtensionsGenerator/BasicEnum_IsInMethod/Status.EnumExtensions.g.txt +++ b/GFramework.SourceGenerators.Tests/Enums/snapshots/EnumExtensionsGenerator/BasicEnum_IsInMethod/Status.EnumExtensions.g.txt @@ -25,7 +25,7 @@ namespace TestApp /// 判断给定值是否属于指定候选集合。 /// /// 要检查的枚举值。 - /// 用于匹配的候选枚举值集合。 + /// 用于匹配的候选枚举值集合;当为 时返回 。 /// 命中任一候选值时返回 ;否则返回 public static bool IsIn(this TestApp.Status value, params TestApp.Status[] values) { diff --git a/GFramework.SourceGenerators.Tests/Enums/snapshots/EnumExtensionsGenerator/BasicEnum_IsMethods/Status.EnumExtensions.g.txt b/GFramework.SourceGenerators.Tests/Enums/snapshots/EnumExtensionsGenerator/BasicEnum_IsMethods/Status.EnumExtensions.g.txt index 1709ecf7..117bcba9 100644 --- a/GFramework.SourceGenerators.Tests/Enums/snapshots/EnumExtensionsGenerator/BasicEnum_IsMethods/Status.EnumExtensions.g.txt +++ b/GFramework.SourceGenerators.Tests/Enums/snapshots/EnumExtensionsGenerator/BasicEnum_IsMethods/Status.EnumExtensions.g.txt @@ -32,7 +32,7 @@ namespace TestApp /// 判断给定值是否属于指定候选集合。 /// /// 要检查的枚举值。 - /// 用于匹配的候选枚举值集合。 + /// 用于匹配的候选枚举值集合;当为 时返回 。 /// 命中任一候选值时返回 ;否则返回 public static bool IsIn(this TestApp.Status value, params TestApp.Status[] values) { diff --git a/GFramework.SourceGenerators.Tests/Enums/snapshots/EnumExtensionsGenerator/BasicEnum_IsMethods_DefaultSnapshotFileNameSelector/Status.EnumExtensions.g.cs b/GFramework.SourceGenerators.Tests/Enums/snapshots/EnumExtensionsGenerator/BasicEnum_IsMethods_DefaultSnapshotFileNameSelector/Status.EnumExtensions.g.cs index 89db6bdb..105fdecb 100644 --- a/GFramework.SourceGenerators.Tests/Enums/snapshots/EnumExtensionsGenerator/BasicEnum_IsMethods_DefaultSnapshotFileNameSelector/Status.EnumExtensions.g.cs +++ b/GFramework.SourceGenerators.Tests/Enums/snapshots/EnumExtensionsGenerator/BasicEnum_IsMethods_DefaultSnapshotFileNameSelector/Status.EnumExtensions.g.cs @@ -25,7 +25,7 @@ namespace TestApp /// 判断给定值是否属于指定候选集合。 /// /// 要检查的枚举值。 - /// 用于匹配的候选枚举值集合。 + /// 用于匹配的候选枚举值集合;当为 时返回 。 /// 命中任一候选值时返回 ;否则返回 public static bool IsIn(this TestApp.Status value, params TestApp.Status[] values) { diff --git a/GFramework.SourceGenerators.Tests/Enums/snapshots/EnumExtensionsGenerator/DisableIsMethods/Status.EnumExtensions.g.txt b/GFramework.SourceGenerators.Tests/Enums/snapshots/EnumExtensionsGenerator/DisableIsMethods/Status.EnumExtensions.g.txt index dc9086bb..19e621e6 100644 --- a/GFramework.SourceGenerators.Tests/Enums/snapshots/EnumExtensionsGenerator/DisableIsMethods/Status.EnumExtensions.g.txt +++ b/GFramework.SourceGenerators.Tests/Enums/snapshots/EnumExtensionsGenerator/DisableIsMethods/Status.EnumExtensions.g.txt @@ -11,7 +11,7 @@ namespace TestApp /// 判断给定值是否属于指定候选集合。 /// /// 要检查的枚举值。 - /// 用于匹配的候选枚举值集合。 + /// 用于匹配的候选枚举值集合;当为 时返回 。 /// 命中任一候选值时返回 ;否则返回 public static bool IsIn(this TestApp.Status value, params TestApp.Status[] values) { diff --git a/GFramework.SourceGenerators.Tests/Enums/snapshots/EnumExtensionsGenerator/EnumWithFlagValues/Permissions.EnumExtensions.g.txt b/GFramework.SourceGenerators.Tests/Enums/snapshots/EnumExtensionsGenerator/EnumWithFlagValues/Permissions.EnumExtensions.g.txt index e99878fd..0026b44c 100644 --- a/GFramework.SourceGenerators.Tests/Enums/snapshots/EnumExtensionsGenerator/EnumWithFlagValues/Permissions.EnumExtensions.g.txt +++ b/GFramework.SourceGenerators.Tests/Enums/snapshots/EnumExtensionsGenerator/EnumWithFlagValues/Permissions.EnumExtensions.g.txt @@ -39,7 +39,7 @@ namespace TestApp /// 判断给定值是否属于指定候选集合。 /// /// 要检查的枚举值。 - /// 用于匹配的候选枚举值集合。 + /// 用于匹配的候选枚举值集合;当为 时返回 。 /// 命中任一候选值时返回 ;否则返回 public static bool IsIn(this TestApp.Permissions value, params TestApp.Permissions[] values) { diff --git a/GFramework.SourceGenerators.Tests/Rule/snapshots/ContextAwareGenerator/MyRule.ContextAware.g.cs b/GFramework.SourceGenerators.Tests/Rule/snapshots/ContextAwareGenerator/MyRule.ContextAware.g.cs index ea081e93..96e929e2 100644 --- a/GFramework.SourceGenerators.Tests/Rule/snapshots/ContextAwareGenerator/MyRule.ContextAware.g.cs +++ b/GFramework.SourceGenerators.Tests/Rule/snapshots/ContextAwareGenerator/MyRule.ContextAware.g.cs @@ -6,6 +6,11 @@ namespace TestApp; /// /// 为当前规则类型补充自动生成的架构上下文访问实现。 /// +/// +/// 生成代码会在实例级缓存首次解析到的上下文,并在未显式配置提供者时回退到 。 +/// 同一生成类型的所有实例共享一个静态上下文提供者;切换或重置提供者只会影响尚未缓存上下文的新实例或未初始化实例, +/// 已缓存的实例上下文需要通过 显式覆盖。 +/// partial class MyRule : global::GFramework.Core.Abstractions.Rule.IContextAware { private global::GFramework.Core.Abstractions.Architectures.IArchitectureContext? _context; @@ -13,8 +18,14 @@ partial class MyRule : global::GFramework.Core.Abstractions.Rule.IContextAware private static readonly object _contextSync = new(); /// - /// 自动获取的架构上下文(懒加载,默认使用 GameContextProvider) + /// 获取当前实例绑定的架构上下文。 /// + /// + /// 该属性会先返回通过 IContextAware.SetContext(...) 显式注入的实例上下文;若尚未设置,则在同一个同步域内惰性初始化共享提供者。 + /// 当静态提供者尚未配置时,生成代码会回退到 。 + /// 一旦某个实例成功缓存上下文,后续 + /// 或 不会自动清除此缓存;如需覆盖,请显式调用 IContextAware.SetContext(...)。 + /// protected global::GFramework.Core.Abstractions.Architectures.IArchitectureContext Context { get @@ -36,9 +47,14 @@ partial class MyRule : global::GFramework.Core.Abstractions.Rule.IContextAware } /// - /// 配置上下文提供者(用于测试或多架构场景) + /// 配置当前生成类型共享的上下文提供者。 /// - /// 上下文提供者实例 + /// 后续懒加载上下文时要使用的提供者实例。 + /// + /// 该方法使用与 相同的同步锁,避免提供者切换与惰性初始化交错。 + /// 已经缓存上下文的实例不会因为提供者切换而自动失效;该变更仅影响尚未初始化上下文的新实例或未缓存实例。 + /// 如需覆盖已有实例的上下文,请显式调用 IContextAware.SetContext(...)。 + /// public static void SetContextProvider(global::GFramework.Core.Abstractions.Architectures.IArchitectureContextProvider provider) { lock (_contextSync) @@ -48,8 +64,13 @@ partial class MyRule : global::GFramework.Core.Abstractions.Rule.IContextAware } /// - /// 重置上下文提供者为默认值(用于测试清理) + /// 重置共享上下文提供者,使后续懒加载回退到默认提供者。 /// + /// + /// 该方法主要用于测试清理或跨用例恢复默认行为。 + /// 它不会清除已经缓存到实例字段中的上下文;只有后续尚未初始化上下文的实例会重新回退到 。 + /// 如需覆盖已有实例的上下文,请显式调用 IContextAware.SetContext(...)。 + /// public static void ResetContextProvider() { lock (_contextSync) @@ -60,7 +81,11 @@ partial class MyRule : global::GFramework.Core.Abstractions.Rule.IContextAware void global::GFramework.Core.Abstractions.Rule.IContextAware.SetContext(global::GFramework.Core.Abstractions.Architectures.IArchitectureContext context) { - _context = context; + // 与 Context getter 共享同一同步协议,避免显式注入被并发懒加载覆盖。 + lock (_contextSync) + { + _context = context; + } } global::GFramework.Core.Abstractions.Architectures.IArchitectureContext global::GFramework.Core.Abstractions.Rule.IContextAware.GetContext()