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

泛型协变理解困惑:C#泛型类入队编译错误排查

泛型协变问题导致的FetchLinesWorker队列编译错误及C#等效Java通配符写法

问题描述

我在尝试将两个不同泛型参数的FetchLinesWorker实例加入同一队列时遇到编译错误:

var transactionsWorker = new FetchLinesWorker<TransactionLine, UnsoldLine>(new UnsoldLineWrapper(), new FilteredTransactionLines());
var ordersWorker = new FetchLinesWorker<OrderLineDeliveryGood, UnsoldLine>(new UnsoldDeliveryLineWrapper(), new FilteredOrderLines());
var workerQueue = new Queue<FetchLinesWorker<object, UnsoldLine>>();
workerQueue.Enqueue(transactionsWorker); // 编译错误:无法转换类型
workerQueue.Enqueue(ordersWorker);      // 同样的编译错误
new WorkerForm(workerQueue, () => whenFetchingFinished(workerQueue.SelectMany(w => w.Result))).StartJob();

编译器提示无法将FetchLinesWorker<XXXLine, UnsoldLine>转换为FetchLinesWorker<object, UnsoldLine>。我有Java背景,原以为C#支持泛型协变,而且Java中可以用Queue<FetchLinesWorker<?, UnsoldLine>>来实现类似场景,请问C#中为什么会报错?有没有等效的写法?

补充上下文代码:

public interface IWrapperLine<In, Out> { Out Convert(In input); }
public class FetchLinesWorker<In, Out> : JobWorker {
    private readonly IQueryable<In> _inputs;
    private readonly IWrapperLine<In, Out> _converter;
    public FetchLinesWorker(IWrapperLine<In, Out> converter, IQueryable<In> inputs) : base(0, TranslationsHelper.GetEntityProgressMessage(typeof(In))) {
        _inputs = inputs;
        _converter = converter;
        DoWork += SoldStatisticLinesWorker_DoWork;
    }
    private void SoldStatisticLinesWorker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e) {
        Result.Clear();
        ProgressMax = _inputs.Count();
        foreach (var input in _inputs) {
            Result.Add(_converter.Convert(input));
            DoProgress();
        }
    }
    public List<Out> Result { get; } = new List<Out>();
}
public class FilteredTransactionLines : IQueryable<TransactionLine> { /* 实现代码 */ }
public class FilteredOrderLines : IQueryable<OrderLineDeliveryGood> { /* 实现代码 */ }
public class WorkerForm {
    public WorkerForm(Queue<JobWorker> workers, Action finishedAction) { /* 实现代码 */ }
}

注:JobWorker继承自BackgroundWorker,无法修改该类。


错误原因分析

你遇到的核心问题是C#的泛型协变/逆变仅支持泛型接口和泛型委托,泛型类不支持协变或逆变。

在Java中,FetchLinesWorker<?, UnsoldLine>的通配符?可以接受任意类型的第一个泛型参数,但C#的泛型类没有类似的通配符语法。更关键的是,即使你认为TransactionLine可以向上转型为object,FetchLinesWorker<TransactionLine, UnsoldLine>也不能直接转换为FetchLinesWorker<object, UnsoldLine>——因为FetchLinesWorker是类,C#不允许泛型类的协变转换,哪怕类型参数是兼容的。

另外,即使你想让In参数支持协变,也需要在接口上标记out关键字(协变),但类无法做到这一点。


C#中的等效解决方案

要实现类似Java通配符的效果,我们可以通过定义协变泛型接口来封装你需要的公共行为(比如获取Result),具体步骤如下:

1. 定义协变接口

创建一个只暴露你需要的公共成员的协变接口,这里我们只需要获取Result,所以接口可以这样定义:

// 使用out关键字标记Out为协变类型参数
public interface IFetchLinesWorker<out Out>
{
    List<Out> Result { get; }
}

2. 修改FetchLinesWorker实现接口

让FetchLinesWorker实现这个新接口:

public class FetchLinesWorker<In, Out> : JobWorker, IFetchLinesWorker<Out>
{
    // 原有的构造函数、DoWork方法、Result属性保持不变
}

3. 调整队列声明和使用

现在你有两种选择,取决于你是否需要兼容WorkerForm的参数要求:

方案A:直接使用协变接口队列(如果WorkerForm允许修改参数类型)

如果可以修改WorkerForm的构造函数参数类型,直接声明队列:

var workerQueue = new Queue<IFetchLinesWorker<UnsoldLine>>();
workerQueue.Enqueue(transactionsWorker);
workerQueue.Enqueue(ordersWorker);
// 假设WorkerForm可以接受Queue<IFetchLinesWorker<UnsoldLine>>
new WorkerForm(workerQueue, () => whenFetchingFinished(workerQueue.SelectMany(w => w.Result))).StartJob();
方案B:使用JobWorker队列(兼容现有WorkerForm)

因为WorkerForm的构造函数接受Queue<JobWorker>,我们可以直接用这个类型的队列,然后在获取Result时通过OfType筛选并转换为协变接口:

var workerQueue = new Queue<JobWorker>();
workerQueue.Enqueue(transactionsWorker);
workerQueue.Enqueue(ordersWorker);
new WorkerForm(workerQueue, () => 
    whenFetchingFinished(workerQueue.OfType<IFetchLinesWorker<UnsoldLine>>().SelectMany(w => w.Result))
).StartJob();

额外说明

  • 协变接口的out关键字表示:如果A是B的子类,那么IFetchLinesWorker<A>可以转换为IFetchLinesWorker<B>,这正好符合我们的需求(所有Worker的Out都是UnsoldLine)。
  • 如果你不需要协变,也可以定义一个非泛型接口(比如IFetchLinesWorker),里面包含IEnumerable<object> Result { get; },但这样需要在使用时强制转换,不如协变接口类型安全。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 07:28:02