0

I am creating a method with this signature

public async Task RunThisFunction(Func<Task> func)

now the user of this method can use it with a function like this as the parameter

 public async Task GetThingsDone(){await DoSomething();}

No surprises there, but it also seems that you can send in functions like this

public async Task<string> GetString(){await return GetSomething();}

Why is that and how would I write a signature to prevent the latter to be sent in to RunThisFunction?

Cowborg
  • 2,645
  • 3
  • 34
  • 51
  • 1
    What do you want to achive? – D-Shih Apr 28 '22 at 05:02
  • This is a little outside the scope of my answer, and perhaps outside the scope of your question as asked, but if you're building a library that will become a NuGet package, you could perhaps [bundle a Roslyn analyzer](https://learn.microsoft.com/en-us/archive/msdn-magazine/2015/october/code-analysis-build-and-deploy-libraries-with-integrated-roslyn-code-analysis-to-nuget) with it, to ensure correct usage of the method. Building analyzers is also outside the scope of my experience so I can't provide an example for it (sorry). – ProgrammingLlama Apr 28 '22 at 05:55

2 Answers2

1

The Task<T> class inherits the Task class, so a method that returns a Task<string> IS returning a Task. You generally can't provide a less specific type where a more specific type is expected, because it won't have the additional members that the more specific type does. You generally can provide a more specific type where a less specific type is expected because it will have all the members that the less specific type does. If you have code that accesses the members of the Task class and you pass in a Task<string> then the code will work because the Task<string> object has all those members.

user18387401
  • 2,514
  • 1
  • 3
  • 8
  • ok, that makes sense.. is there a way around this? – Cowborg Apr 28 '22 at 05:01
  • Note that this is not really correct explanation as the question is not "why `Task` derives from `Task` but rather "why `Func` can take `Func`"... but since it is upvoted it means you should ignore that. – Alexei Levenkov Apr 28 '22 at 05:35
1

You can infer the task's generic argument:

public async Task<TTaskArg> RunThisFunction<TTaskArg>(Func<Task<TTaskArg>> func)
{
    return await func();
}

You can place it as an overload of your existing method, and the compiler will choose the correct one based on the inferred type.

Try it online

Note: While I haven't used the async keyword in my runnable example, that's because my code is essentially synchronous. In practice, you will probably want to use the async and await keywords as above.


If you wish to display a compiler warning, you could add the [Obsolete] attribute, for example:

[Obsolete("Please do not use tasks with a generic parameter.")]
public async Task<TTaskArg> RunThisFunction<TTaskArg>(Func<Task<TTaskArg>> func)
{
    return await func();
}

And perhaps (if you need it) you could throw an InvalidOperationException from within the method if you want to prevent its (successful) use at runtime too.

As Alexei said, you could also enable "warnings as errors" for the obsolete warning (CS0612). See here for instructions on how to do that. Remember that you need to use CS0612 and not NU1605 though.

ProgrammingLlama
  • 36,677
  • 7
  • 67
  • 86