docs: 添加 CQRS 架构模式和游戏配置系统文档

- 新增 CQRS 核心概念、命令查询处理器实现指南
- 添加 CQRS 高级用法包括通知发布、管道行为和流式处理
- 提供 CQRS 最佳实践和常见问题解决方案
- 添加游戏配置系统完整接入模板和运行时读取示例
- 包含 YAML 配置文件和 JSON Schema 结构定义说明
- 提供 Godot 引擎配置桥接和热重载功能使用指南
- 添加架构模块集成和生成查询辅助功能文档
This commit is contained in:
GeWuYou 2026-04-16 20:36:15 +08:00
parent d8831733ff
commit 09f751a4f7
71 changed files with 485 additions and 671 deletions

View File

@ -73,9 +73,8 @@ Architecture 负责统一生命周期编排,核心阶段包括:
### CQRS
命令与查询分离,支持同步与异步执行。当前版本内建自有 CQRS runtime、行为管道和 handler 自动注册;公开 API 里仍保留少量历史
`Mediator` 命名以兼容旧调用点,但这些别名已进入正式弃用周期:新代码应使用 `Cqrs` 命名入口,旧别名会继续兼容一段时间并计划在未来
major 版本中移除。
命令与查询分离,支持同步与异步执行。当前版本内建自有 CQRS runtime、行为管道和 handler 自动注册;历史 `Mediator`
兼容别名已从公开 API 移除,统一使用 `Cqrs` 命名入口。
### EventBus

View File

@ -1,4 +1,3 @@
using System.ComponentModel;
using System.Reflection;
using GFramework.Core.Abstractions.Lifecycle;
using GFramework.Core.Abstractions.Model;
@ -82,20 +81,6 @@ public interface IArchitecture : IAsyncInitializable, IAsyncDestroyable, IInitia
void RegisterCqrsPipelineBehavior<TBehavior>()
where TBehavior : class;
/// <summary>
/// 注册 CQRS 请求管道行为。
/// 该成员保留旧名称以兼容历史调用点,内部行为与 <see cref="RegisterCqrsPipelineBehavior{TBehavior}" /> 一致。
/// 新代码不应继续依赖该别名;兼容层计划在未来的 major 版本中移除。
/// 既支持实现 <c>IPipelineBehavior&lt;,&gt;</c> 的开放泛型行为类型,
/// 也支持绑定到单一请求/响应对的封闭行为类型。
/// </summary>
/// <typeparam name="TBehavior">行为类型,必须是引用类型</typeparam>
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete(
"Use RegisterCqrsPipelineBehavior<TBehavior>() instead. This compatibility alias will be removed in a future major version.")]
void RegisterMediatorBehavior<TBehavior>()
where TBehavior : class;
/// <summary>
/// 从指定程序集显式注册 CQRS 处理器。
/// 当处理器位于默认架构程序集之外的模块或扩展程序集中时,可在初始化阶段调用该入口接入对应程序集。

View File

@ -1,5 +1,4 @@
using System.ComponentModel;
using System.Reflection;
using System.Reflection;
using GFramework.Core.Abstractions.Rule;
using GFramework.Core.Abstractions.Systems;
@ -97,18 +96,6 @@ public interface IIocContainer : IContextAware
void RegisterCqrsPipelineBehavior<TBehavior>()
where TBehavior : class;
/// <summary>
/// 注册 CQRS 请求管道行为。
/// 该成员保留旧名称以兼容历史调用点,内部行为与 <see cref="RegisterCqrsPipelineBehavior{TBehavior}" /> 一致。
/// 新代码不应继续依赖该别名;兼容层计划在未来的 major 版本中移除。
/// </summary>
/// <typeparam name="TBehavior">行为类型,必须是引用类型</typeparam>
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete(
"Use RegisterCqrsPipelineBehavior<TBehavior>() instead. This compatibility alias will be removed in a future major version.")]
void RegisterMediatorBehavior<TBehavior>()
where TBehavior : class;
/// <summary>
/// 从指定程序集显式注册 CQRS 处理器。
/// 该入口适用于处理器不位于默认架构程序集中的场景,例如扩展包、模块程序集或拆分后的业务程序集。

View File

@ -32,4 +32,4 @@ public sealed class PriorityAttribute : Attribute
/// 获取优先级值
/// </summary>
public int Value { get; }
}
}

View File

@ -15,4 +15,4 @@ public sealed class GenerateEnumExtensionsAttribute : Attribute
/// 是否生成一个 IsIn(params T[]) 方法以简化多值判断(默认 true
/// </summary>
public bool GenerateIsInMethod { get; set; } = true;
}
}

View File

@ -15,4 +15,4 @@ global using System;
global using System.Collections.Generic;
global using System.Linq;
global using System.Threading;
global using System.Threading.Tasks;
global using System.Threading.Tasks;

View File

@ -35,4 +35,4 @@ public sealed class LogAttribute : Attribute
/// <summary>访问修饰符</summary>
public string AccessModifier { get; set; } = "private";
}
}

View File

@ -6,4 +6,4 @@
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class ContextAwareAttribute : Attribute
{
}
}

View File

@ -6,4 +6,4 @@ namespace GFramework.SourceGenerators.Abstractions.Rule;
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public sealed class GetAllAttribute : Attribute
{
}
}

View File

@ -6,4 +6,4 @@ namespace GFramework.SourceGenerators.Abstractions.Rule;
[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
public sealed class GetModelAttribute : Attribute
{
}
}

View File

@ -6,4 +6,4 @@ namespace GFramework.SourceGenerators.Abstractions.Rule;
[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
public sealed class GetModelsAttribute : Attribute
{
}
}

View File

@ -6,4 +6,4 @@ namespace GFramework.SourceGenerators.Abstractions.Rule;
[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
public sealed class GetServiceAttribute : Attribute
{
}
}

View File

@ -6,4 +6,4 @@ namespace GFramework.SourceGenerators.Abstractions.Rule;
[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
public sealed class GetServicesAttribute : Attribute
{
}
}

View File

@ -6,4 +6,4 @@ namespace GFramework.SourceGenerators.Abstractions.Rule;
[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
public sealed class GetSystemAttribute : Attribute
{
}
}

View File

@ -6,4 +6,4 @@ namespace GFramework.SourceGenerators.Abstractions.Rule;
[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
public sealed class GetSystemsAttribute : Attribute
{
}
}

View File

@ -6,4 +6,4 @@ namespace GFramework.SourceGenerators.Abstractions.Rule;
[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
public sealed class GetUtilitiesAttribute : Attribute
{
}
}

View File

@ -6,4 +6,4 @@ namespace GFramework.SourceGenerators.Abstractions.Rule;
[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
public sealed class GetUtilityAttribute : Attribute
{
}
}

View File

@ -18,16 +18,6 @@
GF_ContextRegistration_001 | GFramework.SourceGenerators.rule | Warning | ContextRegistrationDiagnostics
GF_ContextRegistration_002 | GFramework.SourceGenerators.rule | Warning | ContextRegistrationDiagnostics
GF_ContextRegistration_003 | GFramework.SourceGenerators.rule | Warning | ContextRegistrationDiagnostics
GF_ConfigSchema_001 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
GF_ConfigSchema_002 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
GF_ConfigSchema_003 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
GF_ConfigSchema_004 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
GF_ConfigSchema_005 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
GF_ConfigSchema_006 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
GF_ConfigSchema_007 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
GF_ConfigSchema_008 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
GF_ConfigSchema_009 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
GF_ConfigSchema_010 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
GF_AutoModule_001 | GFramework.SourceGenerators.Architecture | Error | AutoRegisterModuleDiagnostics
GF_AutoModule_002 | GFramework.SourceGenerators.Architecture | Error | AutoRegisterModuleDiagnostics
GF_AutoModule_003 | GFramework.SourceGenerators.Architecture | Error | AutoRegisterModuleDiagnostics

View File

@ -1,4 +1,3 @@
using System.Collections.Immutable;
using GFramework.SourceGenerators.Common.Constants;
using GFramework.SourceGenerators.Diagnostics;
using Microsoft.CodeAnalysis.Diagnostics;
@ -12,6 +11,17 @@ namespace GFramework.SourceGenerators.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class ContextRegistrationAnalyzer : DiagnosticAnalyzer
{
private static readonly IReadOnlyDictionary<string, ComponentKind> _contextAwareBindingNames =
new Dictionary<string, ComponentKind>(StringComparer.Ordinal)
{
["GetModel"] = ComponentKind.Model,
["GetModels"] = ComponentKind.Model,
["GetSystem"] = ComponentKind.System,
["GetSystems"] = ComponentKind.System,
["GetUtility"] = ComponentKind.Utility,
["GetUtilities"] = ComponentKind.Utility
};
/// <summary>
/// 当前分析器支持的诊断规则。
/// </summary>
@ -58,10 +68,12 @@ public sealed class ContextRegistrationAnalyzer : DiagnosticAnalyzer
if (context.Node is not VariableDeclaratorSyntax variableDeclarator)
return;
if (context.SemanticModel.GetDeclaredSymbol(variableDeclarator, context.CancellationToken) is not IFieldSymbol fieldSymbol)
if (context.SemanticModel.GetDeclaredSymbol(variableDeclarator, context.CancellationToken) is not IFieldSymbol
fieldSymbol)
return;
if (!TryCreateBindingRequest(fieldSymbol, variableDeclarator.Identifier.GetLocation(), symbols, out var request))
if (!TryCreateBindingRequest(fieldSymbol, variableDeclarator.Identifier.GetLocation(), symbols,
out var request))
return;
ReportMissingRegistration(context, registrationIndex, request);
@ -272,17 +284,6 @@ public sealed class ContextRegistrationAnalyzer : DiagnosticAnalyzer
return namedType.TypeArguments[0] as INamedTypeSymbol;
}
private static readonly IReadOnlyDictionary<string, ComponentKind> _contextAwareBindingNames =
new Dictionary<string, ComponentKind>(StringComparer.Ordinal)
{
["GetModel"] = ComponentKind.Model,
["GetModels"] = ComponentKind.Model,
["GetSystem"] = ComponentKind.System,
["GetSystems"] = ComponentKind.System,
["GetUtility"] = ComponentKind.Utility,
["GetUtilities"] = ComponentKind.Utility
};
private enum ComponentKind
{
Model,
@ -368,22 +369,35 @@ public sealed class ContextRegistrationAnalyzer : DiagnosticAnalyzer
{
return new SymbolCache(
compilation.GetTypeByMetadataName($"{PathContests.CoreNamespace}.Architectures.Architecture"),
compilation.GetTypeByMetadataName($"{PathContests.CoreAbstractionsNamespace}.Architectures.IArchitecture"),
compilation.GetTypeByMetadataName($"{PathContests.CoreAbstractionsNamespace}.Architectures.IArchitectureModule"),
compilation.GetTypeByMetadataName($"{PathContests.CoreAbstractionsNamespace}.Architectures.IArchitectureContext"),
compilation.GetTypeByMetadataName(
$"{PathContests.CoreAbstractionsNamespace}.Architectures.IArchitecture"),
compilation.GetTypeByMetadataName(
$"{PathContests.CoreAbstractionsNamespace}.Architectures.IArchitectureModule"),
compilation.GetTypeByMetadataName(
$"{PathContests.CoreAbstractionsNamespace}.Architectures.IArchitectureContext"),
compilation.GetTypeByMetadataName("System.Collections.Generic.IReadOnlyList`1"),
compilation.GetTypeByMetadataName($"{PathContests.CoreNamespace}.Extensions.ContextAwareServiceExtensions"),
compilation.GetTypeByMetadataName($"{PathContests.SourceGeneratorsAbstractionsPath}.Rule.GetModelAttribute"),
compilation.GetTypeByMetadataName($"{PathContests.SourceGeneratorsAbstractionsPath}.Rule.GetModelsAttribute"),
compilation.GetTypeByMetadataName($"{PathContests.SourceGeneratorsAbstractionsPath}.Rule.GetSystemAttribute"),
compilation.GetTypeByMetadataName($"{PathContests.SourceGeneratorsAbstractionsPath}.Rule.GetSystemsAttribute"),
compilation.GetTypeByMetadataName($"{PathContests.SourceGeneratorsAbstractionsPath}.Rule.GetUtilityAttribute"),
compilation.GetTypeByMetadataName($"{PathContests.SourceGeneratorsAbstractionsPath}.Rule.GetUtilitiesAttribute"));
compilation.GetTypeByMetadataName(
$"{PathContests.CoreNamespace}.Extensions.ContextAwareServiceExtensions"),
compilation.GetTypeByMetadataName(
$"{PathContests.SourceGeneratorsAbstractionsPath}.Rule.GetModelAttribute"),
compilation.GetTypeByMetadataName(
$"{PathContests.SourceGeneratorsAbstractionsPath}.Rule.GetModelsAttribute"),
compilation.GetTypeByMetadataName(
$"{PathContests.SourceGeneratorsAbstractionsPath}.Rule.GetSystemAttribute"),
compilation.GetTypeByMetadataName(
$"{PathContests.SourceGeneratorsAbstractionsPath}.Rule.GetSystemsAttribute"),
compilation.GetTypeByMetadataName(
$"{PathContests.SourceGeneratorsAbstractionsPath}.Rule.GetUtilityAttribute"),
compilation.GetTypeByMetadataName(
$"{PathContests.SourceGeneratorsAbstractionsPath}.Rule.GetUtilitiesAttribute"));
}
}
private sealed class RegistrationIndex
{
private readonly Compilation _compilation;
private readonly IReadOnlyDictionary<INamedTypeSymbol, ArchitectureRegistrationData> _registrations;
private RegistrationIndex(
Compilation compilation,
IReadOnlyDictionary<INamedTypeSymbol, ArchitectureRegistrationData> registrations)
@ -392,14 +406,12 @@ public sealed class ContextRegistrationAnalyzer : DiagnosticAnalyzer
_registrations = registrations;
}
private readonly Compilation _compilation;
private readonly IReadOnlyDictionary<INamedTypeSymbol, ArchitectureRegistrationData> _registrations;
public static RegistrationIndex Build(
Compilation compilation,
SymbolCache symbols)
{
var registrations = new Dictionary<INamedTypeSymbol, ArchitectureRegistrationData>(SymbolEqualityComparer.Default);
var registrations =
new Dictionary<INamedTypeSymbol, ArchitectureRegistrationData>(SymbolEqualityComparer.Default);
foreach (var type in SymbolHelpers.EnumerateNamedTypes(compilation.Assembly.GlobalNamespace))
{
@ -690,7 +702,8 @@ public sealed class ContextRegistrationAnalyzer : DiagnosticAnalyzer
return helperMethod.DeclaringSyntaxReferences.Length > 0;
}
helperMethod = SymbolHelpers.ResolveHierarchyMethodImplementation(targetMethod, concreteType) ?? targetMethod;
helperMethod = SymbolHelpers.ResolveHierarchyMethodImplementation(targetMethod, concreteType) ??
targetMethod;
return helperMethod.DeclaringSyntaxReferences.Length > 0;
}
}
@ -813,9 +826,12 @@ public sealed class ContextRegistrationAnalyzer : DiagnosticAnalyzer
if (SymbolEqualityComparer.Default.Equals(candidate.OriginalDefinition, method.OriginalDefinition))
return candidate;
for (var overridden = candidate.OverriddenMethod; overridden != null; overridden = overridden.OverriddenMethod)
for (var overridden = candidate.OverriddenMethod;
overridden != null;
overridden = overridden.OverriddenMethod)
{
if (SymbolEqualityComparer.Default.Equals(overridden.OriginalDefinition, method.OriginalDefinition))
if (SymbolEqualityComparer.Default.Equals(overridden.OriginalDefinition,
method.OriginalDefinition))
return candidate;
}
}

View File

@ -1,6 +1,4 @@
using System.Collections.Immutable;
using GFramework.SourceGenerators.Diagnostics;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
@ -111,4 +109,4 @@ public sealed class PriorityUsageAnalyzer : DiagnosticAnalyzer
{
return type.AllInterfaces.Any(i => SymbolEqualityComparer.Default.Equals(i, interfaceType));
}
}
}

View File

@ -1,10 +1,6 @@
using System.Text;
using GFramework.SourceGenerators.Common.Constants;
using GFramework.SourceGenerators.Common.Generator;
using GFramework.SourceGenerators.Diagnostics;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace GFramework.SourceGenerators.Bases;
@ -150,4 +146,4 @@ public sealed class PriorityGenerator : MetadataAttributeClassGeneratorBase
return $"{metadataName}.Priority.g.cs";
}
}
}

View File

@ -1,6 +1,4 @@
using Microsoft.CodeAnalysis;
namespace GFramework.SourceGenerators.Diagnostics;
namespace GFramework.SourceGenerators.Diagnostics;
/// <summary>
/// 提供与上下文感知相关的诊断规则定义
@ -18,4 +16,4 @@ public static class ContextAwareDiagnostic
DiagnosticSeverity.Error,
true
);
}
}

View File

@ -96,4 +96,4 @@ public static class ContextGetDiagnostics
SourceGeneratorsRuleCategory,
DiagnosticSeverity.Error,
true);
}
}

View File

@ -1,6 +1,4 @@
using Microsoft.CodeAnalysis;
namespace GFramework.SourceGenerators.Diagnostics;
namespace GFramework.SourceGenerators.Diagnostics;
/// <summary>
/// 提供诊断描述符的静态类用于GFramework日志生成器的编译时检查
@ -18,4 +16,4 @@ internal static class LoggerDiagnostics
"GFramework.Godot.Logging",
DiagnosticSeverity.Warning,
true);
}
}

View File

@ -1,5 +1,3 @@
using Microsoft.CodeAnalysis;
namespace GFramework.SourceGenerators.Diagnostics;
/// <summary>
@ -86,4 +84,4 @@ internal static class PriorityDiagnostic
isEnabledByDefault: true,
description: "当获取实现了 IPrioritized 接口的服务时,应使用 GetAllByPriority 方法以确保按优先级排序."
);
}
}

View File

@ -1,9 +1,6 @@
using System.Text;
using GFramework.SourceGenerators.Common.Constants;
using GFramework.SourceGenerators.Common.Constants;
using GFramework.SourceGenerators.Common.Diagnostics;
using GFramework.SourceGenerators.Common.Generator;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace GFramework.SourceGenerators.Enums;
@ -110,4 +107,4 @@ public sealed class EnumExtensionsGenerator : AttributeEnumGeneratorBase
{
return $"{symbol.Name}.EnumExtensions.g.cs";
}
}
}

View File

@ -30,7 +30,7 @@
<!-- Generator 编译期引用 SourceGenerators.Abstractions / Common / Core.Abstractions但不打包 -->
<ItemGroup>
<ProjectReference Include="..\$(AssemblyName).Abstractions\$(AssemblyName).Abstractions.csproj" PrivateAssets="all"/>
<ProjectReference Include="..\$(AssemblyName).Common\$(AssemblyName).Common.csproj" PrivateAssets="all"/>
<ProjectReference Include="..\GFramework.SourceGenerators.Common\GFramework.SourceGenerators.Common.csproj" PrivateAssets="all"/>
</ItemGroup>
<ItemGroup>
@ -63,6 +63,6 @@
<None Include="$(OutputPath)\$(AssemblyName).Abstractions.xml" Pack="true" PackagePath="lib\netstandard2.0" Visible="true"/>
<None Include="$(OutputPath)\$(AssemblyName).Common.dll" Pack="true" PackagePath="lib\netstandard2.0" Visible="true"/>
<None Include="$(OutputPath)\$(AssemblyName).Common.xml" Pack="true" PackagePath="lib\netstandard2.0" Visible="true"/>
<None Include="GeWuYou.$(AssemblyName).targets" Pack="true" PackagePath="build" Visible="false"/>
<None Include="GeWuYou.GFramework.Core.SourceGenerators.targets" Pack="true" PackagePath="build" Visible="false"/>
</ItemGroup>
</Project>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Analyzer Include="$(MSBuildThisFileDirectory)../analyzers/dotnet/cs/GFramework.Core.SourceGenerators.dll"
Condition="Exists('$(MSBuildThisFileDirectory)../analyzers/dotnet/cs/GFramework.Core.SourceGenerators.dll')"/>
<Analyzer Include="$(MSBuildThisFileDirectory)../analyzers/dotnet/cs/GFramework.Core.SourceGenerators.Abstractions.dll"
Condition="Exists('$(MSBuildThisFileDirectory)../analyzers/dotnet/cs/GFramework.Core.SourceGenerators.Abstractions.dll')"/>
<Analyzer Include="$(MSBuildThisFileDirectory)../analyzers/dotnet/cs/GFramework.SourceGenerators.Common.dll"
Condition="Exists('$(MSBuildThisFileDirectory)../analyzers/dotnet/cs/GFramework.SourceGenerators.Common.dll')"/>
</ItemGroup>
<Target Name="EnsureGFrameworkCoreAnalyzers" BeforeTargets="CoreCompile">
<Message Text="Loading GFramework.Core source generators" Importance="high"/>
</Target>
</Project>

View File

@ -24,4 +24,4 @@ global using Microsoft.CodeAnalysis.Text;
global using System.Globalization;
global using System.IO;
global using System.Text;
global using System.Text.Json;
global using System.Text.Json;

View File

@ -1,10 +1,7 @@
using System.Text;
using GFramework.SourceGenerators.Abstractions.Logging;
using GFramework.SourceGenerators.Abstractions.Logging;
using GFramework.SourceGenerators.Common.Constants;
using GFramework.SourceGenerators.Common.Extensions;
using GFramework.SourceGenerators.Common.Generator;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace GFramework.SourceGenerators.Logging;
@ -97,4 +94,4 @@ public sealed class LoggerGenerator : TypeAttributeClassGeneratorBase
{
return $"{symbol.Name}.Logger.g.cs";
}
}
}

View File

@ -1,11 +1,7 @@
using System.Text;
using GFramework.SourceGenerators.Common.Constants;
using GFramework.SourceGenerators.Common.Constants;
using GFramework.SourceGenerators.Common.Diagnostics;
using GFramework.SourceGenerators.Common.Generator;
using GFramework.SourceGenerators.Diagnostics;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace GFramework.SourceGenerators.Rule;
@ -251,4 +247,4 @@ public sealed class ContextAwareGenerator : MetadataAttributeClassGeneratorBase
break;
}
}
}
}

View File

@ -1,4 +1,3 @@
using System.Text;
using GFramework.SourceGenerators.Common.Constants;
using GFramework.SourceGenerators.Common.Diagnostics;
using GFramework.SourceGenerators.Common.Extensions;
@ -894,4 +893,4 @@ public sealed class ContextGetGenerator : IIncrementalGenerator
public List<FieldCandidateInfo> FieldCandidates { get; } = [];
public ClassDeclarationSyntax? GetAllDeclaration { get; set; }
}
}
}

View File

@ -78,28 +78,6 @@ public class ArchitectureModulesBehaviorTests
await architecture.DestroyAsync();
}
/// <summary>
/// 验证兼容别名 <c>RegisterMediatorBehavior</c> 仍会把 CQRS 行为接入请求管道。
/// </summary>
[Test]
public async Task RegisterMediatorBehavior_Should_Apply_Pipeline_Behavior_To_Request()
{
var architecture = new ModuleTestArchitecture(target =>
target.RegisterMediatorBehavior<TrackingPipelineBehavior<ModuleBehaviorRequest, string>>());
await architecture.InitializeAsync();
var response = await architecture.Context.SendRequestAsync(new ModuleBehaviorRequest());
Assert.Multiple(() =>
{
Assert.That(response, Is.EqualTo("handled"));
Assert.That(TrackingPipelineBehavior<ModuleBehaviorRequest, string>.InvocationCount, Is.EqualTo(1));
});
await architecture.DestroyAsync();
}
/// <summary>
/// 用于测试模块行为的最小架构实现。
/// </summary>

View File

@ -206,12 +206,6 @@ public class TestArchitectureWithRegistry : IArchitecture
throw new NotImplementedException();
}
[Obsolete("Use RegisterCqrsPipelineBehavior<TBehavior>() instead.")]
public void RegisterMediatorBehavior<TBehavior>() where TBehavior : class
{
RegisterCqrsPipelineBehavior<TBehavior>();
}
public IArchitectureModule InstallModule(IArchitectureModule module)
{
throw new NotImplementedException();
@ -357,12 +351,6 @@ public class TestArchitectureWithoutRegistry : IArchitecture
throw new NotImplementedException();
}
[Obsolete("Use RegisterCqrsPipelineBehavior<TBehavior>() instead.")]
public void RegisterMediatorBehavior<TBehavior>() where TBehavior : class
{
RegisterCqrsPipelineBehavior<TBehavior>();
}
public IArchitectureModule InstallModule(IArchitectureModule module)
{
throw new NotImplementedException();

View File

@ -1,99 +0,0 @@
using System.ComponentModel;
using System.Reflection;
using GFramework.Core.Abstractions.Architectures;
using GFramework.Core.Abstractions.Ioc;
using GFramework.Core.Architectures;
using GFramework.Core.Coroutine.Extensions;
using GFramework.Core.Ioc;
namespace GFramework.Core.Tests.Cqrs;
/// <summary>
/// 锁定历史 Mediator 兼容入口的正式弃用策略。
/// 这些测试确保旧 API 不仅保留行为兼容,还会通过编译期提示和 IntelliSense 隐藏引导调用方迁移到新的 CQRS 命名。
/// </summary>
[TestFixture]
public class MediatorCompatibilityDeprecationTests
{
/// <summary>
/// 验证公开兼容方法仍可用,但已被显式标记为未来移除的旧别名。
/// </summary>
[Test]
public void Legacy_Public_Methods_Should_Be_Obsolete_And_Hidden_From_Editor_Browsing()
{
AssertLegacyMethod(typeof(IArchitecture), nameof(IArchitecture.RegisterMediatorBehavior));
AssertLegacyMethod(typeof(IIocContainer), nameof(IIocContainer.RegisterMediatorBehavior));
AssertLegacyMethod(typeof(Architecture), nameof(Architecture.RegisterMediatorBehavior));
AssertLegacyMethod(typeof(MicrosoftDiContainer), nameof(MicrosoftDiContainer.RegisterMediatorBehavior));
}
/// <summary>
/// 验证历史扩展类型会把迁移目标写入弃用说明,并从 IntelliSense 主路径隐藏。
/// </summary>
[Test]
public void Legacy_Extension_Types_Should_Be_Obsolete_And_Hidden_From_Editor_Browsing()
{
AssertLegacyType(
typeof(ContextAwareMediatorExtensions),
"Use GFramework.Core.Extensions.ContextAwareCqrsExtensions instead.");
AssertLegacyType(
typeof(ContextAwareMediatorCommandExtensions),
"Use GFramework.Cqrs.Extensions.ContextAwareCqrsCommandExtensions instead.");
AssertLegacyType(
typeof(ContextAwareMediatorQueryExtensions),
"Use GFramework.Cqrs.Extensions.ContextAwareCqrsQueryExtensions instead.");
AssertLegacyType(
typeof(MediatorCoroutineExtensions),
"Use GFramework.Core.Coroutine.Extensions.CqrsCoroutineExtensions instead.");
}
/// <summary>
/// 断言方法级兼容 API 具备统一的弃用元数据。
/// </summary>
/// <param name="declaringType">声明该方法的类型。</param>
/// <param name="methodName">方法名称。</param>
private static void AssertLegacyMethod(Type declaringType, string methodName)
{
var method = declaringType
.GetMethods(BindingFlags.Public | BindingFlags.Instance)
.Single(candidate => candidate.Name == methodName);
var obsoleteAttribute = method.GetCustomAttribute<ObsoleteAttribute>();
var editorBrowsableAttribute = method.GetCustomAttribute<EditorBrowsableAttribute>();
Assert.Multiple(() =>
{
Assert.That(obsoleteAttribute, Is.Not.Null);
Assert.That(
obsoleteAttribute!.Message,
Does.Contain("Use RegisterCqrsPipelineBehavior<TBehavior>() instead."));
Assert.That(
obsoleteAttribute.Message,
Does.Contain("removed in a future major version"));
Assert.That(editorBrowsableAttribute, Is.Not.Null);
Assert.That(editorBrowsableAttribute!.State, Is.EqualTo(EditorBrowsableState.Never));
});
}
/// <summary>
/// 断言类型级兼容扩展具备统一的弃用元数据。
/// </summary>
/// <param name="type">兼容扩展类型。</param>
/// <param name="expectedReplacementHint">期望的迁移提示。</param>
private static void AssertLegacyType(Type type, string expectedReplacementHint)
{
var obsoleteAttribute = type.GetCustomAttribute<ObsoleteAttribute>();
var editorBrowsableAttribute = type.GetCustomAttribute<EditorBrowsableAttribute>();
Assert.Multiple(() =>
{
Assert.That(obsoleteAttribute, Is.Not.Null);
Assert.That(obsoleteAttribute!.Message, Does.Contain(expectedReplacementHint));
Assert.That(
obsoleteAttribute.Message,
Does.Contain("removed in a future major version"));
Assert.That(editorBrowsableAttribute, Is.Not.Null);
Assert.That(editorBrowsableAttribute!.State, Is.EqualTo(EditorBrowsableState.Never));
});
}
}

View File

@ -21,9 +21,11 @@
<ProjectReference Include="..\GFramework.Tests.Common\GFramework.Tests.Common.csproj"/>
<ProjectReference Include="..\GFramework.Core.Abstractions\GFramework.Core.Abstractions.csproj"/>
<ProjectReference Include="..\GFramework.Core\GFramework.Core.csproj"/>
<ProjectReference Include="..\GFramework.SourceGenerators.Abstractions\GFramework.SourceGenerators.Abstractions.csproj"/>
<ProjectReference Include="..\GFramework.SourceGenerators.Common\GFramework.SourceGenerators.Common.csproj"/>
<ProjectReference Include="..\GFramework.SourceGenerators\GFramework.SourceGenerators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
<ProjectReference Include="..\GFramework.Core.SourceGenerators.Abstractions\GFramework.Core.SourceGenerators.Abstractions.csproj"/>
<ProjectReference Include="..\GFramework.SourceGenerators.Common\GFramework.SourceGenerators.Common.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false"/>
<ProjectReference Include="..\GFramework.Core.SourceGenerators\GFramework.Core.SourceGenerators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
</ItemGroup>
</Project>

View File

@ -1,4 +1,3 @@
using System.ComponentModel;
using System.Reflection;
using GFramework.Core.Abstractions.Architectures;
using GFramework.Core.Abstractions.Enums;
@ -8,7 +7,6 @@ using GFramework.Core.Abstractions.Model;
using GFramework.Core.Abstractions.Systems;
using GFramework.Core.Abstractions.Utility;
using GFramework.Core.Environment;
using GFramework.Core.Logging;
namespace GFramework.Core.Architectures;
@ -156,20 +154,6 @@ public abstract class Architecture : IArchitecture
_modules.RegisterCqrsPipelineBehavior<TBehavior>();
}
/// <summary>
/// 注册 CQRS 请求管道行为。
/// 该成员保留旧名称以兼容历史调用点,内部行为与 <see cref="RegisterCqrsPipelineBehavior{TBehavior}" /> 一致。
/// 新代码不应继续依赖该别名;兼容层计划在未来的 major 版本中移除。
/// </summary>
/// <typeparam name="TBehavior">行为类型,必须是引用类型</typeparam>
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete(
"Use RegisterCqrsPipelineBehavior<TBehavior>() instead. This compatibility alias will be removed in a future major version.")]
public void RegisterMediatorBehavior<TBehavior>() where TBehavior : class
{
RegisterCqrsPipelineBehavior<TBehavior>();
}
/// <summary>
/// 从指定程序集显式注册 CQRS 处理器。
/// 该入口适用于把拆分到其他模块或扩展包程序集中的 handlers 接入当前架构。

View File

@ -1,4 +1,3 @@
using System.ComponentModel;
using System.Reflection;
using GFramework.Core.Abstractions.Architectures;
using GFramework.Core.Abstractions.Logging;
@ -25,20 +24,6 @@ internal sealed class ArchitectureModules(
services.Container.RegisterCqrsPipelineBehavior<TBehavior>();
}
/// <summary>
/// 注册 CQRS 请求管道行为。
/// 该成员保留旧名称以兼容历史调用点,内部行为与 <see cref="RegisterCqrsPipelineBehavior{TBehavior}" /> 一致。
/// 新代码不应继续依赖该别名;兼容层计划在未来的 major 版本中移除。
/// </summary>
/// <typeparam name="TBehavior">行为类型,必须是引用类型</typeparam>
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete(
"Use RegisterCqrsPipelineBehavior<TBehavior>() instead. This compatibility alias will be removed in a future major version.")]
public void RegisterMediatorBehavior<TBehavior>() where TBehavior : class
{
RegisterCqrsPipelineBehavior<TBehavior>();
}
/// <summary>
/// 从指定程序集显式注册 CQRS 处理器。
/// 该入口用于把默认架构程序集之外的扩展处理器接入当前架构容器。

View File

@ -1,47 +0,0 @@
// Copyright (c) 2026 GeWuYou
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using System.ComponentModel;
using GFramework.Core.Abstractions.Coroutine;
using GFramework.Core.Abstractions.Rule;
using GFramework.Cqrs.Abstractions.Cqrs;
namespace GFramework.Core.Coroutine.Extensions;
/// <summary>
/// 提供 CQRS 命令与协程集成的扩展方法。
/// 该类型保留旧名称以兼容历史调用点;新代码应改用 <see cref="CqrsCoroutineExtensions" />。
/// 兼容层计划在未来的 major 版本中移除,因此不会继续承载新能力。
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete(
"Use GFramework.Core.Coroutine.Extensions.CqrsCoroutineExtensions instead. This compatibility alias will be removed in a future major version.")]
public static class MediatorCoroutineExtensions
{
/// <summary>
/// 以协程方式发送无返回值 CQRS 命令并处理可能的异常。
/// </summary>
/// <typeparam name="TCommand">命令的类型。</typeparam>
/// <param name="contextAware">上下文感知对象,用于获取服务</param>
/// <param name="command">要发送的命令对象</param>
/// <param name="onError">发生异常时的回调处理函数</param>
/// <returns>协程枚举器,用于协程执行</returns>
public static IEnumerator<IYieldInstruction> SendCommandCoroutine<TCommand>(
this IContextAware contextAware,
TCommand command,
Action<Exception>? onError = null)
where TCommand : IRequest<Unit>
{
return CqrsCoroutineExtensions.SendCommandCoroutine(contextAware, command, onError);
}
}

View File

@ -1,49 +0,0 @@
using System.ComponentModel;
using GFramework.Core.Abstractions.Rule;
using GFramework.Cqrs.Abstractions.Cqrs.Command;
using GFramework.Cqrs.Extensions;
namespace GFramework.Core.Extensions;
/// <summary>
/// 提供对 <see cref="IContextAware" /> 接口的 CQRS 命令扩展方法。
/// 该类型保留旧名称以兼容历史调用点;新代码应改用 <see cref="ContextAwareCqrsCommandExtensions" />。
/// 兼容层计划在未来的 major 版本中移除,因此不会继续承载新能力。
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete(
"Use GFramework.Cqrs.Extensions.ContextAwareCqrsCommandExtensions instead. This compatibility alias will be removed in a future major version.")]
public static class ContextAwareMediatorCommandExtensions
{
/// <summary>
/// 发送命令的同步版本(不推荐,仅用于兼容性)
/// </summary>
/// <typeparam name="TResponse">命令响应类型</typeparam>
/// <param name="contextAware">实现 IContextAware 接口的对象</param>
/// <param name="command">要发送的命令对象</param>
/// <returns>命令执行结果</returns>
/// <exception cref="ArgumentNullException">当 contextAware 或 command 为 null 时抛出</exception>
public static TResponse SendCommand<TResponse>(this IContextAware contextAware,
ICommand<TResponse> command)
{
return ContextAwareCqrsCommandExtensions.SendCommand(contextAware, command);
}
/// <summary>
/// 异步发送命令并返回结果
/// </summary>
/// <typeparam name="TResponse">命令响应类型</typeparam>
/// <param name="contextAware">实现 IContextAware 接口的对象</param>
/// <param name="command">要发送的命令对象</param>
/// <param name="cancellationToken">取消令牌,用于取消操作</param>
/// <returns>包含命令执行结果的ValueTask</returns>
/// <exception cref="ArgumentNullException">当 contextAware 或 command 为 null 时抛出</exception>
public static ValueTask<TResponse> SendCommandAsync<TResponse>(this IContextAware contextAware,
ICommand<TResponse> command, CancellationToken cancellationToken = default)
{
return ContextAwareCqrsCommandExtensions.SendCommandAsync(
contextAware,
command,
cancellationToken);
}
}

View File

@ -1,123 +0,0 @@
using System.ComponentModel;
using GFramework.Core.Abstractions.Rule;
using GFramework.Cqrs.Abstractions.Cqrs;
using GFramework.Cqrs.Extensions;
namespace GFramework.Core.Extensions;
/// <summary>
/// 提供对 <see cref="IContextAware" /> 接口的 CQRS 统一接口扩展方法。
/// 该类型保留旧名称以兼容历史调用点;新代码应改用 <see cref="ContextAwareCqrsExtensions" />。
/// 兼容层计划在未来的 major 版本中移除,因此不会继续承载新能力。
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete(
"Use GFramework.Core.Extensions.ContextAwareCqrsExtensions instead. This compatibility alias will be removed in a future major version.")]
public static class ContextAwareMediatorExtensions
{
/// <summary>
/// 发送请求(统一处理 Command/Query
/// </summary>
/// <typeparam name="TResponse">响应类型</typeparam>
/// <param name="contextAware">实现 IContextAware 接口的对象</param>
/// <param name="request">要发送的请求</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>请求结果</returns>
/// <exception cref="ArgumentNullException">当 contextAware 或 request 为 null 时抛出</exception>
public static ValueTask<TResponse> SendRequestAsync<TResponse>(this IContextAware contextAware,
IRequest<TResponse> request, CancellationToken cancellationToken = default)
{
return ContextAwareCqrsExtensions.SendRequestAsync(
contextAware,
request,
cancellationToken);
}
/// <summary>
/// 发送请求(同步版本,不推荐)
/// </summary>
/// <typeparam name="TResponse">响应类型</typeparam>
/// <param name="contextAware">实现 IContextAware 接口的对象</param>
/// <param name="request">要发送的请求</param>
/// <returns>请求结果</returns>
/// <exception cref="ArgumentNullException">当 contextAware 或 request 为 null 时抛出</exception>
public static TResponse SendRequest<TResponse>(this IContextAware contextAware,
IRequest<TResponse> request)
{
return ContextAwareCqrsExtensions.SendRequest(contextAware, request);
}
/// <summary>
/// 发布通知(一对多事件)
/// </summary>
/// <typeparam name="TNotification">通知类型</typeparam>
/// <param name="contextAware">实现 IContextAware 接口的对象</param>
/// <param name="notification">要发布的通知</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>异步任务</returns>
/// <exception cref="ArgumentNullException">当 contextAware 或 notification 为 null 时抛出</exception>
public static ValueTask PublishAsync<TNotification>(this IContextAware contextAware,
TNotification notification, CancellationToken cancellationToken = default)
where TNotification : INotification
{
return ContextAwareCqrsExtensions.PublishAsync(
contextAware,
notification,
cancellationToken);
}
/// <summary>
/// 创建流式请求(用于大数据集)
/// </summary>
/// <typeparam name="TResponse">响应类型</typeparam>
/// <param name="contextAware">实现 IContextAware 接口的对象</param>
/// <param name="request">流式请求</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>异步响应流</returns>
/// <exception cref="ArgumentNullException">当 contextAware 或 request 为 null 时抛出</exception>
public static IAsyncEnumerable<TResponse> CreateStream<TResponse>(this IContextAware contextAware,
IStreamRequest<TResponse> request, CancellationToken cancellationToken = default)
{
return ContextAwareCqrsExtensions.CreateStream(
contextAware,
request,
cancellationToken);
}
/// <summary>
/// 发送命令(无返回值)
/// </summary>
/// <typeparam name="TCommand">命令类型</typeparam>
/// <param name="contextAware">实现 IContextAware 接口的对象</param>
/// <param name="command">要发送的命令</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>异步任务</returns>
/// <exception cref="ArgumentNullException">当 contextAware 或 command 为 null 时抛出</exception>
public static ValueTask SendAsync<TCommand>(this IContextAware contextAware, TCommand command,
CancellationToken cancellationToken = default)
where TCommand : IRequest<Unit>
{
return ContextAwareCqrsExtensions.SendAsync(
contextAware,
command,
cancellationToken);
}
/// <summary>
/// 发送命令(有返回值)
/// </summary>
/// <typeparam name="TResponse">响应类型</typeparam>
/// <param name="contextAware">实现 IContextAware 接口的对象</param>
/// <param name="command">要发送的命令</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>命令执行结果</returns>
/// <exception cref="ArgumentNullException">当 contextAware 或 command 为 null 时抛出</exception>
public static ValueTask<TResponse> SendAsync<TResponse>(this IContextAware contextAware,
IRequest<TResponse> command, CancellationToken cancellationToken = default)
{
return ContextAwareCqrsExtensions.SendAsync(
contextAware,
command,
cancellationToken);
}
}

View File

@ -1,48 +0,0 @@
using System.ComponentModel;
using GFramework.Core.Abstractions.Rule;
using GFramework.Cqrs.Abstractions.Cqrs.Query;
using GFramework.Cqrs.Extensions;
namespace GFramework.Core.Extensions;
/// <summary>
/// 提供对 <see cref="IContextAware" /> 接口的 CQRS 查询扩展方法。
/// 该类型保留旧名称以兼容历史调用点;新代码应改用 <see cref="ContextAwareCqrsQueryExtensions" />。
/// 兼容层计划在未来的 major 版本中移除,因此不会继续承载新能力。
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete(
"Use GFramework.Cqrs.Extensions.ContextAwareCqrsQueryExtensions instead. This compatibility alias will be removed in a future major version.")]
public static class ContextAwareMediatorQueryExtensions
{
/// <summary>
/// 发送查询的同步版本(不推荐,仅用于兼容性)
/// </summary>
/// <typeparam name="TResponse">查询响应类型</typeparam>
/// <param name="contextAware">实现 IContextAware 接口的对象</param>
/// <param name="query">要发送的查询对象</param>
/// <returns>查询结果</returns>
/// <exception cref="ArgumentNullException">当 contextAware 或 query 为 null 时抛出</exception>
public static TResponse SendQuery<TResponse>(this IContextAware contextAware, IQuery<TResponse> query)
{
return ContextAwareCqrsQueryExtensions.SendQuery(contextAware, query);
}
/// <summary>
/// 异步发送查询并返回结果
/// </summary>
/// <typeparam name="TResponse">查询响应类型</typeparam>
/// <param name="contextAware">实现 IContextAware 接口的对象</param>
/// <param name="query">要发送的查询对象</param>
/// <param name="cancellationToken">取消令牌,用于取消操作</param>
/// <returns>包含查询结果的ValueTask</returns>
/// <exception cref="ArgumentNullException">当 contextAware 或 query 为 null 时抛出</exception>
public static ValueTask<TResponse> SendQueryAsync<TResponse>(this IContextAware contextAware,
IQuery<TResponse> query, CancellationToken cancellationToken = default)
{
return ContextAwareCqrsQueryExtensions.SendQueryAsync(
contextAware,
query,
cancellationToken);
}
}

View File

@ -1,4 +1,3 @@
using System.ComponentModel;
using System.Reflection;
using GFramework.Core.Abstractions.Bases;
using GFramework.Core.Abstractions.Ioc;
@ -367,20 +366,6 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
}
}
/// <summary>
/// 注册 CQRS 请求管道行为。
/// 该成员保留旧名称以兼容历史调用点,内部行为与 <see cref="RegisterCqrsPipelineBehavior{TBehavior}" /> 一致。
/// 新代码不应继续依赖该别名;兼容层计划在未来的 major 版本中移除。
/// </summary>
/// <typeparam name="TBehavior">行为类型,必须是引用类型</typeparam>
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete(
"Use RegisterCqrsPipelineBehavior<TBehavior>() instead. This compatibility alias will be removed in a future major version.")]
public void RegisterMediatorBehavior<TBehavior>() where TBehavior : class
{
RegisterCqrsPipelineBehavior<TBehavior>();
}
/// <summary>
/// 从指定程序集显式注册 CQRS 处理器。
/// </summary>

View File

@ -337,10 +337,17 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
return true;
}
if (type is INamedTypeSymbol namedType &&
SymbolEqualityComparer.Default.Equals(namedType.ContainingAssembly, compilation.Assembly))
if (type is INamedTypeSymbol namedType)
{
runtimeTypeReference = RuntimeTypeReferenceSpec.FromReflectionLookup(
if (SymbolEqualityComparer.Default.Equals(namedType.ContainingAssembly, compilation.Assembly))
{
runtimeTypeReference = RuntimeTypeReferenceSpec.FromReflectionLookup(
GetReflectionTypeMetadataName(namedType));
return true;
}
runtimeTypeReference = RuntimeTypeReferenceSpec.FromExternalReflectionLookup(
namedType.ContainingAssembly.Identity.Name,
GetReflectionTypeMetadataName(namedType));
return true;
}
@ -388,8 +395,10 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
return true;
}
genericTypeDefinitionReference = null;
return false;
genericTypeDefinitionReference = RuntimeTypeReferenceSpec.FromExternalReflectionLookup(
genericTypeDefinition.ContainingAssembly.Identity.Name,
GetReflectionTypeMetadataName(genericTypeDefinition));
return true;
}
private static bool CanReferenceFromGeneratedRegistry(Compilation compilation, ITypeSymbol type)
@ -492,6 +501,9 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
!registration.PreciseReflectedRegistrations.IsDefaultOrEmpty);
var hasRuntimeInterfaceDiscovery = registrations.Any(static registration =>
registration.RequiresRuntimeInterfaceDiscovery);
var hasExternalAssemblyTypeLookups = registrations.Any(static registration =>
registration.PreciseReflectedRegistrations.Any(static preciseRegistration =>
preciseRegistration.ServiceTypeArguments.Any(ContainsExternalAssemblyTypeLookup)));
var builder = new StringBuilder();
builder.AppendLine("// <auto-generated />");
builder.AppendLine("#nullable enable");
@ -556,10 +568,13 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
builder.AppendLine(" }");
if (hasRuntimeInterfaceDiscovery)
if (hasRuntimeInterfaceDiscovery || hasExternalAssemblyTypeLookups)
{
builder.AppendLine();
AppendReflectionHelpers(builder);
AppendReflectionHelpers(
builder,
hasRuntimeInterfaceDiscovery,
hasExternalAssemblyTypeLookups);
}
builder.AppendLine("}");
@ -910,14 +925,68 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
builder.Append(indent);
builder.Append("var ");
builder.Append(variableBaseName);
builder.Append(" = registryAssembly.GetType(\"");
builder.Append(EscapeStringLiteral(reflectionTypeMetadataName));
builder.AppendLine("\", throwOnError: false, ignoreCase: false);");
if (string.IsNullOrWhiteSpace(runtimeTypeReference.ReflectionAssemblyName))
{
builder.Append(" = registryAssembly.GetType(\"");
builder.Append(EscapeStringLiteral(reflectionTypeMetadataName));
builder.AppendLine("\", throwOnError: false, ignoreCase: false);");
}
else
{
builder.Append(" = ResolveReferencedAssemblyType(\"");
builder.Append(EscapeStringLiteral(runtimeTypeReference.ReflectionAssemblyName!));
builder.Append("\", \"");
builder.Append(EscapeStringLiteral(reflectionTypeMetadataName));
builder.AppendLine("\");");
}
return variableBaseName;
}
private static void AppendReflectionHelpers(StringBuilder builder)
private static void AppendReflectionHelpers(
StringBuilder builder,
bool includeRuntimeInterfaceDiscoveryHelpers,
bool includeExternalAssemblyTypeLookupHelpers)
{
if (includeExternalAssemblyTypeLookupHelpers)
{
builder.AppendLine(
" private static global::System.Type? ResolveReferencedAssemblyType(string assemblyName, string typeMetadataName)");
builder.AppendLine(" {");
builder.AppendLine(" var assembly = ResolveReferencedAssembly(assemblyName);");
builder.AppendLine(
" return assembly?.GetType(typeMetadataName, throwOnError: false, ignoreCase: false);");
builder.AppendLine(" }");
builder.AppendLine();
builder.AppendLine(
" private static global::System.Reflection.Assembly? ResolveReferencedAssembly(string assemblyName)");
builder.AppendLine(" {");
builder.AppendLine(
" foreach (var assembly in global::System.AppDomain.CurrentDomain.GetAssemblies())");
builder.AppendLine(" {");
builder.AppendLine(
" if (global::System.StringComparer.Ordinal.Equals(assembly.GetName().Name, assemblyName))");
builder.AppendLine(" return assembly;");
builder.AppendLine(" }");
builder.AppendLine();
builder.AppendLine(" try");
builder.AppendLine(" {");
builder.AppendLine(
" return global::System.Reflection.Assembly.Load(new global::System.Reflection.AssemblyName(assemblyName));");
builder.AppendLine(" }");
builder.AppendLine(" catch");
builder.AppendLine(" {");
builder.AppendLine(" return null;");
builder.AppendLine(" }");
builder.AppendLine(" }");
}
if (!includeRuntimeInterfaceDiscoveryHelpers)
return;
if (includeExternalAssemblyTypeLookupHelpers)
builder.AppendLine();
// Emit the runtime helper methods only when at least one handler still needs implementation-scoped
// interface discovery after all direct / precise registrations have been emitted.
builder.AppendLine(
@ -1038,6 +1107,32 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
.Replace("\r", "\\r");
}
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.GenericTypeDefinitionReference is not null &&
ContainsExternalAssemblyTypeLookup(runtimeTypeReference.GenericTypeDefinitionReference))
{
return true;
}
foreach (var genericTypeArgument in runtimeTypeReference.GenericTypeArguments)
{
if (ContainsExternalAssemblyTypeLookup(genericTypeArgument))
return true;
}
return false;
}
private readonly record struct HandlerRegistrationSpec(
string HandlerInterfaceDisplayName,
string ImplementationTypeDisplayName,
@ -1058,6 +1153,7 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
private sealed record RuntimeTypeReferenceSpec(
string? TypeDisplayName,
string? ReflectionTypeMetadataName,
string? ReflectionAssemblyName,
RuntimeTypeReferenceSpec? ArrayElementTypeReference,
int ArrayRank,
RuntimeTypeReferenceSpec? GenericTypeDefinitionReference,
@ -1065,19 +1161,28 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
{
public static RuntimeTypeReferenceSpec FromDirectReference(string typeDisplayName)
{
return new RuntimeTypeReferenceSpec(typeDisplayName, null, null, 0, null,
return new RuntimeTypeReferenceSpec(typeDisplayName, null, null, null, 0, null,
ImmutableArray<RuntimeTypeReferenceSpec>.Empty);
}
public static RuntimeTypeReferenceSpec FromReflectionLookup(string reflectionTypeMetadataName)
{
return new RuntimeTypeReferenceSpec(null, reflectionTypeMetadataName, null, 0, null,
return new RuntimeTypeReferenceSpec(null, reflectionTypeMetadataName, null, null, 0, null,
ImmutableArray<RuntimeTypeReferenceSpec>.Empty);
}
public static RuntimeTypeReferenceSpec FromExternalReflectionLookup(
string reflectionAssemblyName,
string reflectionTypeMetadataName)
{
return new RuntimeTypeReferenceSpec(null, reflectionTypeMetadataName, reflectionAssemblyName, null, 0,
null,
ImmutableArray<RuntimeTypeReferenceSpec>.Empty);
}
public static RuntimeTypeReferenceSpec FromArray(RuntimeTypeReferenceSpec elementTypeReference, int arrayRank)
{
return new RuntimeTypeReferenceSpec(null, null, elementTypeReference, arrayRank, null,
return new RuntimeTypeReferenceSpec(null, null, null, elementTypeReference, arrayRank, null,
ImmutableArray<RuntimeTypeReferenceSpec>.Empty);
}
@ -1085,7 +1190,7 @@ public sealed class CqrsHandlerRegistryGenerator : IIncrementalGenerator
RuntimeTypeReferenceSpec genericTypeDefinitionReference,
ImmutableArray<RuntimeTypeReferenceSpec> genericTypeArguments)
{
return new RuntimeTypeReferenceSpec(null, null, null, 0, genericTypeDefinitionReference,
return new RuntimeTypeReferenceSpec(null, null, null, null, 0, genericTypeDefinitionReference,
genericTypeArguments);
}
}

View File

@ -0,0 +1,49 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PackageId>GeWuYou.$(AssemblyName)</PackageId>
<TargetFramework>netstandard2.0</TargetFramework>
<IsRoslynAnalyzer>true</IsRoslynAnalyzer>
<IncludeBuildOutput>false</IncludeBuildOutput>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" PrivateAssets="all"/>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="4.14.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Text.Json" Version="8.0.6" PrivateAssets="all"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\GFramework.SourceGenerators.Common\GFramework.SourceGenerators.Common.csproj" PrivateAssets="all"/>
</ItemGroup>
<ItemGroup>
<Compile Include="..\GFramework.SourceGenerators.Common\Internals\IsExternalInit.cs"
Link="Internals\IsExternalInit.cs"/>
</ItemGroup>
<ItemGroup>
<None Include="$(OutputPath)\$(AssemblyName).dll"
Pack="true"
PackagePath="analyzers/dotnet/cs"
Visible="false"/>
<None Include="$(OutputPath)\GFramework.SourceGenerators.Common.dll"
Pack="true"
PackagePath="analyzers/dotnet/cs"
Visible="false"/>
</ItemGroup>
<ItemGroup>
<None Include="$(OutputPath)\GFramework.SourceGenerators.Common.dll" Pack="true" PackagePath="lib\netstandard2.0" Visible="true"/>
<None Include="$(OutputPath)\GFramework.SourceGenerators.Common.xml" Pack="true" PackagePath="lib\netstandard2.0" Visible="true"/>
</ItemGroup>
</Project>

View File

@ -0,0 +1,13 @@
global using System;
global using System.Collections.Generic;
global using System.Collections.Immutable;
global using System.IO;
global using System.Linq;
global using System.Text;
global using System.Text.Json;
global using System.Threading;
global using System.Threading.Tasks;
global using Microsoft.CodeAnalysis;
global using Microsoft.CodeAnalysis.CSharp;
global using Microsoft.CodeAnalysis.CSharp.Syntax;
global using Microsoft.CodeAnalysis.Text;

View File

@ -0,0 +1,2 @@
; Shipped analyzer release
; Intentionally empty until the split game source-generator package ships a stable analyzer release.

View File

@ -0,0 +1,17 @@
; 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_ConfigSchema_001 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
GF_ConfigSchema_002 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
GF_ConfigSchema_003 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
GF_ConfigSchema_004 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
GF_ConfigSchema_005 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
GF_ConfigSchema_006 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
GF_ConfigSchema_007 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
GF_ConfigSchema_008 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
GF_ConfigSchema_009 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics
GF_ConfigSchema_010 | GFramework.SourceGenerators.Config | Error | ConfigSchemaDiagnostics

View File

@ -31,7 +31,8 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
private const string LookupIndexReferencePropertyMessage =
"Reference properties are excluded from generated lookup indexes because they already carry cross-table semantics.";
private const string SupportedStringFormatNames = "'date', 'date-time', 'duration', 'email', 'time', 'uri', and 'uuid'";
private const string SupportedStringFormatNames =
"'date', 'date-time', 'duration', 'email', 'time', 'uri', and 'uuid'";
/// <inheritdoc />
public void Initialize(IncrementalGeneratorInitializationContext context)
@ -3183,7 +3184,8 @@ public sealed class SchemaConfigGenerator : IIncrementalGenerator
var targets = dependency.Value
.EnumerateArray()
.Where(static item => item.ValueKind == JsonValueKind.String && !string.IsNullOrWhiteSpace(item.GetString()))
.Where(static item =>
item.ValueKind == JsonValueKind.String && !string.IsNullOrWhiteSpace(item.GetString()))
.Select(static item => item.GetString()!)
.Distinct(StringComparer.Ordinal)
.ToArray();

View File

@ -0,0 +1,55 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PackageId>GeWuYou.$(AssemblyName)</PackageId>
<TargetFramework>netstandard2.0</TargetFramework>
<IsRoslynAnalyzer>true</IsRoslynAnalyzer>
<IncludeBuildOutput>false</IncludeBuildOutput>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" PrivateAssets="all"/>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="4.14.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Text.Json" Version="8.0.6" PrivateAssets="all"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\GFramework.SourceGenerators.Common\GFramework.SourceGenerators.Common.csproj" PrivateAssets="all"/>
</ItemGroup>
<ItemGroup>
<Compile Include="..\GFramework.SourceGenerators.Common\Internals\IsExternalInit.cs"
Link="Internals\IsExternalInit.cs"/>
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="AnalyzerReleases.Shipped.md"/>
<AdditionalFiles Include="AnalyzerReleases.Unshipped.md"/>
</ItemGroup>
<ItemGroup>
<None Include="$(OutputPath)\$(AssemblyName).dll"
Pack="true"
PackagePath="analyzers/dotnet/cs"
Visible="false"/>
<None Include="$(OutputPath)\GFramework.SourceGenerators.Common.dll"
Pack="true"
PackagePath="analyzers/dotnet/cs"
Visible="false"/>
</ItemGroup>
<ItemGroup>
<None Include="$(OutputPath)\GFramework.SourceGenerators.Common.dll" Pack="true" PackagePath="lib\netstandard2.0" Visible="true"/>
<None Include="$(OutputPath)\GFramework.SourceGenerators.Common.xml" Pack="true" PackagePath="lib\netstandard2.0" Visible="true"/>
<None Include="GeWuYou.GFramework.Game.SourceGenerators.targets" Pack="true" PackagePath="build" Visible="false"/>
</ItemGroup>
</Project>

View File

@ -1,8 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- This file is automatically generated by the NuGet package -->
<!-- It ensures that the source generators are properly registered during build -->
<PropertyGroup>
<!--
消费项目默认从 schemas/ 目录收集配置 schema。
@ -12,15 +9,8 @@
</PropertyGroup>
<ItemGroup>
<!--
仅在 NuGet 打包布局存在时自动注入 analyzer。
仓库内项目引用场景会通过 ProjectReference(OutputItemType=Analyzer) 提供生成器,
因此这里需要避免对不存在的打包路径做无效引用。
-->
<Analyzer Include="$(MSBuildThisFileDirectory)../analyzers/dotnet/cs/GFramework.SourceGenerators.dll"
Condition="Exists('$(MSBuildThisFileDirectory)../analyzers/dotnet/cs/GFramework.SourceGenerators.dll')"/>
<Analyzer Include="$(MSBuildThisFileDirectory)../analyzers/dotnet/cs/GFramework.SourceGenerators.Abstractions.dll"
Condition="Exists('$(MSBuildThisFileDirectory)../analyzers/dotnet/cs/GFramework.SourceGenerators.Abstractions.dll')"/>
<Analyzer Include="$(MSBuildThisFileDirectory)../analyzers/dotnet/cs/GFramework.Game.SourceGenerators.dll"
Condition="Exists('$(MSBuildThisFileDirectory)../analyzers/dotnet/cs/GFramework.Game.SourceGenerators.dll')"/>
<Analyzer Include="$(MSBuildThisFileDirectory)../analyzers/dotnet/cs/GFramework.SourceGenerators.Common.dll"
Condition="Exists('$(MSBuildThisFileDirectory)../analyzers/dotnet/cs/GFramework.SourceGenerators.Common.dll')"/>
</ItemGroup>
@ -33,8 +23,7 @@
<AdditionalFiles Include="$(MSBuildProjectDirectory)/$(GFrameworkConfigSchemaDirectory)/**/*.schema.json"/>
</ItemGroup>
<!-- Ensure the analyzers are loaded -->
<Target Name="EnsureGFrameworkAnalyzers" BeforeTargets="CoreCompile">
<Message Text="Loading GFramework source generators" Importance="high"/>
<Target Name="EnsureGFrameworkGameAnalyzers" BeforeTargets="CoreCompile">
<Message Text="Loading GFramework.Game source generators" Importance="high"/>
</Target>
</Project>

View File

@ -0,0 +1,14 @@
global using System;
global using System.Collections.Generic;
global using System.Collections.Immutable;
global using System.Globalization;
global using System.IO;
global using System.Linq;
global using System.Text;
global using System.Text.Json;
global using System.Threading;
global using System.Threading.Tasks;
global using Microsoft.CodeAnalysis;
global using Microsoft.CodeAnalysis.CSharp;
global using Microsoft.CodeAnalysis.CSharp.Syntax;
global using Microsoft.CodeAnalysis.Text;

View File

@ -20,13 +20,14 @@
<ProjectReference Include="..\GFramework.Game\GFramework.Game.csproj"/>
<ProjectReference Include="..\GFramework.Core\GFramework.Core.csproj"/>
<ProjectReference Include="..\GFramework.Godot\GFramework.Godot.csproj"/>
<ProjectReference Include="..\GFramework.SourceGenerators.Abstractions\GFramework.SourceGenerators.Abstractions.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false"/>
<ProjectReference Include="..\GFramework.Core.SourceGenerators.Abstractions\GFramework.Core.SourceGenerators.Abstractions.csproj"/>
<ProjectReference Include="..\GFramework.SourceGenerators.Common\GFramework.SourceGenerators.Common.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false"/>
<ProjectReference Include="..\GFramework.SourceGenerators\GFramework.SourceGenerators.csproj"
<ProjectReference Include="..\GFramework.Core.SourceGenerators\GFramework.Core.SourceGenerators.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false"/>
<ProjectReference Include="..\GFramework.Game.SourceGenerators\GFramework.Game.SourceGenerators.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false"/>
</ItemGroup>
@ -35,6 +36,6 @@
通过仓库内的 targets 复用消费者默认约定,确保测试项目与真实消费项目一样
自动拾取 schemas/**/*.schema.json 作为 Source Generator 的 AdditionalFiles。
-->
<Import Project="..\GFramework.SourceGenerators\GeWuYou.GFramework.SourceGenerators.targets"/>
<Import Project="..\GFramework.Game.SourceGenerators\GeWuYou.GFramework.Game.SourceGenerators.targets"/>
</Project>

View File

@ -827,10 +827,10 @@ public class CqrsHandlerRegistryGeneratorTests
/// <summary>
/// 验证当外部基类暴露的 handler interface 含有生成注册器顶层上下文不可直接引用的 protected 类型时,
/// 生成器会保留已知直注册,并只对剩余未知接口做本地 interface discovery
/// 生成器会输出定向程序集查找,而不是继续退回 implementation 级接口发现
/// </summary>
[Test]
public void Generates_Partial_Runtime_Interface_Discovery_For_Inaccessible_External_Protected_Types()
public void Generates_Precise_Assembly_Type_Lookups_For_Inaccessible_External_Protected_Types()
{
const string contractsSource = """
namespace GFramework.Cqrs.Abstractions.Cqrs
@ -931,30 +931,28 @@ public class CqrsHandlerRegistryGeneratorTests
Assert.That(
generatedSource,
Does.Contain(
"var knownServiceTypes0 = new global::System.Collections.Generic.HashSet<global::System.Type>();"));
"ResolveReferencedAssemblyType(\"Dependency\", \"Dep.VisibilityScope+ProtectedRequest\")"));
Assert.That(
generatedSource,
Does.Contain(
"// Remaining runtime interface discovery target: GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<Dep.VisibilityScope.ProtectedRequest, Dep.VisibilityScope.ProtectedResponse[]>"));
"ResolveReferencedAssemblyType(\"Dependency\", \"Dep.VisibilityScope+ProtectedResponse\")"));
Assert.That(
generatedSource,
Does.Contain(
"knownServiceTypes0.Add(typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<global::Dep.VisibleRequest, string>));"));
"typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<global::Dep.VisibleRequest, string>)"));
Assert.That(
generatedSource,
Does.Contain(
"RegisterRemainingReflectedHandlerInterfaces(services, logger, implementationType0, knownServiceTypes0);"));
Does.Contain("ResolveReferencedAssembly(string assemblyName)"));
Assert.That(
generatedSource,
Does.Contain("if (knownServiceTypes.Contains(handlerInterface))"));
Does.Not.Contain("knownServiceTypes0"));
Assert.That(
generatedSource,
Does.Contain(
"Registered CQRS handler TestApp.DerivedHandler as GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<Dep.VisibleRequest, string>."));
Does.Not.Contain("RegisterRemainingReflectedHandlerInterfaces"));
Assert.That(
generatedSource,
Does.Not.Contain(
"typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<global::Dep.VisibilityScope.ProtectedRequest"));
"// Remaining runtime interface discovery target:"));
});
}

View File

@ -23,7 +23,9 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\GFramework.SourceGenerators\GFramework.SourceGenerators.csproj"/>
<ProjectReference Include="..\GFramework.Core.SourceGenerators\GFramework.Core.SourceGenerators.csproj"/>
<ProjectReference Include="..\GFramework.Cqrs.SourceGenerators\GFramework.Cqrs.SourceGenerators.csproj"/>
<ProjectReference Include="..\GFramework.Game.SourceGenerators\GFramework.Game.SourceGenerators.csproj"/>
</ItemGroup>
<ItemGroup>

View File

@ -29,21 +29,25 @@
<None Remove="GFramework.Core\**"/>
<None Remove="GFramework.Game\**"/>
<None Remove="GFramework.Godot\**"/>
<None Update="GFramework.SourceGenerators\logging\README.md">
<None Update="GFramework.Core.SourceGenerators\logging\README.md">
<Link>GFramework.SorceGenerators\logging\README.md</Link>
</None>
<None Update="GFramework.SourceGenerators\README.md">
<None Update="GFramework.Core.SourceGenerators\README.md">
<Link>GFramework.SorceGenerators\README.md</Link>
</None>
<None Update="GFramework.SourceGenerators\AnalyzerReleases.Shipped.md">
<None Update="GFramework.Core.SourceGenerators\AnalyzerReleases.Shipped.md">
<Link>GFramework.SorceGenerators\AnalyzerReleases.Shipped.md</Link>
</None>
<None Update="GFramework.SourceGenerators\AnalyzerReleases.Unshipped.md">
<None Update="GFramework.Core.SourceGenerators\AnalyzerReleases.Unshipped.md">
<Link>GFramework.SorceGenerators\AnalyzerReleases.Unshipped.md</Link>
</None>
<None Remove="GFramework.Godot.SourceGenerators\**"/>
<None Remove="GFramework.SorceGenerators\**"/>
<None Remove="GFramework.SourceGenerators\**"/>
<None Remove="GFramework.Core.SourceGenerators\**"/>
<None Remove="GFramework.Core.SourceGenerators.Abstractions\**"/>
<None Remove="GFramework.Cqrs.SourceGenerators\**"/>
<None Remove="GFramework.Game.SourceGenerators\**"/>
<None Remove="GFramework.SourceGenerators.Common\**"/>
<None Remove="GFramework.SourceGenerators.Tests\**"/>
<None Remove="GFramework.Godot.SourceGenerators.Tests\**"/>
@ -77,18 +81,22 @@
<Compile Remove="GFramework.Core\**"/>
<Compile Remove="GFramework.Game\**"/>
<Compile Remove="GFramework.Godot\**"/>
<Compile Update="GFramework.SourceGenerators\enums\EnumExtensionsGenerator.cs">
<Compile Update="GFramework.Core.SourceGenerators\enums\EnumExtensionsGenerator.cs">
<Link>GFramework.SorceGenerators\enums\EnumExtensionsGenerator.cs</Link>
</Compile>
<Compile Update="GFramework.SourceGenerators\logging\Diagnostic.cs">
<Compile Update="GFramework.Core.SourceGenerators\logging\Diagnostic.cs">
<Link>GFramework.SorceGenerators\logging\Diagnostic.cs</Link>
</Compile>
<Compile Update="GFramework.SourceGenerators\logging\LoggerGenerator.cs">
<Compile Update="GFramework.Core.SourceGenerators\logging\LoggerGenerator.cs">
<Link>GFramework.SorceGenerators\logging\LoggerGenerator.cs</Link>
</Compile>
<Compile Remove="GFramework.Godot.SourceGenerators\**"/>
<Compile Remove="GFramework.SorceGenerators\**"/>
<Compile Remove="GFramework.SourceGenerators\**"/>
<Compile Remove="GFramework.Core.SourceGenerators\**"/>
<Compile Remove="GFramework.Core.SourceGenerators.Abstractions\**"/>
<Compile Remove="GFramework.Cqrs.SourceGenerators\**"/>
<Compile Remove="GFramework.Game.SourceGenerators\**"/>
<Compile Remove="GFramework.SourceGenerators.Common\**"/>
<Compile Remove="GFramework.SourceGenerators.Tests\**"/>
<Compile Remove="GFramework.Godot.SourceGenerators.Tests\**"/>
@ -120,6 +128,10 @@
<EmbeddedResource Remove="GFramework.Godot.SourceGenerators\**"/>
<EmbeddedResource Remove="GFramework.SorceGenerators\**"/>
<EmbeddedResource Remove="GFramework.SourceGenerators\**"/>
<EmbeddedResource Remove="GFramework.Core.SourceGenerators\**"/>
<EmbeddedResource Remove="GFramework.Core.SourceGenerators.Abstractions\**"/>
<EmbeddedResource Remove="GFramework.Cqrs.SourceGenerators\**"/>
<EmbeddedResource Remove="GFramework.Game.SourceGenerators\**"/>
<EmbeddedResource Remove="GFramework.SourceGenerators.Common\**"/>
<EmbeddedResource Remove="GFramework.SourceGenerators.Tests\**"/>
<EmbeddedResource Remove="GFramework.Godot.SourceGenerators.Tests\**"/>

View File

@ -2,10 +2,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework", "GFramework.csproj", "{9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.SourceGenerators", "GFramework.SourceGenerators\GFramework.SourceGenerators.csproj", "{E9D51809-0351-4B83-B85B-B5F469AAB3B8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.SourceGenerators.Abstractions", "GFramework.SourceGenerators.Abstractions\GFramework.SourceGenerators.Abstractions.csproj", "{84C5C3C9-5620-4924-BA04-92F813F2B70F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Core", "GFramework.Core\GFramework.Core.csproj", "{A6D5854D-79EA-487A-9ED9-396E6A1F8031}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Godot", "GFramework.Godot\GFramework.Godot.csproj", "{FC56D81A-3A3B-4B49-B318-363DFA0D8206}"
@ -46,6 +42,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Cqrs.Tests", "GF
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Tests.Common", "GFramework.Tests.Common\GFramework.Tests.Common.csproj", "{1100EE3E-A12D-4DE5-ABA8-591D3126570B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Core.SourceGenerators", "GFramework.Core.SourceGenerators\GFramework.Core.SourceGenerators.csproj", "{2E8A4BB6-DA58-484F-ACC5-A8F2FA885B36}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Core.SourceGenerators.Abstractions", "GFramework.Core.SourceGenerators.Abstractions\GFramework.Core.SourceGenerators.Abstractions.csproj", "{8858F489-4EDD-41F1-9A74-1CA1CB287EB4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Cqrs.SourceGenerators", "GFramework.Cqrs.SourceGenerators\GFramework.Cqrs.SourceGenerators.csproj", "{3FDCD803-604F-48D9-B2A8-2EC621E8D598}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GFramework.Game.SourceGenerators", "GFramework.Game.SourceGenerators\GFramework.Game.SourceGenerators.csproj", "{9D3AADF0-55E6-4F80-B9C5-875F63E170D8}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -68,30 +72,6 @@ Global
{9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}.Release|x64.Build.0 = Release|Any CPU
{9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}.Release|x86.ActiveCfg = Release|Any CPU
{9BEDDD6C-DF8B-4E71-9C75-F44EC669ABBD}.Release|x86.Build.0 = Release|Any CPU
{E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Debug|x64.ActiveCfg = Debug|Any CPU
{E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Debug|x64.Build.0 = Debug|Any CPU
{E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Debug|x86.ActiveCfg = Debug|Any CPU
{E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Debug|x86.Build.0 = Debug|Any CPU
{E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Release|Any CPU.Build.0 = Release|Any CPU
{E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Release|x64.ActiveCfg = Release|Any CPU
{E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Release|x64.Build.0 = Release|Any CPU
{E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Release|x86.ActiveCfg = Release|Any CPU
{E9D51809-0351-4B83-B85B-B5F469AAB3B8}.Release|x86.Build.0 = Release|Any CPU
{84C5C3C9-5620-4924-BA04-92F813F2B70F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{84C5C3C9-5620-4924-BA04-92F813F2B70F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{84C5C3C9-5620-4924-BA04-92F813F2B70F}.Debug|x64.ActiveCfg = Debug|Any CPU
{84C5C3C9-5620-4924-BA04-92F813F2B70F}.Debug|x64.Build.0 = Debug|Any CPU
{84C5C3C9-5620-4924-BA04-92F813F2B70F}.Debug|x86.ActiveCfg = Debug|Any CPU
{84C5C3C9-5620-4924-BA04-92F813F2B70F}.Debug|x86.Build.0 = Debug|Any CPU
{84C5C3C9-5620-4924-BA04-92F813F2B70F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{84C5C3C9-5620-4924-BA04-92F813F2B70F}.Release|Any CPU.Build.0 = Release|Any CPU
{84C5C3C9-5620-4924-BA04-92F813F2B70F}.Release|x64.ActiveCfg = Release|Any CPU
{84C5C3C9-5620-4924-BA04-92F813F2B70F}.Release|x64.Build.0 = Release|Any CPU
{84C5C3C9-5620-4924-BA04-92F813F2B70F}.Release|x86.ActiveCfg = Release|Any CPU
{84C5C3C9-5620-4924-BA04-92F813F2B70F}.Release|x86.Build.0 = Release|Any CPU
{A6D5854D-79EA-487A-9ED9-396E6A1F8031}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A6D5854D-79EA-487A-9ED9-396E6A1F8031}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A6D5854D-79EA-487A-9ED9-396E6A1F8031}.Debug|x64.ActiveCfg = Debug|Any CPU
@ -332,6 +312,54 @@ Global
{1100EE3E-A12D-4DE5-ABA8-591D3126570B}.Release|x64.Build.0 = Release|Any CPU
{1100EE3E-A12D-4DE5-ABA8-591D3126570B}.Release|x86.ActiveCfg = Release|Any CPU
{1100EE3E-A12D-4DE5-ABA8-591D3126570B}.Release|x86.Build.0 = Release|Any CPU
{2E8A4BB6-DA58-484F-ACC5-A8F2FA885B36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2E8A4BB6-DA58-484F-ACC5-A8F2FA885B36}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2E8A4BB6-DA58-484F-ACC5-A8F2FA885B36}.Debug|x64.ActiveCfg = Debug|Any CPU
{2E8A4BB6-DA58-484F-ACC5-A8F2FA885B36}.Debug|x64.Build.0 = Debug|Any CPU
{2E8A4BB6-DA58-484F-ACC5-A8F2FA885B36}.Debug|x86.ActiveCfg = Debug|Any CPU
{2E8A4BB6-DA58-484F-ACC5-A8F2FA885B36}.Debug|x86.Build.0 = Debug|Any CPU
{2E8A4BB6-DA58-484F-ACC5-A8F2FA885B36}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2E8A4BB6-DA58-484F-ACC5-A8F2FA885B36}.Release|Any CPU.Build.0 = Release|Any CPU
{2E8A4BB6-DA58-484F-ACC5-A8F2FA885B36}.Release|x64.ActiveCfg = Release|Any CPU
{2E8A4BB6-DA58-484F-ACC5-A8F2FA885B36}.Release|x64.Build.0 = Release|Any CPU
{2E8A4BB6-DA58-484F-ACC5-A8F2FA885B36}.Release|x86.ActiveCfg = Release|Any CPU
{2E8A4BB6-DA58-484F-ACC5-A8F2FA885B36}.Release|x86.Build.0 = Release|Any CPU
{8858F489-4EDD-41F1-9A74-1CA1CB287EB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8858F489-4EDD-41F1-9A74-1CA1CB287EB4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8858F489-4EDD-41F1-9A74-1CA1CB287EB4}.Debug|x64.ActiveCfg = Debug|Any CPU
{8858F489-4EDD-41F1-9A74-1CA1CB287EB4}.Debug|x64.Build.0 = Debug|Any CPU
{8858F489-4EDD-41F1-9A74-1CA1CB287EB4}.Debug|x86.ActiveCfg = Debug|Any CPU
{8858F489-4EDD-41F1-9A74-1CA1CB287EB4}.Debug|x86.Build.0 = Debug|Any CPU
{8858F489-4EDD-41F1-9A74-1CA1CB287EB4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8858F489-4EDD-41F1-9A74-1CA1CB287EB4}.Release|Any CPU.Build.0 = Release|Any CPU
{8858F489-4EDD-41F1-9A74-1CA1CB287EB4}.Release|x64.ActiveCfg = Release|Any CPU
{8858F489-4EDD-41F1-9A74-1CA1CB287EB4}.Release|x64.Build.0 = Release|Any CPU
{8858F489-4EDD-41F1-9A74-1CA1CB287EB4}.Release|x86.ActiveCfg = Release|Any CPU
{8858F489-4EDD-41F1-9A74-1CA1CB287EB4}.Release|x86.Build.0 = Release|Any CPU
{3FDCD803-604F-48D9-B2A8-2EC621E8D598}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3FDCD803-604F-48D9-B2A8-2EC621E8D598}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3FDCD803-604F-48D9-B2A8-2EC621E8D598}.Debug|x64.ActiveCfg = Debug|Any CPU
{3FDCD803-604F-48D9-B2A8-2EC621E8D598}.Debug|x64.Build.0 = Debug|Any CPU
{3FDCD803-604F-48D9-B2A8-2EC621E8D598}.Debug|x86.ActiveCfg = Debug|Any CPU
{3FDCD803-604F-48D9-B2A8-2EC621E8D598}.Debug|x86.Build.0 = Debug|Any CPU
{3FDCD803-604F-48D9-B2A8-2EC621E8D598}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3FDCD803-604F-48D9-B2A8-2EC621E8D598}.Release|Any CPU.Build.0 = Release|Any CPU
{3FDCD803-604F-48D9-B2A8-2EC621E8D598}.Release|x64.ActiveCfg = Release|Any CPU
{3FDCD803-604F-48D9-B2A8-2EC621E8D598}.Release|x64.Build.0 = Release|Any CPU
{3FDCD803-604F-48D9-B2A8-2EC621E8D598}.Release|x86.ActiveCfg = Release|Any CPU
{3FDCD803-604F-48D9-B2A8-2EC621E8D598}.Release|x86.Build.0 = Release|Any CPU
{9D3AADF0-55E6-4F80-B9C5-875F63E170D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9D3AADF0-55E6-4F80-B9C5-875F63E170D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9D3AADF0-55E6-4F80-B9C5-875F63E170D8}.Debug|x64.ActiveCfg = Debug|Any CPU
{9D3AADF0-55E6-4F80-B9C5-875F63E170D8}.Debug|x64.Build.0 = Debug|Any CPU
{9D3AADF0-55E6-4F80-B9C5-875F63E170D8}.Debug|x86.ActiveCfg = Debug|Any CPU
{9D3AADF0-55E6-4F80-B9C5-875F63E170D8}.Debug|x86.Build.0 = Debug|Any CPU
{9D3AADF0-55E6-4F80-B9C5-875F63E170D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9D3AADF0-55E6-4F80-B9C5-875F63E170D8}.Release|Any CPU.Build.0 = Release|Any CPU
{9D3AADF0-55E6-4F80-B9C5-875F63E170D8}.Release|x64.ActiveCfg = Release|Any CPU
{9D3AADF0-55E6-4F80-B9C5-875F63E170D8}.Release|x64.Build.0 = Release|Any CPU
{9D3AADF0-55E6-4F80-B9C5-875F63E170D8}.Release|x86.ActiveCfg = Release|Any CPU
{9D3AADF0-55E6-4F80-B9C5-875F63E170D8}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -246,9 +246,8 @@ public class GameArchitecture : Architecture
优先使用程序集级生成注册器,失败时自动回退到反射扫描;如果同一程序集已经由默认路径或其他模块接入,框架会自动去重,避免重复注册
handler。
`RegisterCqrsPipelineBehavior<TBehavior>()` 是推荐入口;旧的 `RegisterMediatorBehavior<TBehavior>()`
仅作为兼容名称保留,当前已标记为 `Obsolete` 并从 IntelliSense 主路径隐藏,计划在未来 major 版本中移除。
`ContextAwareMediator*Extensions``MediatorCoroutineExtensions` 也遵循同样的弃用节奏。当前接口支持两种形式:
`RegisterCqrsPipelineBehavior<TBehavior>()` 是唯一保留的公开入口;旧的 `Mediator` 兼容别名与扩展已移除,不再继续维护。
当前接口支持两种形式:
- 开放泛型行为,例如 `LoggingBehavior<,>`,用于匹配所有请求
- 封闭行为类型,例如某个只服务于单一请求的 `SpecialBehavior`

View File

@ -153,26 +153,29 @@ GameProject/
<ItemGroup>
<ProjectReference Include="..\GFramework.Game\GFramework.Game.csproj" />
<ProjectReference Include="..\GFramework.SourceGenerators.Abstractions\GFramework.SourceGenerators.Abstractions.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
<ProjectReference Include="..\GFramework.Core.SourceGenerators.Abstractions\GFramework.Core.SourceGenerators.Abstractions.csproj" />
<ProjectReference Include="..\GFramework.SourceGenerators.Common\GFramework.SourceGenerators.Common.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
<ProjectReference Include="..\GFramework.SourceGenerators\GFramework.SourceGenerators.csproj"
<ProjectReference Include="..\GFramework.Core.SourceGenerators\GFramework.Core.SourceGenerators.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
<ProjectReference Include="..\GFramework.Game.SourceGenerators\GFramework.Game.SourceGenerators.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
</ItemGroup>
<Import Project="..\GFramework.SourceGenerators\GeWuYou.GFramework.SourceGenerators.targets" />
<Import Project="..\GFramework.Game.SourceGenerators\GeWuYou.GFramework.Game.SourceGenerators.targets" />
</Project>
```
这段配置的作用:
- `GFramework.Game` 提供运行时 `YamlConfigLoader``ConfigRegistry``GameConfigBootstrap``GameConfigModule` 和只读表实现
- 三个 `ProjectReference(... OutputItemType="Analyzer")` 把生成器接进当前消费者项目
- `GeWuYou.GFramework.SourceGenerators.targets` 自动把 `schemas/**/*.schema.json` 加入 `AdditionalFiles`
- `GFramework.Core.SourceGenerators.Abstractions` 提供 `Core` 侧 source-generator attributes
- `GFramework.SourceGenerators.Common``GFramework.Core.SourceGenerators``GFramework.Game.SourceGenerators`
共同把生成器接进当前消费者项目
- `GeWuYou.GFramework.Game.SourceGenerators.targets` 自动把 `schemas/**/*.schema.json` 加入 `AdditionalFiles`
如果你使用打包后的 NuGet而不是仓库内项目引用原则保持不变