# 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\ | | `[GetSystem]` | 字段 | 注入单个 System 实例 | 必须实现 ISystem | | `[GetSystems]` | 字段 | 注入 System 集合 | IReadOnlyList\ | | `[GetUtility]` | 字段 | 注入单个 Utility 实例 | 必须实现 IUtility | | `[GetUtilities]` | 字段 | 注入 Utility 集合 | IReadOnlyList\ | | `[GetService]` | 字段 | 注入单个服务实例 | 必须是引用类型 | | `[GetServices]` | 字段 | 注入服务集合 | IReadOnlyList\ 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 _playerModels = null!; [GetSystems] private IReadOnlyList _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 _strategies = null!; // Service 不会被自动推断(需要显式标记) [GetService] private IExternalService _externalService = null!; } ``` ## 生成代码解析 ### 注入方法生成 生成器会为每个注入字段生成一个私有方法: ```csharp // #nullable enable using GFramework.Core.Extensions; namespace YourNamespace; partial class PlayerController { private void __InjectContextBindings_Generated() { _playerModel = this.GetModel(); _combatSystem = this.GetSystem(); _pathFinder = this.GetUtility(); _audioService = this.GetService(); } } ``` ### 推荐调用时机与模式 生成器只负责生成 `__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()` | | ISystem 及其子类型 | 单实例注入 | `this.GetSystem()` | | IUtility 及其子类型 | 单实例注入 | `this.GetUtility()` | | IReadOnlyList\ | 集合注入 | `this.GetModels()` | | IReadOnlyList\ | 集合注入 | `this.GetSystems()` | | IReadOnlyList\ | 集合注入 | `this.GetUtilities()` | ### 不自动推断的类型 以下类型不会自动推断,需要显式标记特性: - **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 _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 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` 集合 - 排除 Godot.Node 类型 - 排除 Service 类型 ### Q: 集合注入可以是 List\ 吗? **A**: 不可以。集合注入只支持 `IReadOnlyList` 类型,这是为了保证注入集合的不可变性。 ```csharp [ContextAware] public partial class Controller { // ✅ 正确 [GetModels] private IReadOnlyList _players = null!; // ❌ 错误:不支持 [GetModels] private List _players = null!; } ``` ### Q: 如何在测试中模拟注入? **A**: 配合 `[ContextAware]` 的 `SetContextProvider` 方法,先建立测试上下文,再调用类内部已经封装好注入逻辑的公开入口: ```csharp [Test] public void TestController() { var testArchitecture = new TestArchitecture(); testArchitecture.RegisterModel(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)