GeWuYou 1b9e81bbdb refactor(docs): 将控制器实现转换为上下文感知模式
- 将 IController 实现改为使用 [ContextAware] 特性和 partial 类
- 移除手动实现的 GetArchitecture 方法
- 将架构交互方法从 this 调用改为 Context 调用
- 添加必要的 using 语句以支持新的架构访问方式
- 更新所有涉及模型、系统、工具和事件的架构访问代码
- 统一架构访问模式以提高代码一致性和可维护性
2026-03-08 11:03:02 +08:00

23 KiB
Raw Blame History

GFramework.SourceGenerators

编译时代码生成 - 零运行时开销的代码增强工具

GFramework.SourceGenerators 是 GFramework 框架的源代码生成器包,通过编译时分析自动生成样板代码,显著提升开发效率并减少运行时开销。

📋 目录

概述

GFramework.SourceGenerators 利用 Roslyn 源代码生成器技术,在编译时分析你的代码并自动生成常用的样板代码,让开发者专注于业务逻辑而不是重复的模板代码。

核心设计理念

  • 零运行时开销:代码在编译时生成,无反射或动态调用
  • 类型安全:编译时类型检查,避免运行时错误
  • 开发效率:自动生成样板代码,减少重复工作
  • 可配置性:支持多种配置选项满足不同需求

核心特性

🎯 主要生成器

  • [Log] 属性:自动生成 ILogger 字段和日志方法
  • [ContextAware] 属性:自动实现 IContextAware 接口
  • [GenerateEnumExtensions] 属性:自动生成枚举扩展方法

🔧 高级特性

  • 智能诊断:生成器包含详细的错误诊断信息
  • 增量编译:只生成修改过的代码,提高编译速度
  • 命名空间控制:灵活控制生成代码的命名空间
  • 可访问性控制:支持不同的访问修饰符

安装配置

NuGet 包安装


<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <TargetFramework>net6.0</TargetFramework>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="GeWuYou.GFramework.SourceGenerators" Version="1.0.0"/>
        <PackageReference Include="GeWuYou.GFramework.SourceGenerators.Attributes" Version="1.0.0"/>
    </ItemGroup>
</Project>

项目文件配置


<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <TargetFramework>net6.0</TargetFramework>
        <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
        <CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
    </PropertyGroup>

    <ItemGroup>
        <Compile Remove="$(CompilerGeneratedFilesOutputPath)/**/*.cs"/>
    </ItemGroup>
</Project>

Log 属性生成器

[Log] 属性自动为标记的类生成日志记录功能,包括 ILogger 字段和便捷的日志方法。

基础使用

using GFramework.SourceGenerators.Attributes;

[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");
    }
}

生成的代码

编译器会自动生成如下代码:

// <auto-generated/>
public partial class PlayerController
{
    private static readonly ILogger Logger =
        LoggerFactoryResolver.Provider.CreateLogger("YourNamespace.PlayerController");
}

注意:生成器只生成 ILogger 字段不生成日志方法。日志方法Info、Debug、Error 等)来自 ILogger 接口本身。

高级配置

[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

静态类支持

[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 接口,提供便捷的架构上下文访问能力。

基础使用

using GFramework.Core.Abstractions.controller;
using GFramework.SourceGenerators.Abstractions.rule;

[ContextAware]
public partial class PlayerController : IController
{
    public void Initialize()
    {
        // Context 属性自动生成,提供架构上下文访问
        var playerModel = Context.GetModel<PlayerModel>();
        var combatSystem = Context.GetSystem<CombatSystem>();

        Context.SendEvent(new PlayerInitializedEvent());
    }
}

生成的代码

编译器会自动生成如下代码:

// <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;
    }
}

测试场景配置

在单元测试中,可以配置自定义的上下文提供者:

[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();
    }
}

与其他属性组合

using GFramework.Core.Abstractions.controller;
using GFramework.SourceGenerators.Abstractions.logging;
using GFramework.SourceGenerators.Abstractions.rule;

[Log]
[ContextAware]
public partial class AdvancedController : IController
{
    public void ProcessRequest()
    {
        Logger.Info("Processing request");

        var model = Context.GetModel<PlayerModel>();
        Logger.Info($"Player health: {model.Health}");

        Context.SendCommand(new ProcessCommand());
        Logger.Debug("Command sent");
    }
}

GenerateEnumExtensions 属性生成器

[GenerateEnumExtensions] 属性为枚举类型生成便捷的扩展方法,提高代码可读性和类型安全性。

基础使用

using GFramework.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);
    }
}

配置选项

[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 方法

诊断信息

GFramework.SourceGenerators 提供详细的编译时诊断信息,帮助开发者快速定位和解决问题。

GF_Logging_001 - 日志字段名冲突

[Log(fieldName = "Logger")]
public partial class ClassWithLogger
{
    private readonly ILogger Logger; // ❌ 冲突!
}

错误信息: GF_Logging_001: Logger field name 'Logger' conflicts with existing field

解决方案: 更改字段名或移除冲突字段

[Log(fieldName = "CustomLogger")]
public partial class ClassWithLogger
{
    private readonly ILogger Logger; // ✅ 不冲突
    private static readonly ILogger CustomLogger; // ✅ 生成器使用 CustomLogger
}

GF_Rule_001 - ContextAware 接口冲突

[ContextAware]
public partial class AlreadyContextAware : IContextAware // ❌ 冲突!
{
    // 已实现 IContextAware
}

错误信息: GF_Rule_001: Type already implements IContextAware interface

解决方案: 移除 [ContextAware] 属性或移除手动实现

// 方案1移除属性
public partial class AlreadyContextAware : IContextAware
{
    // 手动实现
}

// 方案2移除手动实现使用生成器
[ContextAware]
public partial class AlreadyContextAware
{
    // 生成器自动实现
}

GF_Enum_001 - 枚举成员命名冲突

[GenerateEnumExtensions]
public enum ConflictEnum
{
    IsPlaying, // ❌ 冲突!会生成 IsIsPlaying()
    HasJump    // ❌ 冲突!会生成 HasHasJump()
}

错误信息: GF_Enum_001: Enum member name conflicts with generated method

解决方案: 重命名枚举成员或自定义前缀

[GenerateEnumExtensions(customPrefix = "IsState")]
public enum ConflictEnum
{
    Playing, // ✅ 生成 IsStatePlaying()
    Jump     // ✅ 生成 IsStateJump()
}

性能优势

编译时 vs 运行时对比

特性 手动实现 反射实现 源码生成器
运行时性能 最优 最差 最优
内存开销 最小 最大 最小
类型安全 编译时 运行时 编译时
开发效率
调试友好

基准测试结果

// 日志性能对比 (100,000 次调用)
// 手动实现:    0.23ms
// 反射实现:    45.67ms
// 源码生成器:  0.24ms (几乎无差异)

// 上下文访问性能对比 (1,000,000 次访问)
// 手动实现:    0.12ms
// 反射实现:    23.45ms
// 源码生成器:  0.13ms (几乎无差异)

内存分配分析

// 使用 source generators 的内存分配
[Log]
[ContextAware]
public partial class EfficientController : IController
{
    public void Process()
    {
        Logger.Info("Processing");      // 0 分配
        var model = Context.GetModel<PlayerModel>(); // 0 分配
    }
}

// 对比反射实现的内存分配
public class InefficientController : IController
{
    public void Process()
    {
        var logger = GetLoggerViaReflection();      // 每次分配
        var model = GetModelViaReflection<PlayerModel>(); // 每次分配
    }
}

使用示例

完整的游戏控制器示例

using GFramework.Core.Abstractions.controller;
using GFramework.SourceGenerators.Abstractions.logging;
using GFramework.SourceGenerators.Abstractions.rule;

[Log]
[ContextAware]
public partial class GameController : Node, IController
{
    private PlayerModel _playerModel;
    private CombatSystem _combatSystem;

    public override void _Ready()
    {
        // 初始化模型和系统引用
        _playerModel = Context.GetModel<PlayerModel>();
        _combatSystem = Context.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();
            Context.SendEvent(new AttackEvent());
        }
        else
        {
            Logger.Warning("Player cannot attack - cooldown");
        }
    }

    private void HandleDefend()
    {
        if (_playerModel.CanDefend())
        {
            Logger.Info("Player defends");
            _playerModel.IsDefending.Value = true;
            Context.SendEvent(new DefendEvent());
        }
        else
        {
            Logger.Warning("Player cannot defend");
        }
    }
}

枚举状态管理示例

[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.SourceGenerators.Abstractions.logging;
using GFramework.SourceGenerators.Abstractions.rule;

[Log]
[ContextAware]
public partial class CharacterController : Node, IController
{
    private CharacterModel _characterModel;

    public override void _Ready()
    {
        _characterModel = Context.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();
        Context.SendEvent(new CharacterDeathEvent());
    }
}

最佳实践

🎯 属性使用策略

1. 合理的属性组合

// 好的做法:相关功能组合使用
[Log]
[ContextAware]
public partial class BusinessLogicComponent : IComponent
{
    // 既有日志记录又有上下文访问
}

// 避免:不必要的属性
[Log] // ❌ 静态工具类通常不需要日志
public static class MathHelper
{
    public static int Add(int a, int b) => a + b;
}

2. 命名约定

// 好的做法:一致的命名
[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. 分离生成器和业务逻辑

// 好的做法:部分类分离
// 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. 枚举设计

// 好的做法:有意义的枚举设计
[GenerateEnumExtensions]
public enum GameState
{
    MainMenu,    // 主菜单
    Playing,     // 游戏中
    Paused,      // 暂停
    GameOver,    // 游戏结束
    Victory      // 胜利
}

// 避免:含义不明确的枚举值
[GenerateEnumExtensions]
public enum State
{
    State1,
    State2,
    State3
}

🔧 性能优化

1. 避免过度日志

// 好的做法:合理的日志级别
[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()}"); // 太频繁
    }
}

🛡️ 错误处理

[Log]
[ContextAware]
public partial class RobustComponent : IComponent
{
    public void RiskyOperation()
    {
        try
        {
            var model = Context.GetModel<RiskyModel>();
            model.PerformRiskyOperation();
            Logger.Info("Operation completed successfully");
        }
        catch (Exception ex)
        {
            Logger.Error($"Operation failed: {ex.Message}");
            Context.SendEvent(new OperationFailedEvent { Error = ex.Message });
        }
    }
}

常见问题

Q: 为什么需要标记类为 partial

A: 源代码生成器需要向现有类添加代码,partial 关键字允许一个类的定义分散在多个文件中。生成器会在编译时创建另一个部分类文件,包含生成的代码。

[Log]
public partial class MyClass { } // ✅ 需要 partial

[Log]
public class MyClass { } // ❌ 编译错误,无法添加生成代码

Q: 生成的代码在哪里?

A: 生成的代码在编译过程中创建,默认位置在 obj/Debug/net6.0/generated/ 目录下。可以在项目文件中配置输出位置:


<PropertyGroup>
    <CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
</PropertyGroup>

Q: 如何调试生成器问题?

A: 生成器提供了详细的诊断信息:

  1. 查看错误列表:编译错误会显示在 IDE 中
  2. 查看生成文件:检查生成的代码文件
  3. 启用详细日志:在项目文件中添加:

<PropertyGroup>
    <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
</PropertyGroup>

Q: 可以自定义生成器吗?

A: 当前版本的生成器支持有限的配置。如需完全自定义,可以创建自己的源代码生成器项目。

Q: 性能影响如何?

A: 源代码生成器对运行时性能的影响几乎为零:

  • 编译时:可能会增加编译时间(通常几秒)
  • 运行时:与手写代码性能相同
  • 内存使用:与手写代码内存使用相同

Q: 与依赖注入框架兼容吗?

A: 完全兼容。生成器创建的是标准代码,可以与任何依赖注入框架配合使用:

[Log]
[ContextAware]
public partial class ServiceComponent : IService
{
    // 可以通过构造函数注入依赖
    private readonly IDependency _dependency;
    
    public ServiceComponent(IDependency dependency)
    {
        _dependency = dependency;
        Logger.Info("Service initialized with dependency injection");
    }
}

依赖关系

graph TD
    A[GFramework.SourceGenerators] --> B[GFramework.SourceGenerators.Abstractions]
    A --> C[GFramework.SourceGenerators.Common]
    A --> D[GFramework.Core.Abstractions]
    A --> E[Microsoft.CodeAnalysis.CSharp]
    A --> F[Microsoft.CodeAnalysis.Analyzers]

版本兼容性

  • .NET: 6.0+
  • Visual Studio: 2022 17.0+
  • Rider: 2022.3+
  • Roslyn: 4.0+