GFramework/docs/zh-CN/source-generators/context-get-generator.md
GeWuYou 010ab70b7f docs(generators): 添加 Priority 和 Context Get 生成器文档
- 新增 Priority 生成器文档,介绍自动实现 IPrioritized 接口的功能
- 新增 Context Get 注入生成器文档,说明自动注入架构组件的特性
- 更新索引页面,添加新生成器的导航链接和功能描述
- 补充 EnumGenerator 配置选项说明,列出已实现和未实现的选项
- 添加完整的诊断信息说明,涵盖新生成器的所有错误场景
2026-03-29 19:54:26 +08:00

17 KiB
Raw Blame History

Context Get 注入生成器

自动注入架构组件,消除样板代码

Context Get 注入生成器提供自动注入架构组件的能力,通过源代码生成器在编译时自动生成依赖注入代码,无需手动调用 GetModel/GetSystem 等方法。

概述

核心功能

  • 自动注入:自动注入 Model、System、Utility 和 Service 实例
  • 集合注入:支持注入多个同类型组件的集合
  • 智能推断[GetAll] 特性可自动识别字段类型并注入
  • 零运行时开销:编译时生成代码,无反射或动态调用

与 ContextAware 的关系

Context Get 注入依赖 [ContextAware] 特性提供的上下文访问能力。使用注入特性前,类必须:

  • 标记 [ContextAware] 特性,或
  • 实现 IContextAware 接口,或
  • 继承 ContextAwareBase

注入特性

支持的9个特性

特性 应用目标 功能描述 类型约束
[GetModel] 字段 注入单个 Model 实例 必须实现 IModel
[GetModels] 字段 注入 Model 集合 IReadOnlyList<IModel>
[GetSystem] 字段 注入单个 System 实例 必须实现 ISystem
[GetSystems] 字段 注入 System 集合 IReadOnlyList<ISystem>
[GetUtility] 字段 注入单个 Utility 实例 必须实现 IUtility
[GetUtilities] 字段 注入 Utility 集合 IReadOnlyList<IUtility>
[GetService] 字段 注入单个服务实例 必须是引用类型
[GetServices] 字段 注入服务集合 IReadOnlyList<T> where T : class
[GetAll] 自动推断并注入所有符合类型的字段 智能推断

基础使用

单实例注入

使用 [GetModel][GetSystem][GetUtility][GetService] 注入单个实例:

using GFramework.SourceGenerators.Abstractions.Rule;
using GFramework.Core.Abstractions.Model;
using GFramework.Core.Abstractions.Systems;
using GFramework.Core.Abstractions.Utility;

[ContextAware]
public partial class PlayerController
{
    [GetModel]
    private IPlayerModel _playerModel = null!;

    [GetSystem]
    private ICombatSystem _combatSystem = null!;

    [GetUtility]
    private IPathFinder _pathFinder = null!;

    [GetService]
    private IAudioService _audioService = null!;

    public void Initialize()
    {
        // 字段已自动注入,可直接使用
        _combatSystem.Attack(_playerModel);
    }
}

集合注入

使用 [GetModels][GetSystems][GetUtilities][GetServices] 注入多个实例:

using GFramework.SourceGenerators.Abstractions.Rule;

[ContextAware]
public partial class StrategyManager
{
    [GetModels]
    private IReadOnlyList<IPlayerModel> _playerModels = null!;

    [GetSystems]
    private IReadOnlyList<IGameSystem> _gameSystems = null!;

    public void ProcessAll()
    {
        foreach (var system in _gameSystems)
        {
            system.Initialize();
        }
    }
}

智能推断注入 [GetAll]

[GetAll] 特性标记在类上,自动推断所有符合类型的字段并注入:

using GFramework.SourceGenerators.Abstractions.Rule;

[ContextAware]
[GetAll]
public partial class GameManager
{
    // 自动推断为 Model 注入
    private IPlayerModel _playerModel = null!;

    // 自动推断为 System 注入
    private ICombatSystem _combatSystem = null!;

    // 自动推断为 Utility 注入
    private IPathFinder _pathFinder = null!;

    // 自动推断为集合注入
    private IReadOnlyList<IStrategy> _strategies = null!;

    // Service 不会被自动推断(需要显式标记)
    [GetService]
    private IExternalService _externalService = null!;
}

生成代码解析

注入方法生成

生成器会为每个注入字段生成一个私有方法:

// <auto-generated />
#nullable enable

using GFramework.Core.Extensions;

namespace YourNamespace;

partial class PlayerController
{
    private void __InjectContextBindings_Generated()
    {
        _playerModel = this.GetModel<global::IPlayerModel>();
        _combatSystem = this.GetSystem<global::ICombatSystem>();
        _pathFinder = this.GetUtility<global::IPathFinder>();
        _audioService = this.GetService<global::IAudioService>();
    }
}

调用时机

注入方法需要在适当的时机手动调用:

[ContextAware]
[GetAll]
public partial class GameController
{
    private IPlayerModel _player = null!;
    private ICombatSystem _combat = null!;

    public GameController()
    {
        // 在构造函数后调用注入方法
        __InjectContextBindings_Generated();
    }

    public void Initialize()
    {
        // 此时字段已注入,可以安全使用
        _combat.Initialize(_player);
    }
}

智能推断规则

自动推断的类型

[GetAll] 会自动识别并注入以下类型:

字段类型 注入方式 生成代码示例
IModel 及其子类型 单实例注入 this.GetModel<T>()
ISystem 及其子类型 单实例注入 this.GetSystem<T>()
IUtility 及其子类型 单实例注入 this.GetUtility<T>()
IReadOnlyList<IModel> 集合注入 this.GetModels<T>()
IReadOnlyList<ISystem> 集合注入 this.GetSystems<T>()
IReadOnlyList<IUtility> 集合注入 this.GetUtilities<T>()

不自动推断的类型

以下类型不会自动推断,需要显式标记特性:

  • Service 类型:任意引用类型太宽泛,需要使用 [GetService][GetServices] 显式标记
  • Godot.Node 及其子类:避免与 Godot 引擎的节点注入冲突
[ContextAware]
[GetAll]
public partial class GameNode : Node
{
    // ✅ 自动注入
    private IPlayerModel _model = null!;

    // ✅ 自动注入
    private IGameSystem _system = null!;

    // ❌ 不会自动注入Service 需要显式标记)
    [GetService]
    private IAudioService _audio = null!;

    // ❌ 不会自动注入Godot.Node 被排除)
    private Node _otherNode = null!;
}

使用场景

控制器依赖注入

using GFramework.Core.Abstractions.Controller;
using GFramework.SourceGenerators.Abstractions.Rule;

[ContextAware]
[GetAll]
public partial class GameController : IController
{
    private IPlayerModel _player = null!;
    private IEnemyModel _enemy = null!;
    private ICombatSystem _combat = null!;
    private IAudioSystem _audio = null!;

    public void Initialize()
    {
        _combat.Initialize(_player, _enemy);
    }

    public void Attack()
    {
        _combat.Attack(_player, _enemy);
        _audio.PlayAttackSound();
    }
}

策略模式实现

[ContextAware]
public partial class StrategyManager
{
    [GetServices]
    private IReadOnlyList<IStrategy> _strategies = null!;

    public IStrategy SelectBestStrategy()
    {
        return _strategies.FirstOrDefault(s => s.CanExecute())
            ?? throw new InvalidOperationException("No strategy available");
    }
}

Godot 节点集成

using Godot;
using GFramework.SourceGenerators.Abstractions.Rule;

[ContextAware]
[GetAll]
public partial class PlayerNode : Node
{
    private IPlayerModel _model = null!;
    private IMovementSystem _movement = null!;

    public override void _Ready()
    {
        __InjectContextBindings_Generated();
        _movement.Initialize(_model);
    }
}

诊断信息

GF_ContextGet_001 - 不支持嵌套类

错误信息Class '{ClassName}' cannot use context Get injection inside a nested type

场景:在嵌套类中使用注入特性

[ContextAware]
public partial class OuterClass
{
    [GetModel]  // ❌ 错误
    private IModel _model = null!;

    public partial class InnerClass  // 嵌套类
    {
        [GetModel]
        private IModel _innerModel = null!;
    }
}

解决方案:避免在嵌套类中使用注入特性,将其提取为独立的类

GF_ContextGet_002 - 不支持静态字段

错误信息Field '{FieldName}' cannot be static when using generated context Get injection

场景:标记静态字段进行注入

[ContextAware]
public partial class GameController
{
    [GetModel]
    private static IPlayerModel _playerModel = null!;  // ❌ 错误
}

解决方案:改为实例字段

[ContextAware]
public partial class GameController
{
    [GetModel]
    private IPlayerModel _playerModel = null!;  // ✅ 正确
}

GF_ContextGet_003 - 不支持只读字段

错误信息Field '{FieldName}' cannot be readonly when using generated context Get injection

场景:标记只读字段进行注入

[ContextAware]
public partial class GameController
{
    [GetModel]
    private readonly IPlayerModel _playerModel = null!;  // ❌ 错误
}

解决方案:移除 readonly 关键字

[ContextAware]
public partial class GameController
{
    [GetModel]
    private IPlayerModel _playerModel = null!;  // ✅ 正确
}

GF_ContextGet_004 - 字段类型不匹配

错误信息Field '{FieldName}' type '{FieldType}' is not valid for [{AttributeName}]

场景:字段类型与注入特性要求的类型不匹配

[ContextAware]
public partial class GameController
{
    [GetModel]
    private ISystem _system = null!;  // ❌ 错误ISystem 不实现 IModel
}

解决方案:确保字段类型符合特性要求

[ContextAware]
public partial class GameController
{
    [GetModel]
    private IPlayerModel _model = null!;  // ✅ 正确

    [GetSystem]
    private ISystem _system = null!;  // ✅ 正确
}

GF_ContextGet_005 - 必须是上下文感知类型

错误信息Class '{ClassName}' must be context-aware to use generated context Get injection

场景:类未标记 [ContextAware] 或未实现 IContextAware

public partial class GameController  // ❌ 缺少 [ContextAware]
{
    [GetModel]
    private IPlayerModel _model = null!;
}

解决方案:添加 [ContextAware] 特性或实现 IContextAware 接口

[ContextAware]  // ✅ 正确
public partial class GameController
{
    [GetModel]
    private IPlayerModel _model = null!;
}

GF_ContextGet_006 - 不支持多个注入特性

错误信息Field '{FieldName}' cannot declare multiple generated context Get attributes

场景:同一字段标记了多个注入特性

[ContextAware]
public partial class GameController
{
    [GetModel]
    [GetSystem]  // ❌ 错误:多个特性冲突
    private IPlayerModel _model = null!;
}

解决方案:每个字段只标记一个注入特性

[ContextAware]
public partial class GameController
{
    [GetModel]
    private IPlayerModel _model = null!;  // ✅ 正确

    [GetSystem]
    private ISystem _system = null!;  // ✅ 正确
}

最佳实践

1. 优先使用 [GetAll]

使用 [GetAll] 可以减少特性标记,保持代码简洁:

// ✅ 推荐:简洁
[ContextAware]
[GetAll]
public partial class Controller
{
    private ICoreModel _core = null!;
    private ICoreSystem _system = null!;
    private ICoreUtility _utility = null!;
}

// ❌ 不推荐:冗余
[ContextAware]
public partial class Controller
{
    [GetModel]
    private ICoreModel _core = null!;

    [GetSystem]
    private ICoreSystem _system = null!;

    [GetUtility]
    private ICoreUtility _utility = null!;
}

2. 为字段提供默认值

使用 = null! 或其他默认值避免编译警告:

[ContextAware]
[GetAll]
public partial class Controller
{
    // ✅ 推荐:明确提供默认值
    private IPlayerModel _player = null!;

    // ❌ 不推荐:会产生编译警告
    private IPlayerModel _player;
}

3. 避免过度注入

只注入真正需要的依赖,保持类的职责单一:

// ✅ 推荐:只注入必需的依赖
[ContextAware]
[GetAll]
public partial class CombatController
{
    private ICombatModel _combat = null!;
    private ICombatSystem _system = null!;
}

// ❌ 不推荐:注入过多依赖
[ContextAware]
[GetAll]
public partial class MegaController
{
    private IModel1 _m1 = null!;
    private IModel2 _m2 = null!;
    private IModel3 _m3 = null!;
    // ... 10+ 个依赖,职责不清
}

4. 显式标记 Service 注入

Service 类型不会自动推断,需要显式标记以保持意图清晰:

[ContextAware]
[GetAll]
public partial class ServiceManager
{
    // 自动推断
    private IPlayerModel _model = null!;
    private IGameSystem _system = null!;

    // Service 必须显式标记
    [GetService]
    private IExternalService _external = null!;
}

5. 在构造函数或初始化方法中调用注入

确保在使用字段前调用注入方法:

[ContextAware]
[GetAll]
public partial class GameController
{
    private IPlayerModel _player = null!;

    public GameController()
    {
        // ✅ 在构造函数中调用
        __InjectContextBindings_Generated();
    }

    public void Initialize()
    {
        // ✅ 此时字段已注入
        _player.Reset();
    }
}

高级场景

泛型类型支持

注入支持泛型类型字段:

[ContextAware]
public partial class GenericController<TModel> where TModel : IModel
{
    [GetModel]
    private TModel _model = null!;
}

接口与实现类型

字段类型应使用接口而非具体实现:

[ContextAware]
[GetAll]
public partial class Controller
{
    // ✅ 推荐:使用接口类型
    private IPlayerModel _player = null!;

    // ❌ 不推荐:使用具体实现类型
    private PlayerModel _player = null!;
}

多架构场景

在多架构场景中,可以通过 SetContextProvider 切换架构:

[ContextAware]
[GetAll]
public partial class GameController
{
    private IPlayerModel _player = null!;

    public static void SetArchitecture(IArchitecture architecture)
    {
        // 切换架构提供者
        SetContextProvider(new CustomContextProvider(architecture));
    }
}

常见问题

Q: 为什么 Service 不会自动推断?

A: Service 可以是任意引用类型,自动推断可能导致误注入。显式标记确保意图清晰,避免意外行为。

Q: 可以在构造函数中使用注入的字段吗?

A: 不可以。注入方法需要在构造函数中调用,字段在调用后才被注入。推荐在单独的初始化方法中使用。

[ContextAware]
[GetAll]
public partial class Controller
{
    private IPlayerModel _player = null!;

    public Controller()
    {
        __InjectContextBindings_Generated();
        // ❌ 不推荐:在这里使用 _player
    }

    public void Initialize()
    {
        // ✅ 推荐:在初始化方法中使用
        _player.Reset();
    }
}

Q: GetAll 会注入所有字段吗?

A: 不会。[GetAll] 只注入符合类型约束的字段:

  • 实现 IModel、ISystem、IUtility 接口的类型
  • 上述类型的 IReadOnlyList<T> 集合
  • 排除 Godot.Node 类型
  • 排除 Service 类型

Q: 集合注入可以是 List<T> 吗?

A: 不可以。集合注入只支持 IReadOnlyList<T> 类型,这是为了保证注入集合的不可变性。

[ContextAware]
public partial class Controller
{
    // ✅ 正确
    [GetModels]
    private IReadOnlyList<IPlayerModel> _players = null!;

    // ❌ 错误:不支持
    [GetModels]
    private List<IPlayerModel> _players = null!;
}

Q: 如何在测试中模拟注入?

A: 配合 [ContextAware]SetContextProvider 方法,使用测试架构:

[Test]
public void TestController()
{
    var testArchitecture = new TestArchitecture();
    testArchitecture.RegisterModel<IPlayerModel>(new MockPlayerModel());

    GameController.SetContextProvider(new TestContextProvider(testArchitecture));

    try
    {
        var controller = new GameController();
        controller.__InjectContextBindings_Generated();
        controller.Initialize();
        // 测试逻辑...
    }
    finally
    {
        GameController.ResetContextProvider();
    }
}

相关文档