Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I define a function that accepts any Task producing an IEnumerable<T>?

I'm looking to create a function that accepts any task that produces an IEnumerable<T>. To illustrate, consider the following function signature.

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

Now, I would like to call this method as follows:

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

Clearly, this doesn't work since the two Task types are not the same, and that covariance doesn't exist for Tasks. However, I am wondering if there are some clever tricks that will allow this to work, inspired by the following example.

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

Here, await is effectively creating a new task with the result of the inline task, hence the illusion of a type conversion.

To give a more detailed example, I'd like to allow for users to call DoWork without too much of a burden in conversions:

// 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);
like image 582
Steve Guidi Avatar asked Aug 09 '14 17:08

Steve Guidi


People also ask

How define IEnumerable in C#?

What is IEnumerable in C#? IEnumerable in C# is an interface that defines one method, GetEnumerator which returns an IEnumerator interface. This allows readonly access to a collection then a collection that implements IEnumerable can be used with a for-each statement.

How can I add an item to a IEnumerable T collection?

What you can do is use the Add extension method to create a new IEnumerable<T> with the added value. var items = new string[]{"foo"}; var temp = items; items = items. Add("bar");


1 Answers

You could introduce one more Type parameter and do something like this:

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;
}

Or as noted by OP in comments you can make compiler to infer the types if you specify TElement explicitly.

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

Your code doesn't work since Task<T> is not "Covariant" on T. You may be aware that classes can't be covariant.

like image 193
Sriram Sakthivel Avatar answered Oct 11 '22 14:10

Sriram Sakthivel