Unity C#泛型MonoPool<T>无法转换为MonoPool<MonoBehaviour>报错
报错原因
这个错误是C#泛型的类型可变性规则导致的:
- C#中默认泛型类型参数是*不变(Invariant)*的,即使两个泛型参数类型存在继承关系,对应的闭合泛型类型之间也不存在隐式转换关系。
- 虽然
ModularBuilding派生自MonoBehaviour,但MonoPool<ModularBuilding>和MonoPool<MonoBehaviour>是完全独立的两个类型,没有继承关系,因此不能直接返回。 - 编译器做这个限制是为了保证类型安全:如果允许隐式转换,外部代码拿到
MonoPool<MonoBehaviour>类型的引用后,就可以调用AddObject方法向池子里塞入任意继承自MonoBehaviour的对象(比如游戏角色、UI组件等),破坏原本只存储ModularBuilding的池子的类型一致性,最终导致运行时类型转换错误。
解决方案
根据业务场景可以选择以下两种合规方案:
方案1:声明协变泛型接口(推荐)
C#支持在泛型接口、泛型委托上用out关键字声明协变类型参数,允许泛型接口类型按照类型参数的继承关系做隐式转换。协变要求类型参数只能出现在接口方法的返回值位置,不能作为方法入参,从语法层面避免类型安全问题。
- 首先定义协变的对象池接口:
// out关键字标记T为协变类型参数 public interface IMonoPool<out T> where T : MonoBehaviour { // T只能作为返回值,不能作为方法入参 T Get(); // 暴露池内容时用支持协变的IEnumerable<T>,不能用List<T> IEnumerable<T> PrefabPool { get; } }
- 让原MonoPool类实现该接口:
public class MonoPool<T> : IMonoPool<T> where T : MonoBehaviour { private readonly List<T> _prefabPool = new List<T>(); public IEnumerable<T> PrefabPool => _prefabPool; // 写入方法属于类的自有成员,不在协变接口中暴露,不会破坏类型安全 public void AddObject(T prefab) { _prefabPool.Add(prefab); } public T Get() { // 取对象逻辑,比如从池内拿可用对象、不足时实例化新对象 return _prefabPool.FirstOrDefault(); } }
- 修改ILevelGenerator接口的返回值为协变接口类型:
public interface ILevelGenerator { // 返回协变接口类型 public IMonoPool<MonoBehaviour> Create(); public void Update(); public void SetMode(int mode); }
修改完成后BuildingsGenerator的代码不需要做额外调整,直接返回_buildingsPool就可以通过编译:MonoPool<ModularBuilding>实现了IMonoPool<ModularBuilding>,协变规则允许IMonoPool<ModularBuilding>隐式转换为IMonoPool<MonoBehaviour>,且外部无法通过接口引用向池内写入错误类型的对象,完全保证类型安全。
方案2:将ILevelGenerator声明为泛型接口
如果业务逻辑中调用Create方法后需要拿到明确类型的对象池,不需要统一按非泛型类型处理所有生成器,可以直接把生成器接口改成泛型:
// 给接口加泛型参数,约束T必须是MonoBehaviour子类 public interface ILevelGenerator<T> where T : MonoBehaviour { public MonoPool<T> Create(); public void Update(); public void SetMode(int mode); }
实现时指定具体的泛型类型即可:
public class BuildingsGenerator : ILevelGenerator<ModularBuilding> { private readonly MonoPool<ModularBuilding> _buildingsPool; public BuildingsGenerator() { _buildingsPool = new MonoPool<ModularBuilding>(); } public MonoPool<ModularBuilding> Create() { //业务逻辑 return _buildingsPool; } public void Update() { // 业务逻辑 } public void SetMode(int mode) { // 业务逻辑 } }
这种方案没有任何类型转换开销,类型安全度最高,缺点是无法用非泛型的接口类型统一持有所有生成器实例。
不推荐通过新建
MonoPool<MonoBehaviour>、逐个复制原池子对象的方式做返回,会产生额外的内存和性能开销,还容易出现数据不一致的问题。
内容的提问来源于stack exchange,提问作者Alex.D
相关产品推荐
相关产品推荐

