GFramework/docs/zh-CN/source-generators/context-get-generator.md
GeWuYou c9423702a2 docs(context-get): 更新上下文注入生成器文档
- 在多个示例中添加 __InjectContextBindings_Generated() 调用
- 重写推荐调用时机与模式章节,强调在生命周期入口统一调用
- 添加 Godot 节点和测试场景的具体使用模式
- 重构诊断信息表格,整合 Priority 和 Context Get 相关诊断
- 更新 FAQ 部分关于构造函数使用的建议
2026-03-29 20:23:45 +08:00

760 lines
19 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.

# 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();
}
}
```
如果构造函数执行时上下文尚未建立,过早注入会失败;即使在构造函数中调用了注入方法,也不要在调用之前访问这些字段。
## 高级场景
### 泛型类型支持
注入支持泛型类型字段:
```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)