mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-30 01:06:43 +08:00
- 在多个示例中添加 __InjectContextBindings_Generated() 调用 - 重写推荐调用时机与模式章节,强调在生命周期入口统一调用 - 添加 Godot 节点和测试场景的具体使用模式 - 重构诊断信息表格,整合 Priority 和 Context Get 相关诊断 - 更新 FAQ 部分关于构造函数使用的建议
760 lines
19 KiB
Markdown
760 lines
19 KiB
Markdown
# 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)
|