1

I have this code:

static List<string> lst = new List<string>();

public static Task<IEnumerable<String>> GetStrings() {
    IEnumerable<String> x = lst;
    
    // This line below compiles just fine.
    // return Task.Run(() => x);
    
    // This line gives a compiler error:
    // Cannot implicitly convert type 'System.Threading.Tasks.Task<System.Collections.Generic.List<string>>' to
    // 'System.Threading.Tasks.Task<System.Collections.Generic.IEnumerable<string>>'
    return Task.Run(() => lst);
}    

Why does this line compile just fine: return Task.Run(() => x); while return Task.Run(() => lst); gives the compiler error:

Cannot implicitly convert type 'System.Threading.Tasks.Task<System.Collections.Generic.List>' to 'System.Threading.Tasks.Task<System.Collections.Generic.IEnumerable>'

I would think that since List<string> is an IEnumerable<string>, I should not have to explicitly set x. I mean, the compiler knows lst is an IEnumerable<string>.

I think I must be missing something fundamental here.

Tom Baxter
  • 2,110
  • 2
  • 19
  • 38
  • 1
    You could fix it with `Task.Run(() => (IEnumerable)lst);` – Rand Random Aug 27 '20 at 12:55
  • 1
    https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.asenumerable?redirectedfrom=MSDN&view=netcore-3.1#System_Linq_Enumerable_AsEnumerable__1_System_Collections_Generic_IEnumerable___0__ – vhr Aug 27 '20 at 12:55
  • The error is about the `Task` types, not Listand IEnumerable. Just because the type parameters have an inheritance relation doesn't mean the generic types have such a relation too – Panagiotis Kanavos Aug 27 '20 at 12:56
  • 7
    `Task` is not [covariant](https://learn.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance). – Johnathan Barclay Aug 27 '20 at 12:57
  • `Task` doesn't state that `T` as `out T` but `IEnumerable` does – Rand Random Aug 27 '20 at 12:57
  • `Task` is invariant (as any other class), only interfaces and delegates support generic variance – Pavel Anikhouski Aug 27 '20 at 12:58
  • "_But the type of lst and the type of x in the example are both identical_" No they are not. `lst` is `List` whereas `x` is `IEnumerable` at compile time. Runtime type is irrelevant here. – Johnathan Barclay Aug 27 '20 at 12:59
  • Another fix would be to await the `Task.Run` and make the method `async`, that way you are returning a compatible object. – DavidG Aug 27 '20 at 12:59
  • You could fix it with `Task.Run>(() => lst);`. – vernou Aug 27 '20 at 13:01

2 Answers2

5

Because C# first tries to determine the type of your expression Task.Run(() => lst) and then checks whether it is a suitable return type for your method.

  1. lst is a List<string>.
  2. Thus, () => lst is a lambda with a return value of List<string>.
  3. Thus, Task.Run<TResult>(Func<TResult>) called with () => lst infers List<string> as its type parameter TResult.
  4. Thus, Task.Run(() => lst) is of type Task<List<string>>.

You then try to use a value of type Task<List<string>> as the return value of a method returning a Task<IEnumerable<string>>. Task<TResult> is not covariant, hence, you get a compile-time error.

(As an aside: Having a covariant ITask<out TResult> interface would fix this, but the .NET development team decided against it.)

Yes, the compiler could check how you are using the expression created in step 4 and try different interfaces of lst to make it fit, but that's a feature that C# (currently) does not have (and which, in my purely personal opinion, is probably not worth implementing and maintaining due to its low cost-benefit ratio).


To solve this, you can either provide a different type in step 2, by explicitly casting the return type:

return Task.Run(() => (IEnumerable<string>)lst);

or by explicitly declaring the type of the lambda expression:

return Task.Run(new Func<IEnumerable<string>>(() => lst));

or you could override the automatic inference in step 3 with an explicit type argument:

return Task.Run<IEnumerable<string>>(() => lst);
Heinzi
  • 167,459
  • 57
  • 363
  • 519
1

The problem is that you are using different data types here (List<T> vs. IEnumerable<T>). You can fix the issue with using type casting like:

Task.Run(() => (IEnumerable<string>)lst);

JKD
  • 1,279
  • 1
  • 6
  • 26