feat(source-generators): 添加多个代码生成器功能

- 新增 PriorityGenerator 为标记 Priority 特性的类自动生成 IPrioritized 接口实现
- 新增 EnumExtensionsGenerator 为枚举自动生成 Is 和 IsIn 扩展方法
- 新增 LoggerGenerator 为标记 Log 特性的类自动生成日志字段
- 新增 ContextAwareGenerator 为标记 ContextAware 特性的类自动生成 IContextAware 接口实现
- 新增 CqrsHandlerRegistryGenerator 为 CQRS 处理器生成编译时注册器减少运行时反射开销
This commit is contained in:
GeWuYou 2026-04-17 10:07:57 +08:00
parent 35a1634697
commit b19877f970
26 changed files with 299 additions and 50 deletions

View File

@ -118,6 +118,9 @@ public sealed class PriorityGenerator : MetadataAttributeClassGeneratorBase
? $"<{string.Join(", ", symbol.TypeParameters.Select(tp => tp.Name))}>" ? $"<{string.Join(", ", symbol.TypeParameters.Select(tp => tp.Name))}>"
: string.Empty; : string.Empty;
sb.AppendLine("/// <summary>");
sb.AppendLine("/// 为当前分部类型补充自动生成的优先级契约实现。");
sb.AppendLine("/// </summary>");
sb.AppendLine( sb.AppendLine(
$"partial class {symbol.Name}{typeParameters} : global::GFramework.Core.Abstractions.Bases.IPrioritized"); $"partial class {symbol.Name}{typeParameters} : global::GFramework.Core.Abstractions.Bases.IPrioritized");
sb.AppendLine("{"); sb.AppendLine("{");

View File

@ -95,6 +95,9 @@ public sealed class EnumExtensionsGenerator : AttributeEnumGeneratorBase
sb.AppendLine("{"); sb.AppendLine("{");
sb.AppendLine(" /// <summary>");
sb.AppendLine($" /// 为 <see cref=\"{fullEnumName}\" /> 提供自动生成的扩展方法。");
sb.AppendLine(" /// </summary>");
sb.AppendLine($" public static partial class {enumName}Extensions"); sb.AppendLine($" public static partial class {enumName}Extensions");
sb.AppendLine(" {"); sb.AppendLine(" {");
@ -176,7 +179,13 @@ public sealed class EnumExtensionsGenerator : AttributeEnumGeneratorBase
builder.AppendLine(); builder.AppendLine();
} }
builder.AppendLine($" /// <summary>是否为 {memberName}</summary>"); builder.AppendLine(" /// <summary>");
builder.AppendLine(
$" /// 判断给定值是否为 <see cref=\"{fullEnumName}.{memberName}\" />。");
builder.AppendLine(" /// </summary>");
builder.AppendLine(" /// <param name=\"value\">要检查的枚举值。</param>");
builder.AppendLine(
$" /// <returns>当 <paramref name=\"value\" /> 等于 <see cref=\"{fullEnumName}.{memberName}\" /> 时返回 <see langword=\"true\" />;否则返回 <see langword=\"false\" />。</returns>");
builder.AppendLine( builder.AppendLine(
$" public static bool Is{memberName}(this {fullEnumName} value) => value == {fullEnumName}.{memberName};"); $" public static bool Is{memberName}(this {fullEnumName} value) => value == {fullEnumName}.{memberName};");
hasGeneratedMembers = true; hasGeneratedMembers = true;
@ -192,7 +201,13 @@ public sealed class EnumExtensionsGenerator : AttributeEnumGeneratorBase
/// <param name="fullEnumName">枚举的完整类型名。</param> /// <param name="fullEnumName">枚举的完整类型名。</param>
private static void AppendIsInMethod(StringBuilder builder, string fullEnumName) private static void AppendIsInMethod(StringBuilder builder, string fullEnumName)
{ {
builder.AppendLine(" /// <summary>判断是否属于指定集合</summary>"); builder.AppendLine(" /// <summary>");
builder.AppendLine(" /// 判断给定值是否属于指定候选集合。");
builder.AppendLine(" /// </summary>");
builder.AppendLine(" /// <param name=\"value\">要检查的枚举值。</param>");
builder.AppendLine(" /// <param name=\"values\">用于匹配的候选枚举值集合。</param>");
builder.AppendLine(
" /// <returns>当 <paramref name=\"value\" /> 命中任一候选值时返回 <see langword=\"true\" />;否则返回 <see langword=\"false\" />。</returns>");
builder.AppendLine( builder.AppendLine(
$" public static bool IsIn(this {fullEnumName} value, params {fullEnumName}[] values)"); $" public static bool IsIn(this {fullEnumName} value, params {fullEnumName}[] values)");
builder.AppendLine(" {"); builder.AppendLine(" {");

View File

@ -71,13 +71,18 @@ public sealed class LoggerGenerator : TypeAttributeClassGeneratorBase
.AppendLine($"namespace {ns};"); .AppendLine($"namespace {ns};");
sb.AppendLine() sb.AppendLine()
.AppendLine("/// <summary>")
.AppendLine("/// 为当前分部类型提供自动生成的日志字段。")
.AppendLine("/// </summary>")
.AppendLine($"partial {typeKind} {className}{generics.Parameters}"); .AppendLine($"partial {typeKind} {className}{generics.Parameters}");
foreach (var c in generics.Constraints) foreach (var c in generics.Constraints)
sb.AppendLine($" {c}"); sb.AppendLine($" {c}");
sb.AppendLine("{") sb.AppendLine("{")
.AppendLine(" /// <summary>Auto-generated logger</summary>") .AppendLine(" /// <summary>")
.AppendLine(" /// 自动生成的日志字段。")
.AppendLine(" /// </summary>")
.AppendLine( .AppendLine(
$" {access} {staticKeyword}readonly ILogger {fieldName} = " + $" {access} {staticKeyword}readonly ILogger {fieldName} = " +
$"LoggerFactoryResolver.Provider.CreateLogger(\"{logName}\");") $"LoggerFactoryResolver.Provider.CreateLogger(\"{logName}\");")

View File

@ -96,6 +96,9 @@ public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase
var interfaceName = iContextAware.ToDisplayString( var interfaceName = iContextAware.ToDisplayString(
SymbolDisplayFormat.FullyQualifiedFormat); SymbolDisplayFormat.FullyQualifiedFormat);
sb.AppendLine("/// <summary>");
sb.AppendLine("/// 为当前规则类型补充自动生成的架构上下文访问实现。");
sb.AppendLine("/// </summary>");
sb.AppendLine($"partial class {symbol.Name} : {interfaceName}"); sb.AppendLine($"partial class {symbol.Name} : {interfaceName}");
sb.AppendLine("{"); sb.AppendLine("{");
@ -128,6 +131,7 @@ public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase
sb.AppendLine(" private global::GFramework.Core.Abstractions.Architectures.IArchitectureContext? _context;"); sb.AppendLine(" private global::GFramework.Core.Abstractions.Architectures.IArchitectureContext? _context;");
sb.AppendLine( sb.AppendLine(
" private static global::GFramework.Core.Abstractions.Architectures.IArchitectureContextProvider? _contextProvider;"); " private static global::GFramework.Core.Abstractions.Architectures.IArchitectureContextProvider? _contextProvider;");
sb.AppendLine(" private static readonly object _contextSync = new();");
sb.AppendLine(); sb.AppendLine();
sb.AppendLine(" /// <summary>"); sb.AppendLine(" /// <summary>");
sb.AppendLine(" /// 自动获取的架构上下文(懒加载,默认使用 GameContextProvider"); sb.AppendLine(" /// 自动获取的架构上下文(懒加载,默认使用 GameContextProvider");
@ -136,14 +140,20 @@ public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase
sb.AppendLine(" {"); sb.AppendLine(" {");
sb.AppendLine(" get"); sb.AppendLine(" get");
sb.AppendLine(" {"); sb.AppendLine(" {");
sb.AppendLine(" if (_context == null)"); sb.AppendLine(" var context = _context;");
sb.AppendLine(" if (context is not null)");
sb.AppendLine(" {");
sb.AppendLine(" return context;");
sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine(" // 在同一个同步域内协调懒加载与 provider 切换,避免读取到被并发重置的空提供者。");
sb.AppendLine(" lock (_contextSync)");
sb.AppendLine(" {"); sb.AppendLine(" {");
sb.AppendLine( sb.AppendLine(
" _contextProvider ??= new global::GFramework.Core.Architectures.GameContextProvider();"); " _contextProvider ??= new global::GFramework.Core.Architectures.GameContextProvider();");
sb.AppendLine(" _context = _contextProvider.GetContext();"); sb.AppendLine(" _context ??= _contextProvider.GetContext();");
sb.AppendLine(" return _context;");
sb.AppendLine(" }"); sb.AppendLine(" }");
sb.AppendLine();
sb.AppendLine(" return _context;");
sb.AppendLine(" }"); sb.AppendLine(" }");
sb.AppendLine(" }"); sb.AppendLine(" }");
sb.AppendLine(); sb.AppendLine();
@ -154,7 +164,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(" _contextProvider = provider;"); sb.AppendLine(" lock (_contextSync)");
sb.AppendLine(" {");
sb.AppendLine(" _contextProvider = provider;");
sb.AppendLine(" }");
sb.AppendLine(" }"); sb.AppendLine(" }");
sb.AppendLine(); sb.AppendLine();
sb.AppendLine(" /// <summary>"); sb.AppendLine(" /// <summary>");
@ -162,7 +175,10 @@ public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase
sb.AppendLine(" /// </summary>"); sb.AppendLine(" /// </summary>");
sb.AppendLine(" public static void ResetContextProvider()"); sb.AppendLine(" public static void ResetContextProvider()");
sb.AppendLine(" {"); sb.AppendLine(" {");
sb.AppendLine(" _contextProvider = null;"); sb.AppendLine(" lock (_contextSync)");
sb.AppendLine(" {");
sb.AppendLine(" _contextProvider = null;");
sb.AppendLine(" }");
sb.AppendLine(" }"); sb.AppendLine(" }");
sb.AppendLine(); sb.AppendLine();
} }

View File

@ -165,8 +165,9 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
.Cast<string>() .Cast<string>()
.ToArray(); .ToArray();
if (fallbackHandlerTypeMetadataNames.Length > 0 && if (!CanEmitGeneratedRegistry(
!generationEnvironment.SupportsReflectionFallbackAttribute) generationEnvironment.SupportsReflectionFallbackAttribute,
fallbackHandlerTypeMetadataNames.Length))
{ {
return; return;
} }
@ -176,6 +177,26 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
GenerateSource(generationEnvironment, registrations, fallbackHandlerTypeMetadataNames)); GenerateSource(generationEnvironment, registrations, fallbackHandlerTypeMetadataNames));
} }
/// <summary>
/// 判断当前轮次是否允许输出生成注册器。
/// </summary>
/// <param name="supportsReflectionFallbackAttribute">
/// runtime 合同中是否存在 <c>CqrsReflectionFallbackAttribute</c>,以承载生成器无法静态精确表达的 handler 回退元数据。
/// </param>
/// <param name="fallbackHandlerTypeCount">
/// 当前轮次需要依赖程序集级 reflection fallback 元数据恢复的 handler 数量。
/// </param>
/// <returns>
/// 当没有 handler 依赖 fallback或 runtime 已提供承载该元数据的特性契约时返回 <see langword="true" />
/// 否则返回 <see langword="false" />,调用方必须放弃生成以避免输出会静默漏注册的半成品注册器。
/// </returns>
private static bool CanEmitGeneratedRegistry(
bool supportsReflectionFallbackAttribute,
int fallbackHandlerTypeCount)
{
return fallbackHandlerTypeCount == 0 || supportsReflectionFallbackAttribute;
}
private static List<ImplementationRegistrationSpec> CollectRegistrations( private static List<ImplementationRegistrationSpec> CollectRegistrations(
ImmutableArray<HandlerCandidateAnalysis?> candidates) ImmutableArray<HandlerCandidateAnalysis?> candidates)
{ {

View File

@ -3,10 +3,13 @@
namespace TestApp; namespace TestApp;
/// <summary>
/// 为当前分部类型补充自动生成的优先级契约实现。
/// </summary>
partial class MySystem : global::GFramework.Core.Abstractions.Bases.IPrioritized partial class MySystem : global::GFramework.Core.Abstractions.Bases.IPrioritized
{ {
/// <summary> /// <summary>
/// 获取优先级值: 10 /// 获取优先级值: 10
/// </summary> /// </summary>
public int Priority => 10; public int Priority => 10;
} }

View File

@ -3,10 +3,13 @@
namespace TestApp; namespace TestApp;
/// <summary>
/// 为当前分部类型补充自动生成的优先级契约实现。
/// </summary>
partial class GenericSystem<T> : global::GFramework.Core.Abstractions.Bases.IPrioritized partial class GenericSystem<T> : global::GFramework.Core.Abstractions.Bases.IPrioritized
{ {
/// <summary> /// <summary>
/// 获取优先级值: 20 /// 获取优先级值: 20
/// </summary> /// </summary>
public int Priority => 20; public int Priority => 20;
} }

View File

@ -3,10 +3,13 @@
namespace TestApp; namespace TestApp;
/// <summary>
/// 为当前分部类型补充自动生成的优先级契约实现。
/// </summary>
partial class CriticalSystem : global::GFramework.Core.Abstractions.Bases.IPrioritized partial class CriticalSystem : global::GFramework.Core.Abstractions.Bases.IPrioritized
{ {
/// <summary> /// <summary>
/// 获取优先级值: -100 /// 获取优先级值: -100
/// </summary> /// </summary>
public int Priority => -100; public int Priority => -100;
} }

View File

@ -3,10 +3,13 @@
namespace TestApp; namespace TestApp;
/// <summary>
/// 为当前分部类型补充自动生成的优先级契约实现。
/// </summary>
partial class HighPrioritySystem : global::GFramework.Core.Abstractions.Bases.IPrioritized partial class HighPrioritySystem : global::GFramework.Core.Abstractions.Bases.IPrioritized
{ {
/// <summary> /// <summary>
/// 获取优先级值: -50 /// 获取优先级值: -50
/// </summary> /// </summary>
public int Priority => -50; public int Priority => -50;
} }

View File

@ -42,7 +42,7 @@ public static class GeneratorSnapshotTest<TGenerator>
compilationErrors, compilationErrors,
Is.Empty, Is.Empty,
() => () =>
$"编译生成的代码时出现错误:{Environment.NewLine}{string.Join(Environment.NewLine, compilationErrors.Select(static diagnostic => diagnostic.ToString()))}"); $"编译生成的代码时出现错误{Environment.NewLine}{string.Join(Environment.NewLine, compilationErrors.Select(static diagnostic => diagnostic.ToString()))}");
var runResult = driver.GetRunResult(); var runResult = driver.GetRunResult();
var generated = runResult.Results var generated = runResult.Results
@ -53,7 +53,7 @@ public static class GeneratorSnapshotTest<TGenerator>
Assert.That( Assert.That(
generated, generated,
Is.Not.Empty, Is.Not.Empty,
$"Generator '{typeof(TGenerator).FullName}' did not produce any sources."); $"生成器 '{typeof(TGenerator).FullName}' 未产生任何输出。");
foreach (var (filename, content) in generated) foreach (var (filename, content) in generated)
{ {
@ -70,7 +70,7 @@ public static class GeneratorSnapshotTest<TGenerator>
await File.WriteAllTextAsync(path, content.ToString()); await File.WriteAllTextAsync(path, content.ToString());
Assert.Fail( Assert.Fail(
$"Snapshot not found. Generated new snapshot at:\n{path}"); $"未找到快照文件,已在以下路径生成新快照:\n{path}");
} }
var expected = await File.ReadAllTextAsync(path); var expected = await File.ReadAllTextAsync(path);
@ -78,7 +78,7 @@ public static class GeneratorSnapshotTest<TGenerator>
Assert.That( Assert.That(
Normalize(expected), Normalize(expected),
Is.EqualTo(Normalize(content.ToString())), Is.EqualTo(Normalize(content.ToString())),
$"Snapshot mismatch: {snapshotFileName}"); $"快照不匹配:{snapshotFileName}");
} }
} }

View File

@ -1168,6 +1168,32 @@ public class CqrsHandlerRegistryGeneratorTests
("CqrsHandlerRegistry.g.cs", HiddenNestedHandlerSelfRegistrationExpected)); ("CqrsHandlerRegistry.g.cs", HiddenNestedHandlerSelfRegistrationExpected));
} }
/// <summary>
/// 验证当某轮生成仍然需要程序集级 reflection fallback 元数据时,
/// 若 runtime 合同未提供对应特性契约,生成器会放弃输出注册器以避免静默漏注册。
/// </summary>
[Test]
public void
Rejects_Registry_Emission_When_Fallback_Metadata_Is_Required_But_Runtime_Contract_Lacks_Fallback_Attribute()
{
var method = typeof(CqrsHandlerRegistryGenerator).GetMethod(
"CanEmitGeneratedRegistry",
BindingFlags.NonPublic | BindingFlags.Static);
Assert.That(method, Is.Not.Null);
var canEmitWithoutFallbackRequirement = (bool?)method!.Invoke(null, [false, 0]);
var canEmitWithSupportedFallbackAttribute = (bool?)method.Invoke(null, [true, 1]);
var canEmitWithoutSupportedFallbackAttribute = (bool?)method.Invoke(null, [false, 1]);
Assert.Multiple(() =>
{
Assert.That(canEmitWithoutFallbackRequirement, Is.True);
Assert.That(canEmitWithSupportedFallbackAttribute, Is.True);
Assert.That(canEmitWithoutSupportedFallbackAttribute, Is.False);
});
}
/// <summary> /// <summary>
/// 验证日志字符串转义会覆盖换行、反斜杠和双引号,避免生成代码中的字符串字面量被意外截断。 /// 验证日志字符串转义会覆盖换行、反斜杠和双引号,避免生成代码中的字符串字面量被意外截断。
/// </summary> /// </summary>

View File

@ -15,6 +15,7 @@ public class EnumExtensionsGeneratorSnapshotTests
/// <summary> /// <summary>
/// 验证默认配置会为普通枚举生成逐项判断方法与集合判断方法。 /// 验证默认配置会为普通枚举生成逐项判断方法与集合判断方法。
/// </summary> /// </summary>
/// <returns>异步任务。</returns>
[Test] [Test]
public async Task Snapshot_BasicEnum_IsMethods() public async Task Snapshot_BasicEnum_IsMethods()
{ {
@ -37,6 +38,7 @@ public class EnumExtensionsGeneratorSnapshotTests
/// <summary> /// <summary>
/// 验证未提供快照文件名映射时,会直接按生成文件名进行快照比对。 /// 验证未提供快照文件名映射时,会直接按生成文件名进行快照比对。
/// </summary> /// </summary>
/// <returns>异步任务。</returns>
[Test] [Test]
public async Task Snapshot_BasicEnum_IsMethods_DefaultSnapshotFileNameSelector() public async Task Snapshot_BasicEnum_IsMethods_DefaultSnapshotFileNameSelector()
{ {
@ -57,6 +59,7 @@ public class EnumExtensionsGeneratorSnapshotTests
/// <summary> /// <summary>
/// 验证默认配置在较小枚举上仍会生成集合判断方法。 /// 验证默认配置在较小枚举上仍会生成集合判断方法。
/// </summary> /// </summary>
/// <returns>异步任务。</returns>
[Test] [Test]
public async Task Snapshot_BasicEnum_IsInMethod() public async Task Snapshot_BasicEnum_IsInMethod()
{ {
@ -78,6 +81,7 @@ public class EnumExtensionsGeneratorSnapshotTests
/// <summary> /// <summary>
/// 验证带显式位标志值的枚举也会生成对应扩展方法。 /// 验证带显式位标志值的枚举也会生成对应扩展方法。
/// </summary> /// </summary>
/// <returns>异步任务。</returns>
[Test] [Test]
public async Task Snapshot_EnumWithFlagValues() public async Task Snapshot_EnumWithFlagValues()
{ {
@ -102,6 +106,7 @@ public class EnumExtensionsGeneratorSnapshotTests
/// <summary> /// <summary>
/// 验证关闭逐项判断开关后仅保留集合判断方法。 /// 验证关闭逐项判断开关后仅保留集合判断方法。
/// </summary> /// </summary>
/// <returns>异步任务。</returns>
[Test] [Test]
public async Task Snapshot_DisableIsMethods() public async Task Snapshot_DisableIsMethods()
{ {
@ -124,6 +129,7 @@ public class EnumExtensionsGeneratorSnapshotTests
/// <summary> /// <summary>
/// 验证关闭集合判断开关后仅保留逐项判断方法。 /// 验证关闭集合判断开关后仅保留逐项判断方法。
/// </summary> /// </summary>
/// <returns>异步任务。</returns>
[Test] [Test]
public async Task Snapshot_DisableIsInMethod() public async Task Snapshot_DisableIsInMethod()
{ {
@ -146,6 +152,7 @@ public class EnumExtensionsGeneratorSnapshotTests
/// <summary> /// <summary>
/// 验证同时关闭两个生成开关时不会输出任何扩展方法。 /// 验证同时关闭两个生成开关时不会输出任何扩展方法。
/// </summary> /// </summary>
/// <returns>异步任务。</returns>
[Test] [Test]
public async Task Snapshot_DisableAllGeneratedMethods() public async Task Snapshot_DisableAllGeneratedMethods()
{ {

View File

@ -2,15 +2,31 @@
using System; using System;
namespace TestApp namespace TestApp
{ {
/// <summary>
/// 为 <see cref="TestApp.Status" /> 提供自动生成的扩展方法。
/// </summary>
public static partial class StatusExtensions public static partial class StatusExtensions
{ {
/// <summary>是否为 Active</summary> /// <summary>
/// 判断给定值是否为 <see cref="TestApp.Status.Active" />。
/// </summary>
/// <param name="value">要检查的枚举值。</param>
/// <returns>当 <paramref name="value" /> 等于 <see cref="TestApp.Status.Active" /> 时返回 <see langword="true" />;否则返回 <see langword="false" />。</returns>
public static bool IsActive(this TestApp.Status value) => value == TestApp.Status.Active; public static bool IsActive(this TestApp.Status value) => value == TestApp.Status.Active;
/// <summary>是否为 Inactive</summary> /// <summary>
/// 判断给定值是否为 <see cref="TestApp.Status.Inactive" />。
/// </summary>
/// <param name="value">要检查的枚举值。</param>
/// <returns>当 <paramref name="value" /> 等于 <see cref="TestApp.Status.Inactive" /> 时返回 <see langword="true" />;否则返回 <see langword="false" />。</returns>
public static bool IsInactive(this TestApp.Status value) => value == TestApp.Status.Inactive; public static bool IsInactive(this TestApp.Status value) => value == TestApp.Status.Inactive;
/// <summary>判断是否属于指定集合</summary> /// <summary>
/// 判断给定值是否属于指定候选集合。
/// </summary>
/// <param name="value">要检查的枚举值。</param>
/// <param name="values">用于匹配的候选枚举值集合。</param>
/// <returns>当 <paramref name="value" /> 命中任一候选值时返回 <see langword="true" />;否则返回 <see langword="false" />。</returns>
public static bool IsIn(this TestApp.Status value, params TestApp.Status[] values) public static bool IsIn(this TestApp.Status value, params TestApp.Status[] values)
{ {
if (values == null) return false; if (values == null) return false;

View File

@ -2,18 +2,38 @@
using System; using System;
namespace TestApp namespace TestApp
{ {
/// <summary>
/// 为 <see cref="TestApp.Status" /> 提供自动生成的扩展方法。
/// </summary>
public static partial class StatusExtensions public static partial class StatusExtensions
{ {
/// <summary>是否为 Active</summary> /// <summary>
/// 判断给定值是否为 <see cref="TestApp.Status.Active" />。
/// </summary>
/// <param name="value">要检查的枚举值。</param>
/// <returns>当 <paramref name="value" /> 等于 <see cref="TestApp.Status.Active" /> 时返回 <see langword="true" />;否则返回 <see langword="false" />。</returns>
public static bool IsActive(this TestApp.Status value) => value == TestApp.Status.Active; public static bool IsActive(this TestApp.Status value) => value == TestApp.Status.Active;
/// <summary>是否为 Inactive</summary> /// <summary>
/// 判断给定值是否为 <see cref="TestApp.Status.Inactive" />。
/// </summary>
/// <param name="value">要检查的枚举值。</param>
/// <returns>当 <paramref name="value" /> 等于 <see cref="TestApp.Status.Inactive" /> 时返回 <see langword="true" />;否则返回 <see langword="false" />。</returns>
public static bool IsInactive(this TestApp.Status value) => value == TestApp.Status.Inactive; public static bool IsInactive(this TestApp.Status value) => value == TestApp.Status.Inactive;
/// <summary>是否为 Pending</summary> /// <summary>
/// 判断给定值是否为 <see cref="TestApp.Status.Pending" />。
/// </summary>
/// <param name="value">要检查的枚举值。</param>
/// <returns>当 <paramref name="value" /> 等于 <see cref="TestApp.Status.Pending" /> 时返回 <see langword="true" />;否则返回 <see langword="false" />。</returns>
public static bool IsPending(this TestApp.Status value) => value == TestApp.Status.Pending; public static bool IsPending(this TestApp.Status value) => value == TestApp.Status.Pending;
/// <summary>判断是否属于指定集合</summary> /// <summary>
/// 判断给定值是否属于指定候选集合。
/// </summary>
/// <param name="value">要检查的枚举值。</param>
/// <param name="values">用于匹配的候选枚举值集合。</param>
/// <returns>当 <paramref name="value" /> 命中任一候选值时返回 <see langword="true" />;否则返回 <see langword="false" />。</returns>
public static bool IsIn(this TestApp.Status value, params TestApp.Status[] values) public static bool IsIn(this TestApp.Status value, params TestApp.Status[] values)
{ {
if (values == null) return false; if (values == null) return false;

View File

@ -2,15 +2,31 @@
using System; using System;
namespace TestApp namespace TestApp
{ {
/// <summary>
/// 为 <see cref="TestApp.Status" /> 提供自动生成的扩展方法。
/// </summary>
public static partial class StatusExtensions public static partial class StatusExtensions
{ {
/// <summary>是否为 Active</summary> /// <summary>
/// 判断给定值是否为 <see cref="TestApp.Status.Active" />。
/// </summary>
/// <param name="value">要检查的枚举值。</param>
/// <returns>当 <paramref name="value" /> 等于 <see cref="TestApp.Status.Active" /> 时返回 <see langword="true" />;否则返回 <see langword="false" />。</returns>
public static bool IsActive(this TestApp.Status value) => value == TestApp.Status.Active; public static bool IsActive(this TestApp.Status value) => value == TestApp.Status.Active;
/// <summary>是否为 Inactive</summary> /// <summary>
/// 判断给定值是否为 <see cref="TestApp.Status.Inactive" />。
/// </summary>
/// <param name="value">要检查的枚举值。</param>
/// <returns>当 <paramref name="value" /> 等于 <see cref="TestApp.Status.Inactive" /> 时返回 <see langword="true" />;否则返回 <see langword="false" />。</returns>
public static bool IsInactive(this TestApp.Status value) => value == TestApp.Status.Inactive; public static bool IsInactive(this TestApp.Status value) => value == TestApp.Status.Inactive;
/// <summary>判断是否属于指定集合</summary> /// <summary>
/// 判断给定值是否属于指定候选集合。
/// </summary>
/// <param name="value">要检查的枚举值。</param>
/// <param name="values">用于匹配的候选枚举值集合。</param>
/// <returns>当 <paramref name="value" /> 命中任一候选值时返回 <see langword="true" />;否则返回 <see langword="false" />。</returns>
public static bool IsIn(this TestApp.Status value, params TestApp.Status[] values) public static bool IsIn(this TestApp.Status value, params TestApp.Status[] values)
{ {
if (values == null) return false; if (values == null) return false;

View File

@ -2,6 +2,9 @@
using System; using System;
namespace TestApp namespace TestApp
{ {
/// <summary>
/// 为 <see cref="TestApp.Status" /> 提供自动生成的扩展方法。
/// </summary>
public static partial class StatusExtensions public static partial class StatusExtensions
{ {
} }

View File

@ -2,12 +2,23 @@
using System; using System;
namespace TestApp namespace TestApp
{ {
/// <summary>
/// 为 <see cref="TestApp.Status" /> 提供自动生成的扩展方法。
/// </summary>
public static partial class StatusExtensions public static partial class StatusExtensions
{ {
/// <summary>是否为 Active</summary> /// <summary>
/// 判断给定值是否为 <see cref="TestApp.Status.Active" />。
/// </summary>
/// <param name="value">要检查的枚举值。</param>
/// <returns>当 <paramref name="value" /> 等于 <see cref="TestApp.Status.Active" /> 时返回 <see langword="true" />;否则返回 <see langword="false" />。</returns>
public static bool IsActive(this TestApp.Status value) => value == TestApp.Status.Active; public static bool IsActive(this TestApp.Status value) => value == TestApp.Status.Active;
/// <summary>是否为 Inactive</summary> /// <summary>
/// 判断给定值是否为 <see cref="TestApp.Status.Inactive" />。
/// </summary>
/// <param name="value">要检查的枚举值。</param>
/// <returns>当 <paramref name="value" /> 等于 <see cref="TestApp.Status.Inactive" /> 时返回 <see langword="true" />;否则返回 <see langword="false" />。</returns>
public static bool IsInactive(this TestApp.Status value) => value == TestApp.Status.Inactive; public static bool IsInactive(this TestApp.Status value) => value == TestApp.Status.Inactive;
} }
} }

View File

@ -2,9 +2,17 @@
using System; using System;
namespace TestApp namespace TestApp
{ {
/// <summary>
/// 为 <see cref="TestApp.Status" /> 提供自动生成的扩展方法。
/// </summary>
public static partial class StatusExtensions public static partial class StatusExtensions
{ {
/// <summary>判断是否属于指定集合</summary> /// <summary>
/// 判断给定值是否属于指定候选集合。
/// </summary>
/// <param name="value">要检查的枚举值。</param>
/// <param name="values">用于匹配的候选枚举值集合。</param>
/// <returns>当 <paramref name="value" /> 命中任一候选值时返回 <see langword="true" />;否则返回 <see langword="false" />。</returns>
public static bool IsIn(this TestApp.Status value, params TestApp.Status[] values) public static bool IsIn(this TestApp.Status value, params TestApp.Status[] values)
{ {
if (values == null) return false; if (values == null) return false;

View File

@ -2,21 +2,45 @@
using System; using System;
namespace TestApp namespace TestApp
{ {
/// <summary>
/// 为 <see cref="TestApp.Permissions" /> 提供自动生成的扩展方法。
/// </summary>
public static partial class PermissionsExtensions public static partial class PermissionsExtensions
{ {
/// <summary>是否为 None</summary> /// <summary>
/// 判断给定值是否为 <see cref="TestApp.Permissions.None" />。
/// </summary>
/// <param name="value">要检查的枚举值。</param>
/// <returns>当 <paramref name="value" /> 等于 <see cref="TestApp.Permissions.None" /> 时返回 <see langword="true" />;否则返回 <see langword="false" />。</returns>
public static bool IsNone(this TestApp.Permissions value) => value == TestApp.Permissions.None; public static bool IsNone(this TestApp.Permissions value) => value == TestApp.Permissions.None;
/// <summary>是否为 Read</summary> /// <summary>
/// 判断给定值是否为 <see cref="TestApp.Permissions.Read" />。
/// </summary>
/// <param name="value">要检查的枚举值。</param>
/// <returns>当 <paramref name="value" /> 等于 <see cref="TestApp.Permissions.Read" /> 时返回 <see langword="true" />;否则返回 <see langword="false" />。</returns>
public static bool IsRead(this TestApp.Permissions value) => value == TestApp.Permissions.Read; public static bool IsRead(this TestApp.Permissions value) => value == TestApp.Permissions.Read;
/// <summary>是否为 Write</summary> /// <summary>
/// 判断给定值是否为 <see cref="TestApp.Permissions.Write" />。
/// </summary>
/// <param name="value">要检查的枚举值。</param>
/// <returns>当 <paramref name="value" /> 等于 <see cref="TestApp.Permissions.Write" /> 时返回 <see langword="true" />;否则返回 <see langword="false" />。</returns>
public static bool IsWrite(this TestApp.Permissions value) => value == TestApp.Permissions.Write; public static bool IsWrite(this TestApp.Permissions value) => value == TestApp.Permissions.Write;
/// <summary>是否为 Execute</summary> /// <summary>
/// 判断给定值是否为 <see cref="TestApp.Permissions.Execute" />。
/// </summary>
/// <param name="value">要检查的枚举值。</param>
/// <returns>当 <paramref name="value" /> 等于 <see cref="TestApp.Permissions.Execute" /> 时返回 <see langword="true" />;否则返回 <see langword="false" />。</returns>
public static bool IsExecute(this TestApp.Permissions value) => value == TestApp.Permissions.Execute; public static bool IsExecute(this TestApp.Permissions value) => value == TestApp.Permissions.Execute;
/// <summary>判断是否属于指定集合</summary> /// <summary>
/// 判断给定值是否属于指定候选集合。
/// </summary>
/// <param name="value">要检查的枚举值。</param>
/// <param name="values">用于匹配的候选枚举值集合。</param>
/// <returns>当 <paramref name="value" /> 命中任一候选值时返回 <see langword="true" />;否则返回 <see langword="false" />。</returns>
public static bool IsIn(this TestApp.Permissions value, params TestApp.Permissions[] values) public static bool IsIn(this TestApp.Permissions value, params TestApp.Permissions[] values)
{ {
if (values == null) return false; if (values == null) return false;

View File

@ -4,8 +4,13 @@ using GFramework.Core.Logging;
namespace TestApp; namespace TestApp;
/// <summary>
/// 为当前分部类型提供自动生成的日志字段。
/// </summary>
partial class MyService partial class MyService
{ {
/// <summary>Auto-generated logger</summary> /// <summary>
/// 自动生成的日志字段。
/// </summary>
private static readonly ILogger MyLogger = LoggerFactoryResolver.Provider.CreateLogger("MyService"); private static readonly ILogger MyLogger = LoggerFactoryResolver.Provider.CreateLogger("MyService");
} }

View File

@ -4,8 +4,13 @@ using GFramework.Core.Logging;
namespace TestApp; namespace TestApp;
/// <summary>
/// 为当前分部类型提供自动生成的日志字段。
/// </summary>
partial class MyService partial class MyService
{ {
/// <summary>Auto-generated logger</summary> /// <summary>
/// 自动生成的日志字段。
/// </summary>
private static readonly ILogger _log = LoggerFactoryResolver.Provider.CreateLogger("MyService"); private static readonly ILogger _log = LoggerFactoryResolver.Provider.CreateLogger("MyService");
} }

View File

@ -4,8 +4,13 @@ using GFramework.Core.Logging;
namespace TestApp; namespace TestApp;
/// <summary>
/// 为当前分部类型提供自动生成的日志字段。
/// </summary>
partial class MyService partial class MyService
{ {
/// <summary>Auto-generated logger</summary> /// <summary>
/// 自动生成的日志字段。
/// </summary>
private static readonly ILogger _log = LoggerFactoryResolver.Provider.CreateLogger("MyService"); private static readonly ILogger _log = LoggerFactoryResolver.Provider.CreateLogger("MyService");
} }

View File

@ -4,8 +4,13 @@ using GFramework.Core.Logging;
namespace TestApp; namespace TestApp;
/// <summary>
/// 为当前分部类型提供自动生成的日志字段。
/// </summary>
partial class MyService<T> partial class MyService<T>
{ {
/// <summary>Auto-generated logger</summary> /// <summary>
/// 自动生成的日志字段。
/// </summary>
private static readonly ILogger _log = LoggerFactoryResolver.Provider.CreateLogger("MyService"); private static readonly ILogger _log = LoggerFactoryResolver.Provider.CreateLogger("MyService");
} }

View File

@ -4,8 +4,13 @@ using GFramework.Core.Logging;
namespace TestApp; namespace TestApp;
/// <summary>
/// 为当前分部类型提供自动生成的日志字段。
/// </summary>
partial class MyService partial class MyService
{ {
/// <summary>Auto-generated logger</summary> /// <summary>
/// 自动生成的日志字段。
/// </summary>
private readonly ILogger _log = LoggerFactoryResolver.Provider.CreateLogger("MyService"); private readonly ILogger _log = LoggerFactoryResolver.Provider.CreateLogger("MyService");
} }

View File

@ -4,8 +4,13 @@ using GFramework.Core.Logging;
namespace TestApp; namespace TestApp;
/// <summary>
/// 为当前分部类型提供自动生成的日志字段。
/// </summary>
partial class MyService partial class MyService
{ {
/// <summary>Auto-generated logger</summary> /// <summary>
/// 自动生成的日志字段。
/// </summary>
public static readonly ILogger _log = LoggerFactoryResolver.Provider.CreateLogger("MyService"); public static readonly ILogger _log = LoggerFactoryResolver.Provider.CreateLogger("MyService");
} }

View File

@ -3,10 +3,14 @@
namespace TestApp; namespace TestApp;
/// <summary>
/// 为当前规则类型补充自动生成的架构上下文访问实现。
/// </summary>
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? _context;
private static global::GFramework.Core.Abstractions.Architectures.IArchitectureContextProvider? _contextProvider; private static global::GFramework.Core.Abstractions.Architectures.IArchitectureContextProvider? _contextProvider;
private static readonly object _contextSync = new();
/// <summary> /// <summary>
/// 自动获取的架构上下文(懒加载,默认使用 GameContextProvider /// 自动获取的架构上下文(懒加载,默认使用 GameContextProvider
@ -15,13 +19,19 @@ partial class MyRule : global::GFramework.Core.Abstractions.Rule.IContextAware
{ {
get get
{ {
if (_context == null) var context = _context;
if (context is not null)
{ {
_contextProvider ??= new global::GFramework.Core.Architectures.GameContextProvider(); return context;
_context = _contextProvider.GetContext();
} }
return _context; // 在同一个同步域内协调懒加载与 provider 切换,避免读取到被并发重置的空提供者。
lock (_contextSync)
{
_contextProvider ??= new global::GFramework.Core.Architectures.GameContextProvider();
_context ??= _contextProvider.GetContext();
return _context;
}
} }
} }
@ -31,7 +41,10 @@ partial class MyRule : global::GFramework.Core.Abstractions.Rule.IContextAware
/// <param name="provider">上下文提供者实例</param> /// <param name="provider">上下文提供者实例</param>
public static void SetContextProvider(global::GFramework.Core.Abstractions.Architectures.IArchitectureContextProvider provider) public static void SetContextProvider(global::GFramework.Core.Abstractions.Architectures.IArchitectureContextProvider provider)
{ {
_contextProvider = provider; lock (_contextSync)
{
_contextProvider = provider;
}
} }
/// <summary> /// <summary>
@ -39,7 +52,10 @@ partial class MyRule : global::GFramework.Core.Abstractions.Rule.IContextAware
/// </summary> /// </summary>
public static void ResetContextProvider() public static void ResetContextProvider()
{ {
_contextProvider = null; lock (_contextSync)
{
_contextProvider = 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)
@ -52,4 +68,4 @@ partial class MyRule : global::GFramework.Core.Abstractions.Rule.IContextAware
return Context; return Context;
} }
} }