You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

泛型接口协变问题:游戏任务系统无法添加特定任务的解决方法

问题原因与解决方案

为什么会出现类型转换失败?

C# 默认的泛型接口是不变类型,也就是说IGameTask<IBuildTaskPayload>和IGameTask<IPayload>属于完全独立的类型,无法直接互相转换。

你的场景中,IGameTask<T>的UpdateTask方法接收T作为输入参数,属于输入型泛型参数。如果强行允许IGameTask<IBuildTaskPayload>转换为IGameTask<IPayload>,会破坏类型安全:后续若调用该任务的UpdateTask时传入一个普通IPayload实例(而非IBuildTaskPayload),BuildTask的实现逻辑无法处理该类型,必然引发错误。因此C#编译器直接禁止了这种不安全的转换。

正确实现方案

方案一:定义非泛型基础接口(推荐)

如果任务仓库无需知晓具体Payload类型,只需统一管理任务实例,可先创建一个非泛型的IGameTask接口,让泛型接口继承它:

// 非泛型基础接口,存放所有任务的通用成员
public interface IGameTask
{
    TaskStatus CurrentStatus { get; }
    // 可添加其他通用属性/方法,比如任务ID、取消方法等
}

// 泛型任务接口,继承自非泛型接口
public interface IGameTask<in T> : IGameTask where T : IPayload
{
    TaskStatus UpdateTask(T updatePayload);
}

随后修改任务仓库的存储容器和添加方法:

// 仓库改为存储非泛型IGameTask
public List<IGameTask> Tasks { get; } = new();

internal void AddTask(IGameTask task)       
{
    Tasks.Add(task);
    Broker.Instance.Publish(new TaskAdded(task));
}

这样BuildTask(实现IGameTask<IBuildTaskPayload>)可以直接添加到仓库,因为它自动继承了非泛型的IGameTask接口,完全符合仓库的存储要求。

方案二:利用逆变特性(仅限特定场景)

如果仓库必须保留泛型类型,且调用UpdateTask时只会传入对应具体类型的Payload,可以将IGameTask的泛型参数声明为逆变(使用in关键字):

// 声明逆变泛型参数in T
public interface IGameTask<in T> where T : IPayload
{
    TaskStatus UpdateTask(T updatePayload);
}

注意:逆变泛型参数只能作为方法的输入参数,不能作为返回值或属性的类型。这种方案下,你无法将IGameTask<IBuildTaskPayload>直接存入List<IGameTask<IPayload>>(逆变允许的是更抽象的IGameTask<IPayload>转换为更具体的IGameTask<IBuildTaskPayload>),因此仅适用于仓库需要处理“接收任意IPayload任务”的场景,大多数游戏任务系统中方案一更实用。


内容的提问来源于stack exchange,提问作者Coreylh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 04:25:34