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);
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.
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");
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With