# IoC 包使用说明 ## 概述 IoC(Inversion of Control,控制反转)包提供了一个轻量级的依赖注入容器,用于管理框架中各种组件的注册和获取。通过 IoC 容器,可以实现组件间的解耦,便于测试和维护。 IoC 容器是 GFramework 架构的核心组件之一,为整个框架提供依赖管理和组件解析服务。 ## 核心类 ### IocContainer IoC 容器类,负责管理对象的注册和获取。 **继承关系:** ```csharp public class IocContainer : ContextAwareBase, IIocContainer ``` **主要功能:** - 注册实例到容器 - 从容器中获取实例 - 类型安全的依赖管理 - 线程安全操作 - 容器冻结保护 - 多实例注册支持 ## 核心方法 ### Register`` 和 Register(Type, object) 注册一个实例到容器中。 **方法签名:** ```csharp public void Register(T instance) ``` **参数:** - `instance`: 要注册的实例对象 **特点:** - 支持泛型注册 - 自动注册到实例的所有接口类型 - 线程安全操作 - 容器冻结后抛出异常 **使用示例:** ```csharp var container = new IocContainer(); // 注册各种类型的实例 container.Register(new PlayerModel()); container.Register(new GameSystem()); container.Register(new StorageUtility()); ``` **注意:** 非泛型的 `Register(Type, object)` 方法是内部方法 `RegisterInternal`,不作为公开 API 使用。 ### RegisterSingleton`` 注册单例实例到容器中。一个类型只允许一个实例。 **方法签名:** ```csharp public void RegisterSingleton(T instance) ``` **参数:** - `instance`: 要注册的单例实例 **特点:** - 严格单例约束:同一类型只能注册一个实例 - 重复注册会抛出 `InvalidOperationException` - 适用于全局服务和配置对象 **使用示例:** ```csharp var container = new IocContainer(); // 注册单例 container.RegisterSingleton(new PlayerModel()); container.RegisterSingleton(new GameConfiguration()); // 以下会抛出异常(重复注册) // container.RegisterSingleton(new AnotherPlayerModel()); ``` ### RegisterPlurality 注册多个实例,将实例注册到其实现的所有接口和具体类型上。 **方法签名:** ```csharp public void RegisterPlurality(object instance) ``` **参数:** - `instance`: 要注册的实例 **特点:** - 自动注册到所有实现的接口 - 同时注册具体类型 - 支持多态注册 - 常用于 System 和 Utility 注册 **使用示例:** ```csharp var container = new IocContainer(); // 假设 GameSystem 实现了 IGameSystem 和 IUpdateable 接口 var gameSystem = new GameSystem(); container.RegisterPlurality(gameSystem); // 现在可以通过任一接口获取 var system1 = container.Get(); // 返回 gameSystem var system2 = container.Get(); // 返回 gameSystem var system3 = container.Get(); // 返回 gameSystem ``` ### RegisterSystem(Architecture 方法) **注意:** `RegisterSystem` 是 `Architecture` 类的方法,不是 `IocContainer` 的方法。它用于在架构中注册系统实例。 **方法签名(在 Architecture 中):** ```csharp public TSystem RegisterSystem(TSystem system) where TSystem : ISystem ``` **特点:** - 专门为 System 设计的注册方法 - 内部调用 `IocContainer.RegisterPlurality` - 自动处理 System 的生命周期 - 提供语义化的 API **使用示例:** ```csharp public class GameArchitecture : Architecture { protected override void Init() { // 注册系统 RegisterSystem(new CombatSystem()); RegisterSystem(new InventorySystem()); RegisterSystem(new QuestSystem()); } } ``` ### Get`` 和 GetAll`` 从容器中获取指定类型的实例。 **方法签名:** ```csharp public T? Get() where T : class public IReadOnlyList GetAll() where T : class ``` **返回值:** - `Get`: 返回指定类型的实例,如果未找到则返回 `null` - `GetAll`: 返回指定类型的所有实例列表,如果未找到则返回空数组 **特点:** - 线程安全读取 - 支持接口和具体类型查询 - 返回值快照(线程安全) **使用示例:** ```csharp // 获取已注册的实例 var playerModel = container.Get(); var gameSystems = container.GetAll(); // 如果类型未注册,Get 返回 null,GetAll 返回空数组 var unknownService = container.Get(); // null var allUnknownServices = container.GetAll(); // 空数组 ``` ### GetRequired`` 获取指定类型的必需实例,如果没有注册或注册了多个实例会抛出异常。 **方法签名:** ```csharp public T GetRequired() where T : class ``` **返回值:** - 返回找到的唯一实例 **特点:** - 严格模式获取 - 无实例时抛出 `InvalidOperationException` - 多实例时抛出 `InvalidOperationException` - 适用于必需的单例依赖 **使用示例:** ```csharp // 获取必需的服务 var config = container.GetRequired(); // 必须存在且唯一 // 以下情况会抛出异常: // 1. IGameConfiguration 未注册 // 2. IGameConfiguration 注册了多个实例 ``` ### GetAllSorted`` 获取并排序(系统调度专用)。 **方法签名:** ```csharp public IReadOnlyList GetAllSorted(Comparison comparison) where T : class ``` **参数:** - `comparison`: 比较器委托,定义排序规则 **返回值:** - 按指定方式排序后的实例列表 **特点:** - 支持自定义排序 - 适用于需要按优先级执行的场景 - 常用于 System 更新顺序控制 **使用示例:** ```csharp // 按优先级排序系统 var sortedSystems = container.GetAllSorted((a, b) => { var priorityA = GetSystemPriority(a); var priorityB = GetSystemPriority(b); return priorityA.CompareTo(priorityB); }); ``` ## IoC 容器架构 ``` Architecture (架构层) ├── IocContainer (IoC容器) │ ├── Register() // 泛型注册 │ ├── Register(Type, obj) // 非泛型注册 │ ├── RegisterSingleton() // 单例注册 │ ├── RegisterPlurality() // 多态注册 │ ├── RegisterSystem() // 系统注册 │ ├── Get() // 获取实例 │ ├── GetAll() // 获取所有实例 │ ├── GetRequired() // 获取必需实例 │ ├── GetAllSorted() // 排序获取 │ ├── Contains() // 检查存在性 │ ├── ContainsInstance() // 检查实例 │ ├── Clear() // 清空容器 │ └── Freeze() // 冻结容器 │ ├── Components (组件) │ ├── Model (数据模型) │ ├── System (业务系统) │ └── Utility (工具类) │ └── Context (上下文) └── 提供容器访问 ``` ## 在框架中的使用 ### Architecture 中的应用 IoC 容器是 [`Architecture`](./architecture.md) 类的核心组件,用于管理所有的 System、Model 和 Utility。 ```csharp public abstract class Architecture : IArchitecture { // 内置 IoC 容器 private readonly IocContainer _mContainer = new(); // 注册系统 public TSystem RegisterSystem(TSystem system) where TSystem : ISystem { system.SetContext(Context); _mContainer.RegisterPlurality(system); // 注册到容器 RegisterLifecycleComponent(system); // 处理生命周期 return system; } // 获取系统 public TSystem GetSystem() where TSystem : class, ISystem => _mContainer.Get(); // 从容器获取 // Model 和 Utility 同理 public TModel RegisterModel(TModel model) where TModel : IModel { model.SetContext(Context); _mContainer.RegisterPlurality(model); RegisterLifecycleComponent(model); return model; } public TModel GetModel() where TModel : class, IModel => _mContainer.Get(); } ``` ### 注册组件到容器 ``` public class GameArchitecture : Architecture { protected override void Init() { // 这些方法内部都使用 IoC 容器 // 注册 Model(存储游戏数据) RegisterModel(new PlayerModel()); RegisterModel(new InventoryModel()); // 注册 System(业务逻辑) RegisterSystem(new GameplaySystem()); RegisterSystem(new SaveSystem()); // 注册 Utility(工具类) RegisterUtility(new TimeUtility()); RegisterUtility(new StorageUtility()); } } ``` ### 从容器获取组件 ``` // 通过扩展方法间接使用 IoC 容器 public class PlayerController : IController { public void Start() { // GetModel 内部调用 Architecture.GetModel // Architecture.GetModel 内部调用 IocContainer.Get var playerModel = this.GetModel(); var gameplaySystem = this.GetSystem(); var timeUtility = this.GetUtility(); } } ``` ## 工作原理 ### 内部实现 ``` public class IocContainer { // 使用字典存储类型到实例集合的映射 private readonly Dictionary> _typeIndex = new(); private readonly HashSet _objects = []; private readonly ReaderWriterLockSlim _lock = new(LockRecursionPolicy.NoRecursion); private volatile bool _frozen = false; public void Register(T instance) { // 获取写锁以确保线程安全 _lock.EnterWriteLock(); try { RegisterInternal(typeof(T), instance!); } finally { _lock.ExitWriteLock(); } } public T? Get() where T : class { _lock.EnterReadLock(); try { if (_typeIndex.TryGetValue(typeof(T), out var set) && set.Count > 0) { var result = set.First() as T; return result; } return null; } finally { _lock.ExitReadLock(); } } private void RegisterInternal(Type type, object instance) { if (_frozen) throw new InvalidOperationException("IocContainer is frozen"); _objects.Add(instance); if (!_typeIndex.TryGetValue(type, out var set)) { set = []; _typeIndex[type] = set; } set.Add(instance); } } ``` ### 线程安全机制 容器使用 [ReaderWriterLockSlim](xref:System.Threading.ReaderWriterLockSlim) 来确保线程安全操作,允许多个线程同时读取,但在写入时阻止其他线程访问。 ### 注册流程 ``` 用户代码 ↓ RegisterSystem(system) ↓ IocContainer.Register(system) ↓ 加写锁 -> Dictionary[typeof(T)] 添加实例到HashSet ``` ### 获取流程 ``` 用户代码 ↓ this.GetSystem() ↓ Architecture.GetSystem() ↓ IocContainer.Get() ↓ 加读锁 -> Dictionary.TryGetValue(typeof(T)) 获取HashSet ↓ 返回HashSet中第一个实例或 null ``` ## 使用示例 ### 基础使用 ``` // 1. 创建容器 var container = new IocContainer(); // 2. 注册服务 var playerService = new PlayerService(); container.Register(playerService); // 3. 获取服务 var service = container.Get(); service.DoSomething(); ``` ### 接口和实现分离 ``` // 定义接口 public interface IDataService { void SaveData(string data); string LoadData(); } // 实现类 public class LocalDataService : IDataService { public void SaveData(string data) { /* 本地存储 */ } public string LoadData() { /* 本地加载 */ return ""; } } public class CloudDataService : IDataService { public void SaveData(string data) { /* 云端存储 */ } public string LoadData() { /* 云端加载 */ return ""; } } // 注册(可以根据配置选择不同实现) var container = new IocContainer(); #if CLOUD_SAVE container.Register(new CloudDataService()); #else container.Register(new LocalDataService()); #endif // 使用(不需要关心具体实现) var dataService = container.Get(); dataService.SaveData("game data"); ``` ### 注册多个实现 ``` var container = new IocContainer(); // 注册多个相同接口的不同实现 container.Register(new LocalDataService()); container.Register(new CloudDataService()); // 获取单个实例(返回第一个) var singleService = container.Get(); // 返回第一个注册的实例 // 获取所有实例 var allServices = container.GetAll(); // 返回两个实例的列表 ``` ## 其他实用方法 ### Contains``() 检查容器中是否包含指定类型的实例。 ``` public bool Contains() where T : class ``` **参数:** - 无泛型参数 **返回值:** - 如果容器中包含指定类型的实例则返回 `true`,否则返回 `false` **使用示例:** ``` var container = new IocContainer(); // 检查服务是否已注册 if (container.Contains()) { Console.WriteLine("Player service is available"); } // 根据检查结果决定是否注册 if (!container.Contains()) { container.Register(new SettingsService()); } ``` **应用场景:** - 条件注册服务 - 检查依赖是否可用 - 动态功能开关 ### `ContainsInstance(object instance)` 判断容器中是否包含某个具体的实例对象。 ``` public bool ContainsInstance(object instance) ``` **参数:** - `instance`:待查询的实例对象 **返回值:** - 若容器中包含该实例则返回 `true`,否则返回 `false` **使用示例:** ``` var container = new IocContainer(); var service = new MyService(); container.Register(service); // 检查特定实例是否在容器中 if (container.ContainsInstance(service)) { Console.WriteLine("This instance is registered in the container"); } // 检查另一个实例 var anotherService = new MyService(); if (!container.ContainsInstance(anotherService)) { Console.WriteLine("This instance is not in the container"); } ``` **应用场景:** - 避免重复注册同一实例 - 检查对象是否已被管理 - 调试和日志记录 ### `Clear()` 清空容器中的所有实例。 ``` public void Clear() ``` **使用示例:** ``` var container = new IocContainer(); // 注册多个服务 container.Register(new Service1()); container.Register(new Service2()); container.Register(new Service3()); // 清空容器 container.Clear(); // 检查是否清空成功 Console.WriteLine($"Contains IService1: {container.Contains()}"); // False Console.WriteLine($"Contains IService2: {container.Contains()}"); // False ``` **应用场景:** - 重置容器状态 - 内存清理 - 测试环境准备 **注意事项:** - 容器冻结后也可以调用 `Clear()` 方法 - 清空后,所有已注册的实例都将丢失 - 不会自动清理已注册对象的其他引用 ## 设计特点 ### 1. 简单轻量 - 支持多种注册方式:普通注册、单例注册、多实例注册 - 基于字典和哈希集实现,性能高效 - 无复杂的依赖解析逻辑 ### 2. 手动注册 - 需要显式注册每个组件 - 不支持自动依赖注入 - 完全可控的组件生命周期 ### 3. 多实例支持 - 每个类型可以注册多个实例 - 提供 `GetAll` 方法获取所有实例 - 提供 `Get` 方法获取单个实例 ### 4. 类型安全 - 基于泛型,编译时类型检查 - 避免字符串键导致的错误 - IDE 友好,支持自动补全 ### 5. 线程安全 - 使用读写锁确保多线程环境下的安全操作 - 读操作可以并发执行 - 写操作独占锁,防止并发修改冲突 ### 6. 容器冻结 - 提供 `Freeze` 方法,防止进一步修改容器内容 - 防止在初始化后意外修改注册内容 ## 与其他 IoC 容器的区别 ### 本框架的 IocContainer ``` // 简单直接 var container = new IocContainer(); container.Register(new MyService()); var service = container.Get(); ``` **特点:** - ✅ 简单易用 - ✅ 性能高 - ✅ 线程安全 - ✅ 支持多实例 - ❌ 不支持构造函数注入 - ❌ 不支持自动解析依赖 - ❌ 不支持生命周期管理(Transient/Scoped/Singleton) ### 完整的 IoC 框架(如 Autofac、Zenject) ``` // 复杂但功能强大 var builder = new ContainerBuilder(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().WithParameter("config", config); var container = builder.Build(); // 自动解析依赖 var controller = container.Resolve(); ``` **特点:** - ✅ 自动依赖注入 - ✅ 生命周期管理 - ✅ 复杂场景支持 - ❌ 学习成本高 - ❌ 性能开销大 ## 最佳实践 ### 1. 在架构初始化时注册 ``` public class GameArchitecture : Architecture { protected override void Init() { // 按顺序注册组件 // 1. 工具类(无依赖) RegisterUtility(new TimeUtility()); RegisterUtility(new StorageUtility()); // 2. 模型(可能依赖工具) RegisterModel(new PlayerModel()); RegisterModel(new GameModel()); // 3. 系统(可能依赖模型和工具) RegisterSystem(new GameplaySystem()); RegisterSystem(new SaveSystem()); } } ``` ### 2. 使用接口类型注册 ``` // ❌ 不推荐:直接使用实现类 RegisterSystem(new ConcreteSystem()); var system = GetSystem(); // ✅ 推荐:使用接口 RegisterSystem(new ConcreteSystem()); var system = GetSystem(); ``` ### 3. 避免运行时频繁注册 ``` // ❌ 不好:游戏运行时频繁注册 void Update() { RegisterService(new TempService()); // 每帧创建 } // ✅ 好:在初始化时一次性注册 protected override void Init() { RegisterService(new PersistentService()); } ``` ### 4. 检查 null 返回值 ``` // 获取可能不存在的服务 var service = container.Get(); if (service != null) { service.DoSomething(); } else { GD.Print("Service not registered!"); } ``` ### 5. 合理使用容器冻结 ``` // 在架构初始化完成后冻结容器,防止意外修改 protected override void OnInit() { // 注册所有组件 RegisterModel(new PlayerModel()); RegisterSystem(new GameSystem()); // ... // 冻结容器 Container.Freeze(); // 此后无法再注册新组件 } ``` ## 注意事项 1. **线程安全操作**:容器内部使用读写锁确保线程安全,无需额外同步 2. **容器冻结**:一旦调用 `Freeze` 方法,将不能再注册新实例 3. **单例注册限制 **:`RegisterSingleton` 方法确保一个类型只能有一个实例,重复注册会抛出异常 4. **内存管理**:容器持有的实例不会自动释放,需要注意内存泄漏问题 5. **注册顺序**:组件的依赖关系需要手动保证,先注册被依赖的组件 ## 相关包 - [`architecture`](./architecture.md) - 使用 IoC 容器管理所有组件 - [`model`](./model.md) - Model 通过 IoC 容器注册和获取 - [`system`](./system.md) - System 通过 IoC 容器注册和获取 - [`utility`](./utility.md) - Utility 通过 IoC 容器注册和获取