# 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]` 注入单个实例：

```csharp
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()
    {
        // 在首次使用注入字段前，先完成绑定
        __InjectContextBindings_Generated();

        _combatSystem.Attack(_playerModel);
    }
}
```

### 集合注入

使用 `[GetModels]`、`[GetSystems]`、`[GetUtilities]` 和 `[GetServices]` 注入多个实例：

```csharp
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()
    {
        __InjectContextBindings_Generated();

        foreach (var system in _gameSystems)
        {
            system.Initialize();
        }
    }
}
```

### 智能推断注入 `[GetAll]`

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

```csharp
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!;
}
```

## 生成代码解析

### 注入方法生成

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

```csharp
// <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>();
    }
}
```

### 推荐调用时机与模式

生成器只负责生成 `__InjectContextBindings_Generated()` 方法，不会自动织入构造函数或生命周期回调。推荐统一遵循下面两条规则：

- 在上下文已经可用之后调用，例如 `Initialize()`、`OnEnter()`、Godot 的 `_Ready()` 或测试初始化阶段。
- 在首次读取任一注入字段之前调用。不要在构造函数体中假设这些字段已经可用。

推荐把调用集中放在单一的生命周期入口，而不是分散到构造函数、`_Ready()`、`Initialize()`
等多个位置。这样更容易保证字段只在“上下文已准备好且尚未被读取”时完成注入。
由于该方法是生成的私有成员，类外代码（包括测试）应复用类内部公开的生命周期入口，而不是直接调用生成方法。

#### 普通类型推荐模式

对普通控制器、服务协调器或策略管理器，优先在显式初始化方法开头调用：

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

    public void Initialize()
    {
        __InjectContextBindings_Generated();
        _combat.Initialize(_player);
    }
}
```

#### Godot 节点推荐模式

对 `Node` 类型，优先在 `_Ready()` 开头调用，再使用注入字段：

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

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

#### 测试与手动装配模式

先配置上下文提供者，再调用与生产代码一致的公开入口，让入口内部完成注入：

```csharp
[Test]
public void TestController()
{
    var testArchitecture = new TestArchitecture();
    GameController.SetContextProvider(new TestContextProvider(testArchitecture));

    try
    {
        var controller = new GameController();
        controller.Initialize();
    }
    finally
    {
        GameController.ResetContextProvider();
    }
}
```

## 智能推断规则

### 自动推断的类型

`[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 引擎的节点注入冲突

```csharp
[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!;
}
```

## 使用场景

### 控制器依赖注入

```csharp
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()
    {
        __InjectContextBindings_Generated();
        _combat.Initialize(_player, _enemy);
    }

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

### 策略模式实现

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

    public IStrategy SelectBestStrategy()
    {
        __InjectContextBindings_Generated();

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

### Godot 节点集成

```csharp
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`

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

```csharp
[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`

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

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

**解决方案**：改为实例字段

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

### GF_ContextGet_003 - 不支持只读字段

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

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

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

**解决方案**：移除 `readonly` 关键字

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

### GF_ContextGet_004 - 字段类型不匹配

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

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

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

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

```csharp
[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`

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

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

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

### GF_ContextGet_006 - 不支持多个注入特性

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

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

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

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

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

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

## 最佳实践

### 1. 优先使用 [GetAll]

使用 `[GetAll]` 可以减少特性标记，保持代码简洁：

```csharp
// ✅ 推荐：简洁
[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!` 或其他默认值避免编译警告：

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

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

### 3. 避免过度注入

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

```csharp
// ✅ 推荐：只注入必需的依赖
[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 类型不会自动推断，需要显式标记以保持意图清晰：

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

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

### 5. 在上下文就绪后的生命周期入口中调用注入

不要把调用点分散到多个位置。优先选择一个明确的入口，例如 `Initialize()`、`Activate()` 或 `_Ready()`，并在方法开头完成注入：

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

    public void Initialize()
    {
        __InjectContextBindings_Generated();
        _player.Reset();
    }
}
```

如果构造函数执行时上下文尚未建立，过早注入会失败；即使在构造函数中调用了注入方法，也不要在调用之前访问这些字段。

## 注册可见性分析

除了生成注入方法，`GFramework.SourceGenerators` 现在还会分析 `Model`、`System`、`Utility` 的使用点是否存在静态可见注册。

当前支持的使用点：

- 字段特性注入：`[GetModel]`、`[GetModels]`、`[GetSystem]`、`[GetSystems]`、`[GetUtility]`、`[GetUtilities]`
- 手写调用：`this.GetModel<T>()`、`this.GetSystem<T>()`、`this.GetUtility<T>()`

当前支持的注册来源：

- `Architecture.OnInitialize()` 中的直接 `RegisterModel/RegisterSystem/RegisterUtility`
- `InstallModules()` 中的直接注册
- `InstallModule(new SomeModule())` 和 `InstallGodotModule(new SomeModule())` 可静态展开到 `module.Install(...)` 的直接注册

分析器会在能够唯一定位所属架构时，对缺失注册报告 `Warning`：

- `GF_ContextRegistration_001`：`Model` 使用点未找到静态可见注册
- `GF_ContextRegistration_002`：`System` 使用点未找到静态可见注册
- `GF_ContextRegistration_003`：`Utility` 使用点未找到静态可见注册

这个分析器的目标是提供稳定提示，而不是完整模拟运行时 DI 图。以下场景默认不做强行推断：

- 运行时条件分支控制的注册
- 反射、配置驱动或外部程序集动态注册
- 无法唯一判定组件归属架构的多架构场景

## 高级场景

### 泛型类型支持

注入支持泛型类型字段：

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

### 接口与实现类型

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

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

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

### 多架构场景

在多架构场景中，可以通过 `SetContextProvider` 切换架构：

```csharp
[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**: 一般不推荐。构造函数阶段通常不是最清晰的生命周期入口，而且在调用注入方法之前字段一定还不可用。推荐做法是在
`Initialize()`、`_Ready()` 等上下文已就绪的方法开头调用 `__InjectContextBindings_Generated()`，随后再使用这些字段。

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

    public void Initialize()
    {
        __InjectContextBindings_Generated();
        _player.Reset();
    }
}
```

### Q: GetAll 会注入所有字段吗？

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

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

### Q: 集合注入可以是 List\<T\> 吗？

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

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

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

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

**A**: 配合 `[ContextAware]` 的 `SetContextProvider` 方法，先建立测试上下文，再调用类内部已经封装好注入逻辑的公开入口：

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

    GameController.SetContextProvider(new TestContextProvider(testArchitecture));

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

## 相关文档

- [Source Generators 概述](./index.md)
- [ContextAware 生成器](./context-aware-generator.md)
- [架构组件](../core/architecture.md)
- [依赖注入](../core/ioc.md)
