如何定义一个函数来接受任何产生IEnumerable的Task ?

本文关键字:IEnumerable 任何产 Task 定义 何定义 函数 一个 | 更新日期: 2023-09-27 18:17:11

我希望创建一个函数,该函数接受产生IEnumerable<T>的任何任务。为了说明这一点,考虑下面的函数签名。

void DoWork<TElement>(Task<IEnumerable<TElement>> task)
{ }

现在,我想这样调用这个方法:

Task<int[]> task = Task.FromResult(new[] { 1, 2, 3 });
DoWork(task);

显然,这不起作用,因为两个Task类型不相同,并且任务不存在协方差。然而,我想知道是否有一些聪明的技巧可以让它工作,灵感来自下面的例子。

async Task<IEnumerable<int>> GetTask()
{
    return await Task.FromResult(new int[] { 1, 2, 3 });
}

这里,await有效地用内联任务的结果创建了一个新任务,因此产生了类型转换的错觉。

为了给出一个更详细的示例,我希望允许用户调用DoWork而不会在转换中造成太多负担:

// Service proxy method
Task<int[]> GetInts()
{
    // simplified for brevity
    return Task.FromResult(new[] { 1, 2, 3 });
}
// Service proxy method
Task<long[]> GetLongs()
{
    // simplified for brevity
    return Task.FromResult(new[] { 100L, 200L, 300L });
}
async Task<IEnumerable<T>> DoWork<T>(Func<Task<IEnumerable<T>>> getData,
                                     Func<T, bool> predicate)
{
    return (await getData()).Where(predicate);
}
// GOAL:
DoWork(GetInts, i => i % 2 == 0);
DoWork(GetLongs, l => l % 40 == 0);

如何定义一个函数来接受任何产生IEnumerable<T>的Task ?

您可以再引入一个Type参数,这样做:

async Task<IEnumerable<TElement>> DoWork<T, TElement>(Func<Task<T>> getData,
                              Func<TElement, bool> predicate) where T : IEnumerable<TElement>
{
    return (await getData()).Where(predicate);
}
Task<int[]> GetInts()
{
    return Task.Run(() => new[] { 1, 2, 3 });
}
Task<long[]> GetLongs()
{
    return Task.Run(() => new[] { 100L, 200L, 300L });
}

Then you could to

static void Main()
{
    var ints = DoWork<int[], int>(GetInts, i => i % 2 == 0).Result;
    var longs = DoWork<long[], long>(GetLongs, i => i % 2 == 0).Result;
}

或者如OP在注释中所指出的,如果显式指定TElement,则可以使编译器推断类型。

var ints = DoWork(GetInts, (int i) => i % 2 == 0).Result;

你的代码不工作,因为Task<T>不是T的"协变"。你可能知道类不能协变。