GeWuYou 04123d2a71 docs: 添加 CQRS 架构模式与源码生成器完整文档
- 新增 CQRS 核心概念、命令查询处理器实现指南
- 添加安装配置文档,包含各模块包说明与环境要求
- 实现源码生成器全面文档,涵盖 Log、Config Schema、CQRS Handler Registry 等特性
- 提供详细用法示例与最佳实践指导
- 包含常见问题解答与故障排查方案
- 添加 Godot 项目集成与性能优化相关内容
2026-04-17 17:41:38 +08:00

1321 lines
38 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Source Generators
> 编译时代码生成 - 零运行时开销的代码增强工具
GFramework 当前按模块提供一组 Source Generators通过编译时分析自动生成样板代码显著提升开发效率并减少运行时开销。
## 📋 目录
- [概述](#概述)
- [核心特性](#核心特性)
- [安装配置](#安装配置)
- [Log 属性生成器](#log-属性生成器)
- [Config Schema 生成器](#config-schema-生成器)
- [CQRS Handler Registry 生成器](#cqrs-handler-registry-生成器)
- [ContextAware 属性生成器](#contextaware-属性生成器)
- [GenerateEnumExtensions 属性生成器](#generateenumextensions-属性生成器)
- [Priority 属性生成器](#priority-属性生成器)
- [Context Get 注入生成器](#context-get-注入生成器)
- [AutoRegisterModule 生成器](#autoregistermodule-生成器)
- [Godot 项目元数据生成](#godot-项目元数据生成)
- [GetNode 生成器 (Godot)](#getnode-生成器)
- [BindNodeSignal 生成器 (Godot)](#bindnodesignal-生成器)
- [AutoUiPage 生成器 (Godot)](#autouipage-生成器)
- [AutoScene 生成器 (Godot)](#autoscene-生成器)
- [AutoRegisterExportedCollections 生成器 (Godot)](#autoregisterexportedcollections-生成器)
- [诊断信息](#诊断信息)
- [性能优势](#性能优势)
- [使用示例](#使用示例)
- [最佳实践](#最佳实践)
- [常见问题](#常见问题)
## 概述
GFramework 的 Source Generators 利用 Roslyn 源代码生成器技术,在编译时分析你的代码并自动生成常用的样板代码,让开发者专注于业务逻辑而不是重复的模板代码。
当前 NuGet 发布按模块拆分为:
- `GeWuYou.GFramework.Core.SourceGenerators`
- `GeWuYou.GFramework.Game.SourceGenerators`
- `GeWuYou.GFramework.Godot.SourceGenerators`
- `GeWuYou.GFramework.Cqrs.SourceGenerators`
不存在 `GeWuYou.GFramework.SourceGenerators``GeWuYou.GFramework.SourceGenerators.Attributes` 这类聚合包。
### 核心设计理念
- **零运行时开销**:代码在编译时生成,无反射或动态调用
- **类型安全**:编译时类型检查,避免运行时错误
- **开发效率**:自动生成样板代码,减少重复工作
- **可配置性**:支持多种配置选项满足不同需求
## 核心特性
### 🎯 主要生成器
- **[Log] 属性**:自动生成 ILogger 字段和日志方法
- **Config Schema 生成器**:根据 `*.schema.json` 生成配置类型和表包装
- **CQRS Handler Registry 生成器**:为 CQRS handlers 生成程序集级注册表并缩小运行时反射范围
- **[ContextAware] 属性**:自动实现 IContextAware 接口
- **[GenerateEnumExtensions] 属性**:自动生成枚举扩展方法
- **[Priority] 属性**:自动实现 IPrioritized 接口,为类添加优先级标记
- **Context Get 注入特性**自动注入架构组件GetModel/GetSystem/GetUtility/GetService/GetAll
- **[AutoRegisterModule] 属性**:为模块类自动生成固定顺序的 `Install(IArchitecture)` 注册代码
### Godot 专用生成器
- **Godot 项目元数据生成 (Godot)**:从 `project.godot` 生成 AutoLoad 与 Input Action 的强类型访问入口
- **[GetNode] 属性 (Godot)**:自动获取 Godot 节点引用,支持多种查找模式
- **[BindNodeSignal] 属性 (Godot)**:自动生成 Godot 节点信号绑定与解绑逻辑
- **[AutoUiPage] 属性 (Godot)**:自动生成 UI 页面行为包装与页面 Key
- **[AutoScene] 属性 (Godot)**:自动生成场景行为包装与场景 Key
- **[AutoRegisterExportedCollections] 属性 (Godot)**:自动生成导出集合的批量注册方法
### 🔧 高级特性
- **智能诊断**:生成器包含详细的错误诊断信息
- **增量编译**:只生成修改过的代码,提高编译速度
- **命名空间控制**:灵活控制生成代码的命名空间
- **可访问性控制**:支持不同的访问修饰符
- **智能推断注入**[GetAll] 自动推断字段类型并注入架构组件
- **优先级排序建议**:分析器自动建议使用 GetAllByPriority 确保正确排序
## 安装配置
### NuGet 包安装
```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="GeWuYou.GFramework.Core.SourceGenerators"
PrivateAssets="all"
ExcludeAssets="runtime" />
<PackageReference Include="GeWuYou.GFramework.Game.SourceGenerators"
PrivateAssets="all"
ExcludeAssets="runtime" />
</ItemGroup>
</Project>
```
如果你只使用 Godot 生成器或 CQRS 处理器注册生成器,请把上面的包替换为对应的
`GeWuYou.GFramework.Godot.SourceGenerators``GeWuYou.GFramework.Cqrs.SourceGenerators`
这些拆分包会同时带上各自需要的 abstractions 程序集,不需要再额外安装单独的 `*.Attributes` 包。
实际接入时请替换为当前发布版本,或与项目中其余 `GeWuYou.GFramework.*` 包保持同一版本。
### Config Schema 文件约定
当项目引用 `GeWuYou.GFramework.Game.SourceGenerators` 的打包产物时,生成器会默认从 `schemas/**/*.schema.json` 收集配置
schema
文件并作为 `AdditionalFiles` 输入。
这意味着消费者项目通常只需要维护如下结构:
```text
GameProject/
├─ config/
│ └─ monster/
│ └─ slime.yaml
└─ schemas/
└─ monster.schema.json
```
如果你需要完整接入运行时加载、schema 校验和 VS Code 工具链,请继续阅读:
- [游戏内容配置系统](/zh-CN/game/config-system)
## Config Schema 生成器
Config Schema 生成器会扫描 `*.schema.json` 文件,并生成:
- 配置数据类型
-`IConfigTable<TKey, TValue>` 对齐的表包装类型
这一生成器适合与 `GFramework.Game.Config.YamlConfigLoader` 配合使用,让 schema、运行时和工具链共享同一份结构约定。
当前支持的 schema 子集以内容配置系统文档中的说明为准,重点覆盖:
- `object` 根节点
- `required`
- `integer`
- `number`
- `boolean`
- `string`
- `array`
### 项目文件配置
```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
<ItemGroup>
<Compile Remove="$(CompilerGeneratedFilesOutputPath)/**/*.cs"/>
</ItemGroup>
</Project>
```
## CQRS Handler Registry 生成器
`GeWuYou.GFramework.Cqrs.SourceGenerators` 会在编译期分析当前业务程序集中的 CQRS handlers并生成
- `ICqrsHandlerRegistry` 实现,用于在启动时直接注册可安全引用的 handlers
- 程序集级 `CqrsHandlerRegistryAttribute` 元数据,供运行时优先走生成注册路径
- 必要时的 `CqrsReflectionFallbackAttribute`,让运行时只补扫生成代码无法合法引用的 handlers
### 接入包
如果你的项目已经使用 GFramework 架构层,请在现有 Core 依赖基础上补齐 CQRS runtime 与 generator
```xml
<ItemGroup>
<PackageReference Include="GeWuYou.GFramework.Cqrs" Version="1.0.0" />
<PackageReference Include="GeWuYou.GFramework.Cqrs.Abstractions" Version="1.0.0" />
<PackageReference Include="GeWuYou.GFramework.Cqrs.SourceGenerators"
Version="1.0.0"
PrivateAssets="all"
ExcludeAssets="runtime" />
</ItemGroup>
```
如果当前项目还没有接入架构运行时,请同时保持 `GeWuYou.GFramework.Core` /
`GeWuYou.GFramework.Core.Abstractions` 与 CQRS 包版本一致。
### 最小示例
下面的最小示例展示了“安装 runtime + source generator 后,正常注册程序集”的接入方式。运行时会优先使用生成的
handler registry如果某个 handler 无法被生成代码直接引用,则自动补走定向反射回退。
```csharp
using GFramework.Core.Architectures;
using GFramework.Cqrs.Abstractions.Cqrs.Command;
using GFramework.Cqrs.Cqrs.Command;
public sealed record CreatePlayerCommand(string Name) : ICommand<int>;
public sealed class CreatePlayerCommandHandler : AbstractCommandHandler<CreatePlayerCommand, int>
{
public override ValueTask<int> Handle(CreatePlayerCommand command, CancellationToken cancellationToken)
{
return ValueTask.FromResult(command.Name.Length);
}
}
public sealed class GameArchitecture : Architecture
{
protected override void OnInitialize()
{
RegisterCqrsHandlersFromAssembly(typeof(GameArchitecture).Assembly);
}
}
```
### 兼容性与迁移说明
- 不安装 `GeWuYou.GFramework.Cqrs.SourceGenerators` 也可以正常运行;此时 CQRS runtime 会继续使用反射扫描注册
handlers。
- 安装生成器后,不需要额外改写 `RegisterCqrsHandlersFromAssembly(...)` /
`RegisterCqrsHandlersFromAssemblies(...)` 调用点;运行时会自动优先使用生成注册表。
- CQRS 消息基类位于 `GFramework.Cqrs.Command` / `Query` / `Notification`,而处理器基类位于
`GFramework.Cqrs.Cqrs.*` 命名空间。文档示例需要分别引用两组命名空间。
## Log 属性生成器
[Log] 属性自动为标记的类生成日志记录功能,包括 ILogger 字段和便捷的日志方法。
### 基础使用
```csharp
using GFramework.Core.SourceGenerators.Abstractions.Logging;
[Log]
public partial class PlayerController
{
public void DoSomething()
{
Logger.Info("Doing something"); // 自动生成的 Logger 字段
Logger.Debug("Debug information");
Logger.Warning("Warning message");
Logger.Error("Error occurred");
}
}
```
### 生成的代码
编译器会自动生成如下代码:
```csharp
// <auto-generated/>
public partial class PlayerController
{
private static readonly ILogger Logger =
LoggerFactoryResolver.Provider.CreateLogger("YourNamespace.PlayerController");
}
```
**注意**:生成器只生成 ILogger 字段不生成日志方法。日志方法Info、Debug、Error 等)来自 ILogger 接口本身。
### 高级配置
```csharp
[Log(
Name = "Custom.PlayerLogger", // 自定义日志分类名称
FieldName = "CustomLogger", // 自定义字段名
IsStatic = false, // 是否为静态字段
AccessModifier = "public" // 访问修饰符
)]
public partial class CustomLoggerExample
{
public void LogSomething()
{
CustomLogger.Info("Custom logger message");
}
}
```
### 配置选项说明
| 参数 | 类型 | 默认值 | 说明 |
|----------------|---------|-----------|---------------------------------|
| Name | string? | null | 日志分类名称(默认使用类名) |
| FieldName | string | "Logger" | 生成的字段名称 |
| IsStatic | bool | true | 是否生成静态字段 |
| AccessModifier | string | "private" | 访问修饰符private/protected/public |
### 静态类支持
```csharp
[Log]
public static partial class MathHelper
{
public static int Add(int a, int b)
{
Logger.Debug($"Adding {a} and {b}");
return a + b;
}
}
```
## ContextAware 属性生成器
[ContextAware] 属性自动实现 IContextAware 接口,提供便捷的架构上下文访问能力。
### 基础使用
```csharp
using GFramework.Core.Abstractions.Controller;
using GFramework.Core.SourceGenerators.Abstractions.Rule;
[ContextAware]
public partial class PlayerController : IController
{
public void Initialize()
{
// 使用扩展方法访问架构([ContextAware] 实现 IContextAware 接口)
var playerModel = this.GetModel<PlayerModel>();
var combatSystem = this.GetSystem<CombatSystem>();
this.SendEvent(new PlayerInitializedEvent());
}
}
```
### 生成的代码
编译器会自动生成如下代码:
```csharp
// <auto-generated/>
#nullable enable
namespace YourNamespace;
partial class PlayerController : global::GFramework.Core.Abstractions.Rule.IContextAware
{
private global::GFramework.Core.Abstractions.Architecture.IArchitectureContext? _context;
private static global::GFramework.Core.Abstractions.Architecture.IArchitectureContextProvider? _contextProvider;
/// <summary>
/// 自动获取的架构上下文(懒加载,默认使用 GameContextProvider
/// </summary>
protected global::GFramework.Core.Abstractions.Architecture.IArchitectureContext Context
{
get
{
if (_context == null)
{
_contextProvider ??= new global::GFramework.Core.Architecture.GameContextProvider();
_context = _contextProvider.GetContext();
}
return _context;
}
}
/// <summary>
/// 配置上下文提供者(用于测试或多架构场景)
/// </summary>
public static void SetContextProvider(global::GFramework.Core.Abstractions.Architecture.IArchitectureContextProvider provider)
{
_contextProvider = provider;
}
/// <summary>
/// 重置上下文提供者为默认值(用于测试清理)
/// </summary>
public static void ResetContextProvider()
{
_contextProvider = null;
}
void global::GFramework.Core.Abstractions.Rule.IContextAware.SetContext(global::GFramework.Core.Abstractions.Architecture.IArchitectureContext context)
{
_context = context;
}
global::GFramework.Core.Abstractions.Architecture.IArchitectureContext global::GFramework.Core.Abstractions.Rule.IContextAware.GetContext()
{
return Context;
}
}
```
### 测试场景配置
在单元测试中,可以配置自定义的上下文提供者:
```csharp
[Test]
public async Task TestPlayerController()
{
var testArchitecture = new TestArchitecture();
await testArchitecture.InitAsync();
// 配置测试上下文提供者
PlayerController.SetContextProvider(new TestContextProvider(testArchitecture));
try
{
var controller = new PlayerController();
controller.Initialize();
// 测试逻辑...
}
finally
{
// 清理:重置上下文提供者
PlayerController.ResetContextProvider();
}
}
```
### 与其他属性组合
```csharp
using GFramework.Core.Abstractions.Controller;
using GFramework.Core.SourceGenerators.Abstractions.Logging;
using GFramework.Core.SourceGenerators.Abstractions.Rule;
[Log]
[ContextAware]
public partial class AdvancedController : IController
{
public void ProcessRequest()
{
Logger.Info("Processing request");
var model = this.GetModel<PlayerModel>();
Logger.Info($"Player health: {model.Health}");
this.SendCommand(new ProcessCommand());
Logger.Debug("Command sent");
}
}
```
## GenerateEnumExtensions 属性生成器
[GenerateEnumExtensions] 属性为枚举类型生成便捷的扩展方法,提高代码可读性和类型安全性。
### 基础使用
```csharp
using GFramework.Core.SourceGenerators.Abstractions.Enums;
[GenerateEnumExtensions]
public enum GameState
{
Playing,
Paused,
GameOver,
Menu
}
// 自动生成的扩展方法:
public static class GameStateExtensions
{
public static bool IsPlaying(this GameState state) => state == GameState.Playing;
public static bool IsPaused(this GameState state) => state == GameState.Paused;
public static bool IsGameOver(this GameState state) => state == GameState.GameOver;
public static bool IsMenu(this GameState state) => state == GameState.Menu;
public static bool IsIn(this GameState state, params GameState[] values)
{
if (values == null) return false;
foreach (var v in values) if (state == v) return true;
return false;
}
}
// 使用示例
public class GameManager
{
private GameState _currentState = GameState.Menu;
public bool CanProcessInput()
{
return _currentState.IsPlaying() || _currentState.IsMenu();
}
public bool IsGameOver()
{
return _currentState.IsGameOver();
}
public bool IsActiveState()
{
return _currentState.IsIn(GameState.Playing, GameState.Menu);
}
}
```
### 配置选项
```csharp
[GenerateEnumExtensions(
GenerateIsMethods = true, // 是否生成 IsX 方法(默认 true
GenerateIsInMethod = true // 是否生成 IsIn 方法(默认 true
)]
public enum PlayerState
{
Idle,
Walking,
Running,
Jumping,
Attacking
}
```
### 配置选项说明
| 参数 | 类型 | 默认值 | 说明 |
|--------------------|------|------|-------------------|
| GenerateIsMethods | bool | true | 是否为每个枚举值生成 IsX 方法 |
| GenerateIsInMethod | bool | true | 是否生成 IsIn 方法 |
## Godot 项目元数据生成
Godot 项目元数据生成器会读取 `project.godot`,把项目级配置转换为稳定的编译期 API。
当前生成两个统一入口:
- `GFramework.Godot.Generated.AutoLoads`
- `GFramework.Godot.Generated.InputActions`
默认情况下NuGet 包引用会自动把项目根目录下的 `project.godot` 加入 `AdditionalFiles`。如果你是在仓库内通过 analyzer
形式直接引用生成器,则需要手动加入:
```xml
<ItemGroup>
<AdditionalFiles Include="project.godot"/>
</ItemGroup>
```
### AutoLoad 映射
当某个 AutoLoad 不能仅靠类名唯一推断到 C# 节点类型时,可以使用 `[AutoLoad]` 指定映射:
```csharp
using GFramework.Godot.SourceGenerators.Abstractions;
using Godot;
[AutoLoad("GameServices")]
public partial class GameServices : Node
{
}
```
生成后可以直接使用:
```csharp
using GFramework.Godot.Generated;
var services = AutoLoads.GameServices;
if (AutoLoads.TryGetAudioBus(out var audioBus))
{
}
```
### Input Action 常量
`[input]` 段会被转换为强类型常量:
```csharp
using GFramework.Godot.Generated;
if (Input.IsActionPressed(InputActions.MoveUp))
{
}
```
完整说明请见:[Godot 项目元数据生成器](./godot-project-generator)
## GetNode 生成器
GetNode 生成器为标记了 `[GetNode]` 特性的字段自动生成 Godot 节点获取代码,无需手动调用 `GetNode<T>()` 方法。
### 主要功能
- **自动节点获取**:根据路径或字段名自动获取 Godot 节点
- **多种查找模式**:支持唯一名(`%Name`)、相对路径、绝对路径查找
- **可选节点支持**:可以标记节点为可选,获取失败时返回 null
- **_Ready 钩子**:自动生成 `_Ready()` 方法注入节点获取逻辑
### 基础示例
```csharp
using GFramework.Godot.SourceGenerators.Abstractions;
using Godot;
public partial class PlayerHud : Control
{
[GetNode]
private Label _healthLabel = null!;
[GetNode("HUD/ScoreValue")]
private Label _scoreLabel = null!;
public override void _Ready()
{
__InjectGetNodes_Generated();
_healthLabel.Text = "100";
}
}
```
**完整文档**[GetNode 生成器](./get-node-generator)
## AutoRegisterModule 生成器
AutoRegisterModule 生成器面向 GFramework 模块安装场景,为类上的注册声明自动生成 `Install(IArchitecture)`
### 主要功能
- **声明式模块注册**:用 Attribute 声明 `Model` / `System` / `Utility`
- **稳定顺序输出**:按 Attribute 书写顺序生成注册代码
- **零反射开销**:编译期生成 `new T()``architecture.RegisterXxx(...)`
### 基础示例
```csharp
using GFramework.Core.SourceGenerators.Abstractions.Architectures;
[AutoRegisterModule]
[RegisterModel(typeof(RunStateModel))]
[RegisterSystem(typeof(BuildSystem))]
[RegisterUtility(typeof(AudioUtility))]
public partial class GameplayModule
{
}
```
**完整文档**[AutoRegisterModule 生成器](./auto-register-module-generator)
## BindNodeSignal 生成器
BindNodeSignal 生成器为标记了 `[BindNodeSignal]` 特性的方法自动生成节点事件绑定和解绑代码。
### 主要功能
- **自动事件绑定**:在 `_Ready()` 中自动订阅节点事件
- **自动事件解绑**:在 `_ExitTree()` 中自动取消订阅
- **多事件绑定**:一个方法可以绑定到多个节点事件
- **类型安全检查**:编译时验证方法签名与事件委托的兼容性
### 基础示例
```csharp
using GFramework.Godot.SourceGenerators.Abstractions;
using Godot;
public partial class MainMenu : Control
{
private Button _startButton = null!;
[BindNodeSignal(nameof(_startButton), nameof(Button.Pressed))]
private void OnStartButtonPressed()
{
StartGame();
}
public override void _Ready()
{
__BindNodeSignals_Generated();
}
public override void _ExitTree()
{
__UnbindNodeSignals_Generated();
}
}
```
**完整文档**[BindNodeSignal 生成器](./bind-node-signal-generator)
## AutoUiPage 生成器
AutoUiPage 生成器为 Godot 页面节点自动生成 `UiKeyStr`、缓存的 `IUiPageBehavior``GetPage()`
### 主要功能
- **页面键统一声明**:用一个 Attribute 同时声明页面 Key 与 `UiLayer`
- **行为缓存**:延迟创建并缓存 `IUiPageBehavior`
- **减少页面样板**:不再手写重复的页面工厂包装
### 基础示例
```csharp
using GFramework.Godot.SourceGenerators.Abstractions.UI;
using GFramework.Game.Abstractions.Enums;
using Godot;
[AutoUiPage(nameof(UiKey.MainMenu), nameof(UiLayer.Page))]
public partial class MainMenu : Control
{
}
```
**完整文档**[AutoUiPage 生成器](./auto-ui-page-generator)
## AutoScene 生成器
AutoScene 生成器为场景根节点自动生成 `SceneKeyStr`、缓存的 `ISceneBehavior``GetScene()`
### 主要功能
- **场景键统一声明**:避免散落的场景 key 字符串
- **行为缓存**:延迟创建并复用场景行为对象
- **路由接入更直接**:场景节点可直接暴露标准场景行为入口
### 基础示例
```csharp
using GFramework.Godot.SourceGenerators.Abstractions.UI;
using GFramework.Game.Abstractions.Enums;
using Godot;
[AutoScene(nameof(SceneKey.Gameplay))]
public partial class GameplayRoot : Node2D
{
}
```
**完整文档**[AutoScene 生成器](./auto-scene-generator)
## AutoRegisterExportedCollections 生成器
AutoRegisterExportedCollections 生成器为 Godot 导出集合自动生成批量注册方法,适合启动入口和资源映射注册。
### 主要功能
- **批量注册样板收敛**:统一 `foreach + registry.Register(item)` 模式
- **编译期校验**:验证集合、注册器成员和方法签名
- **支持接口注册器**:兼容从基类和继承接口解析注册方法
### 基础示例
```csharp
using GFramework.Godot.SourceGenerators.Abstractions.UI;
using Godot;
[AutoRegisterExportedCollections]
public partial class GameEntryPoint : Node
{
[RegisterExportedCollection(nameof(_textureRegistry), "Registry")]
private Godot.Collections.Array<TextureConfig>? _textureConfigs;
}
```
**完整文档**[AutoRegisterExportedCollections 生成器](./auto-register-exported-collections-generator)
## 诊断信息
这一组 Source Generators 提供详细的编译时诊断信息,帮助开发者快速定位和解决问题。
### GF_Logging_001 - 日志字段名冲突
```csharp
[Log(fieldName = "Logger")]
public partial class ClassWithLogger
{
private readonly ILogger Logger; // ❌ 冲突!
}
```
**错误信息**: `GF_Logging_001: Logger field name 'Logger' conflicts with existing field`
**解决方案**: 更改字段名或移除冲突字段
```csharp
[Log(fieldName = "CustomLogger")]
public partial class ClassWithLogger
{
private readonly ILogger Logger; // ✅ 不冲突
private static readonly ILogger CustomLogger; // ✅ 生成器使用 CustomLogger
}
```
### GF_Rule_001 - ContextAware 接口冲突
```csharp
[ContextAware]
public partial class AlreadyContextAware : IContextAware // ❌ 冲突!
{
// 已实现 IContextAware
}
```
**错误信息**: `GF_Rule_001: Type already implements IContextAware interface`
**解决方案**: 移除 [ContextAware] 属性或移除手动实现
```csharp
// 方案1移除属性
public partial class AlreadyContextAware : IContextAware
{
// 手动实现
}
// 方案2移除手动实现使用生成器
[ContextAware]
public partial class AlreadyContextAware
{
// 生成器自动实现
}
```
### GF_Enum_001 - 枚举成员命名冲突
```csharp
[GenerateEnumExtensions]
public enum ConflictEnum
{
IsPlaying, // ❌ 冲突!会生成 IsIsPlaying()
HasJump // ❌ 冲突!会生成 HasHasJump()
}
```
**错误信息**: `GF_Enum_001: Enum member name conflicts with generated method`
**解决方案**: 重命名枚举成员或自定义前缀
```csharp
[GenerateEnumExtensions(customPrefix = "IsState")]
public enum ConflictEnum
{
Playing, // ✅ 生成 IsStatePlaying()
Jump // ✅ 生成 IsStateJump()
}
```
### Priority 相关诊断
`Priority` 相关规则以专用文档为权威来源:
- 完整生成器诊断见 [Priority 生成器](./priority-generator.md#诊断信息)
- 排序分析器规则见 [与 PriorityUsageAnalyzer 集成](./priority-generator.md#与-priorityusageanalyzer-集成)
| 诊断 ID | 含义 | 常见修复方向 |
|-------------------------|-------------------------------|------------------------------|
| `GF_Priority_001` | `[Priority]` 只能应用于类 | 仅在 `partial class` 上使用特性 |
| `GF_Priority_002` | 类型已手动实现 `IPrioritized` | 删除特性或删除手写接口实现 |
| `GF_Priority_003` | 标记类型未声明为 `partial` | 为类型添加 `partial` |
| `GF_Priority_004` | 优先级参数无效 | 提供有效的整数优先级值 |
| `GF_Priority_005` | 嵌套类不支持生成 | 将目标类型提取为顶层类 |
| `GF_Priority_Usage_001` | 应优先使用 `GetAllByPriority<T>()` | 对实现 `IPrioritized` 的类型改用排序获取 |
### Context Get 相关诊断
`Context Get` 相关规则以专用文档为权威来源:
- 完整诊断见 [Context Get 注入生成器](./context-get-generator.md#诊断信息)
- 调用时机建议见 [推荐调用时机与模式](./context-get-generator.md#推荐调用时机与模式)
| 诊断 ID | 含义 | 常见修复方向 |
|---------------------|--------------------|---------------------------------------------------------------|
| `GF_ContextGet_001` | 嵌套类不支持生成注入 | 将目标类型提取为顶层类 |
| `GF_ContextGet_002` | 注入字段不能为 `static` | 改为实例字段 |
| `GF_ContextGet_003` | 注入字段不能为 `readonly` | 移除 `readonly` |
| `GF_ContextGet_004` | 字段类型与注入特性不匹配 | 使用符合特性约束的字段类型 |
| `GF_ContextGet_005` | 目标类型必须具备上下文访问能力 | 添加 `[ContextAware]`、实现 `IContextAware` 或继承 `ContextAwareBase` |
| `GF_ContextGet_006` | 同一字段不能声明多个注入特性 | 每个字段只保留一个注入特性 |
| `GF_ContextRegistration_001` | `Model` 使用点没有静态可见注册 | 在所属架构的初始化链路中显式注册对应 `Model` |
| `GF_ContextRegistration_002` | `System` 使用点没有静态可见注册 | 在所属架构的初始化链路中显式注册对应 `System` |
| `GF_ContextRegistration_003` | `Utility` 使用点没有静态可见注册 | 在所属架构的初始化链路中显式注册对应 `Utility` |
## 性能优势
### 编译时 vs 运行时对比
| 特性 | 手动实现 | 反射实现 | 源码生成器 |
|-----------|------|------|-------|
| **运行时性能** | 最优 | 最差 | 最优 |
| **内存开销** | 最小 | 最大 | 最小 |
| **类型安全** | 编译时 | 运行时 | 编译时 |
| **开发效率** | 低 | 中 | 高 |
| **调试友好** | 好 | 差 | 好 |
### 基准测试结果
```csharp
// 日志性能对比 (100,000 次调用)
// 手动实现: 0.23ms
// 反射实现: 45.67ms
// 源码生成器: 0.24ms (几乎无差异)
// 上下文访问性能对比 (1,000,000 次访问)
// 手动实现: 0.12ms
// 反射实现: 23.45ms
// 源码生成器: 0.13ms (几乎无差异)
```
### 内存分配分析
```csharp
// 使用 source generators 的内存分配
[Log]
[ContextAware]
public partial class EfficientController : IController
{
public void Process()
{
Logger.Info("Processing"); // 0 分配
var model = this.GetModel<PlayerModel>(); // 0 分配
}
}
// 对比反射实现的内存分配
public class InefficientController : IController
{
public void Process()
{
var logger = GetLoggerViaReflection(); // 每次分配
var model = GetModelViaReflection<PlayerModel>(); // 每次分配
}
}
```
## 使用示例
### 完整的游戏控制器示例
```csharp
using GFramework.Core.Abstractions.Controller;
using GFramework.Core.SourceGenerators.Abstractions.Logging;
using GFramework.Core.SourceGenerators.Abstractions.Rule;
[Log]
[ContextAware]
public partial class GameController : Node, IController
{
private PlayerModel _playerModel;
private CombatSystem _combatSystem;
public override void _Ready()
{
// 初始化模型和系统引用
_playerModel = this.GetModel<PlayerModel>();
_combatSystem = this.GetSystem<CombatSystem>();
// 监听事件
this.RegisterEvent<PlayerInputEvent>(OnPlayerInput)
.UnRegisterWhenNodeExitTree(this);
Logger.Info("Game controller initialized");
}
private void OnPlayerInput(PlayerInputEvent e)
{
Logger.Debug($"Processing player input: {e.Action}");
switch (e.Action)
{
case "attack":
HandleAttack();
break;
case "defend":
HandleDefend();
break;
}
}
private void HandleAttack()
{
if (_playerModel.CanAttack())
{
Logger.Info("Player attacks");
_combatSystem.ProcessAttack();
this.SendEvent(new AttackEvent());
}
else
{
Logger.Warning("Player cannot attack - cooldown");
}
}
private void HandleDefend()
{
if (_playerModel.CanDefend())
{
Logger.Info("Player defends");
_playerModel.IsDefending.Value = true;
this.SendEvent(new DefendEvent());
}
else
{
Logger.Warning("Player cannot defend");
}
}
}
```
### 枚举状态管理示例
```csharp
[GenerateEnumExtensions(
generateIsMethods = true,
generateHasMethod = true,
generateInMethod = true,
includeToString = true
)]
public enum CharacterState
{
Idle,
Walking,
Running,
Jumping,
Falling,
Attacking,
Hurt,
Dead
}
using GFramework.Core.Abstractions.Controller;
using GFramework.Core.SourceGenerators.Abstractions.Logging;
using GFramework.Core.SourceGenerators.Abstractions.Rule;
[Log]
[ContextAware]
public partial class CharacterController : Node, IController
{
private CharacterModel _characterModel;
public override void _Ready()
{
_characterModel = this.GetModel<CharacterModel>();
// 监听状态变化
_characterModel.State.Register(OnStateChanged);
}
private void OnStateChanged(CharacterState newState)
{
Logger.Info($"Character state changed to: {newState.ToDisplayString()}");
// 使用生成的扩展方法
if (newState.IsDead())
{
HandleDeath();
}
else if (newState.IsHurt())
{
HandleHurt();
}
else if (newState.In(CharacterState.Walking, CharacterState.Running))
{
StartMovementEffects();
}
// 检查是否可以接受输入
if (newState.In(CharacterState.Idle, CharacterState.Walking, CharacterState.Running))
{
EnableInput();
}
else
{
DisableInput();
}
}
private bool CanAttack()
{
var state = _characterModel.State.Value;
return state.In(CharacterState.Idle, CharacterState.Walking, CharacterState.Running);
}
private void HandleDeath()
{
Logger.Info("Character died");
DisableInput();
PlayDeathAnimation();
this.SendEvent(new CharacterDeathEvent());
}
}
```
## 最佳实践
### 🎯 属性使用策略
#### 1. 合理的属性组合
```csharp
// 好的做法:相关功能组合使用
[Log]
[ContextAware]
public partial class BusinessLogicComponent : IComponent
{
// 既有日志记录又有上下文访问
}
// 避免:不必要的属性
[Log] // ❌ 静态工具类通常不需要日志
public static class MathHelper
{
public static int Add(int a, int b) => a + b;
}
```
#### 2. 命名约定
```csharp
// 好的做法:一致的命名
[Log(fieldName = "Logger")]
public partial class PlayerController { }
[Log(fieldName = "Logger")]
public partial class EnemyController { }
// 避免:不一致的命名
[Log(fieldName = "Logger")]
public partial class PlayerController { }
[Log(fieldName = "CustomLogger")]
public partial class EnemyController { }
```
### 🏗️ 项目组织
#### 1. 分离生成器和业务逻辑
```csharp
// 好的做法:部分类分离
// PlayerController.Logic.cs - 业务逻辑
public partial class PlayerController : IController
{
public void Move(Vector2 direction)
{
if (CanMove())
{
UpdatePosition(direction);
Logger.Debug($"Player moved to {direction}");
}
}
}
// PlayerController.Generated.cs - 生成代码所在
// 不需要手动维护,由生成器处理
```
#### 2. 枚举设计
```csharp
// 好的做法:有意义的枚举设计
[GenerateEnumExtensions]
public enum GameState
{
MainMenu, // 主菜单
Playing, // 游戏中
Paused, // 暂停
GameOver, // 游戏结束
Victory // 胜利
}
// 避免:含义不明确的枚举值
[GenerateEnumExtensions]
public enum State
{
State1,
State2,
State3
}
```
### 🔧 性能优化
#### 1. 避免过度日志
```csharp
// 好的做法:合理的日志级别
[Log]
public partial class PerformanceCriticalComponent
{
public void Update()
{
// 只在必要时记录日志
if (_performanceCounter % 1000 == 0)
{
Logger.Debug($"Performance: {_performanceCounter} ticks");
}
}
}
// 避免:过度日志记录
[Log]
public partial class NoisyComponent
{
public void Update()
{
Logger.Debug($"Frame: {Engine.GetProcessFrames()}"); // 太频繁
}
}
```
### 🛡️ 错误处理
```csharp
[Log]
[ContextAware]
public partial class RobustComponent : IComponent
{
public void RiskyOperation()
{
try
{
var model = this.GetModel<RiskyModel>();
model.PerformRiskyOperation();
Logger.Info("Operation completed successfully");
}
catch (Exception ex)
{
Logger.Error($"Operation failed: {ex.Message}");
this.SendEvent(new OperationFailedEvent { Error = ex.Message });
}
}
}
```
## 常见问题
### Q: 为什么需要标记类为 `partial`
**A**: 源代码生成器需要向现有类添加代码,`partial` 关键字允许一个类的定义分散在多个文件中。生成器会在编译时创建另一个部分类文件,包含生成的代码。
```csharp
[Log]
public partial class MyClass { } // ✅ 需要 partial
[Log]
public class MyClass { } // ❌ 编译错误,无法添加生成代码
```
### Q: 生成的代码在哪里?
**A**: 生成的代码在编译过程中创建,默认位置在 `obj/Debug/net6.0/generated/` 目录下。可以在项目文件中配置输出位置:
```xml
<PropertyGroup>
<CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
```
### Q: 如何调试生成器问题?
**A**: 生成器提供了详细的诊断信息:
1. **查看错误列表**:编译错误会显示在 IDE 中
2. **查看生成文件**:检查生成的代码文件
3. **启用详细日志**:在项目文件中添加:
```xml
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
</PropertyGroup>
```
### Q: 可以自定义生成器吗?
**A**: 当前版本的生成器支持有限的配置。如需完全自定义,可以创建自己的源代码生成器项目。
### Q: 性能影响如何?
**A**: 源代码生成器对运行时性能的影响几乎为零:
- **编译时**:可能会增加编译时间(通常几秒)
- **运行时**:与手写代码性能相同
- **内存使用**:与手写代码内存使用相同
### Q: 与依赖注入框架兼容吗?
**A**: 完全兼容。生成器创建的是标准代码,可以与任何依赖注入框架配合使用:
```csharp
[Log]
[ContextAware]
public partial class ServiceComponent : IService
{
// 可以通过构造函数注入依赖
private readonly IDependency _dependency;
public ServiceComponent(IDependency dependency)
{
_dependency = dependency;
Logger.Info("Service initialized with dependency injection");
}
}
```
---
## 依赖关系
```mermaid
graph TD
A[GeWuYou.GFramework.Core.SourceGenerators] --> B[GFramework.Core.SourceGenerators.Abstractions]
A --> C[GFramework.SourceGenerators.Common]
A --> D[GFramework.Core.Abstractions]
A --> E[Microsoft.CodeAnalysis.CSharp]
A --> F[Microsoft.CodeAnalysis.Analyzers]
G[GeWuYou.GFramework.Game.SourceGenerators] --> C
H[GeWuYou.GFramework.Godot.SourceGenerators] --> C
I[GeWuYou.GFramework.Cqrs.SourceGenerators] --> C
```
## 版本兼容性
- **.NET**: 6.0+
- **Visual Studio**: 2022 17.0+
- **Rider**: 2022.3+
- **Roslyn**: 4.0+