- 新增 Priority 生成器文档,介绍自动实现 IPrioritized 接口的功能 - 新增 Context Get 注入生成器文档,说明自动注入架构组件的特性 - 更新索引页面,添加新生成器的导航链接和功能描述 - 补充 EnumGenerator 配置选项说明,列出已实现和未实现的选项 - 添加完整的诊断信息说明,涵盖新生成器的所有错误场景
17 KiB
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();
}
}