-1

As the title states, I'm curious if it's possible to convert from Task<List<Derived>> to Task<List<BaseType>>?

I have a couple of methods that return Task<List<Derived>>, and there can be multiple types derived classes. I want to store all the tasks in a list of tasks: List<Task<List<BaseType>>> to be executed at a later time.

If my tasks weren't wrapping lists, this would be pretty straight forward: link


Edit 1

Here is the part I'm having trouble with, I'm having issues storing the tasks in a list:

// This throws an error (cannot convert derived list to base list)
var tasks = new List<Task<List<BaseType>>>
{
    asyncThing1(), // returns Task<List<Derived1>>
    asyncThing2(), // returns Task<List<Derived2>>
    // ... n number more
};

// Need the list of tasks before I can execute
List<BaseType>[] results = await Task.WhenAll(tasks);

Edit 2

Above, changed List<Base>[] to List<BaseType>[]

Andrew
  • 893
  • 12
  • 28

2 Answers2

5

As the title states, I'm curious if it's possible to convert from Task<List<Derived>> to Task<List<Base>>?

No. Suppose it were legal, and see what goes wrong:

Task<List<Giraffe>> t1 = GetGiraffesAsync();
Task<List<Animal>> t2 = t1; // Suppose this were legal
(await t2).Add(new Fish());

And now we have a fish in a list of giraffes.

One of those three lines has to be illegal. Plainly it cannot be the first, since GetGiraffesAsync returns Task<List<Giraffe>>. Plainly it cannot be the last, since await t2 produces a List<Animal>, and a fish is an animal. Therefore it must be the middle line that is illegal.

Now, you could do this:

async Task<List<Animal>> ConvertAsync(Task<List<Giraffe>> t) => 
  (await t).Select(g => (Animal)g).ToList();

Or

async Task<List<Animal>> ConvertASync(Task<List<Giraffe>> t) => 
  (await t).Cast<Animal>().ToList();

or

async Task<List<Animal>> ConvertAsync(Task<List<Giraffe>> t) => 
  (await t).OfType<Animal>().ToList();

If you wanted to make it generic you could do

async Task<<List>Animal> ConvertAsync<T>(Task<List<T>> t) where T : Animal =>
  (await t).OfType<Animal>().ToList();

and now it works with giraffes and fish and tigers and so on.

That is, you can asynchronously wait for the original task to finish, and when it is done, you can create a new list of animals from your list of giraffes. That's perfectly legal. You now have two lists, one of giraffes and one of animals.

Or, you could do this:

async Task<IEnumerable<Animal>> ConvertAsync(Task<List<Giraffe>> t) => 
  await t;

since List<Giraffe> is convertible to IEnumerable<Animal>.

I would be inclined to write it as an extension method.

static class Extensions {
  public static Task<List<Animal>> ConvertAsync<T>(
    this Task<List<T>> t) where T : Animal {
      return (await t).OfType<Animal>().ToList();
  } 
}

And now your program fragment is:

var tasks = new List<Task<List<Animal>>>
{
  GetGiraffesAsync().ConvertAsync(),
  GetTigersAsync().ConvertAsync()
};
Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • 2
    The extension method was exactly what I was looking for. Thank you for the well explained answer! – Andrew May 08 '18 at 15:54
2

Just as a little side note, your posted link was just missing the List part, to make it work for your scenario.

So this was proposed in the other topic: Cannot convert type 'Task<Derived>' to 'Task<Interface>'

async Task<TBase> GeneralizeTask<TBase, TDerived>(Task<TDerived> task) 
    where TDerived : TBase 
{
    return (TBase) await task;
}

What you were searching for is:

async Task<List<TBase>> GeneralizeTask<TBase, TDerived>(Task<List<TDerived>> task) 
    where TDerived : TBase 
{
    return (await task).Cast<TBase>().ToList();
}

With that you could call it like this:

var tasks = new List<Task<List<Animal>>>
{
  GetGiraffesAsync().GeneralizeTask<Animal, Giraffe>(),
  GetTigersAsync().GeneralizeTask<Animal, Tiger>()
};
Rand Random
  • 7,300
  • 10
  • 40
  • 88
  • Yup, that's what I was looking for. I wouldn't consider my question a duplicate as it seems to be marked now. Thanks for adding this. – Andrew May 08 '18 at 21:40