Как определить функцию, которая принимает любую задачу, создающую IEnumerable <T>?

Я ищу, чтобы создать функцию, которая принимает любую задачу, которая создает 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);

Ответ 1

Вы можете ввести еще один параметр 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 });
}

Тогда вы могли бы

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. Вы можете знать, что классы не могут быть ковариантными.