2

Summary

Given a run-time reference to a type (and a value of that type), how can I create a completed Task of that type with the given value?

Similar to Task.FromResult<T>(value), but where T is unknown at compile time?


Background

Not that it matters, I'm trying to create a mock implementation of IAsyncQueryProvider, by adapting some code I found here (including some changes for .NET Core 3.1 suggested in the comments by @Mandelbrotter).

Here's the relevant method:

public TResult ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken)
{
    var value = Execute(expression);

    return Task.FromResult((dynamic) value);
}

Note that because this is an async method, TResult is actually always of type Task<something>. But, we can't declare the method like that because we're implementing a pre-defined interface. Nonetheless, we use Task.FromResult() to return a completed Task.

You'll notice that we don't use Task.FromResult(value) directly; we cast value to dynamic first. This is because the return value of the Execute call is declared as Object, and if we simply did Task.FromResult(value) we'd return something of type Task<Object>, which is generally not the correct type - and that causes issues further up the stack.

However, while this approach works for queries returning simple types such as int or DateTime, it fails when the query returns a nullable value. In this case, when the value returned is not null the type of value is just the underlying type, not the nullable type - and so we end up returning something of type Task<int> instead of Task<int?>.

I'd like to solve this (and do away with the use of dynamic) by explicitly creating a Task of the correct type. I can determine the desired type, either using reflection on TResult, or more conveniently by using expression.Type.

However, even once I know the type, I can't find a way to create a completed Task of that type. Task.FromResult<T> requires me to know T at compile-time. I've not been able to find a way to create a completed Task by passing in the type as a parameter.

Servy
  • 202,030
  • 26
  • 332
  • 449
Gary McGill
  • 26,400
  • 25
  • 118
  • 202
  • You'd be way better off redesigning the method so that it returns `Task` and the caller provides a type that *isn't* a task, but rather what the result of that task is. As it is, you've written code that will just fail at runtime if the caller doesn't just happen to know that they need to provide `Task` as the generic argument, and you shouldn't do that. – Servy Jul 15 '21 at 16:50
  • @Servy I think he's trying to mock for the sake of tests – galdin Jul 15 '21 at 16:51
  • @galdin The method is called `ExecuteAsync` it sure does seem like it's inherent to the method that it's returning a task, and it's not specific to just the mock object. The thing that he's basing his design off of has a signature altered in the way I described, so if anything this is likely an unacceptable deviation for the mock version to make. – Servy Jul 15 '21 at 16:52
  • @Servy, @galdin: I cannot change the signature of the method, because I'm implementing an interface (`IAsyncQueryProvider`) whose method signatures I cannot change. Anyway, see my updated - I've found a solution. Thanks. – Gary McGill Jul 15 '21 at 17:01
  • @Servy: since I could no longer post an answer (this having been marked as duplicate), I added my solution to the question, on behalf of all those people who might be trying to do the same thing. You've reverted my changes to the question, and so those changes are lost. I mention that so that anyone looking for the answer knows who to thank. – Gary McGill Jul 15 '21 at 17:06
  • @GaryMcGill There's no need to edit the question to say that the duplicate answers the question or to copy the answer from the duplicate. People who want to find the answer can simply go to the duplicate. That's the whole point of closing questions as a duplicate. But I'm glad that the answer I proved for you want helpful for you solving your problem. – Servy Jul 15 '21 at 17:08
  • 2
    I'm voting to re-open this because this question is more specific than the marked duplicate. – galdin Jul 18 '21 at 12:26
  • 1
    @galdin Thank God that you can view the edit history of the question. And thank you for writing that you put the answer in the question! Your solution fixes my problem 100%. The question which is marked as duplicated is wrong. It is not the same. – TheBigNeo Dec 20 '22 at 12:17

1 Answers1

-2

Since Execute() returns an object, something like this should work as long as it can actually be cast to TResult:

public TResult ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken)
{
    var value = Execute(expression); 
    if(value is TResult valueT) return Task.FromResult(valueT); // works for nullable types
    return Task.FromResult((T) value); // non-nullable types
}

The linked answer has a Execute<T>() example... if you choose to use that, you'd get something like:

public TResult ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken)
{
    var value = Execute<TResult>(expression);
    return Task.FromResult(value);
}
galdin
  • 12,411
  • 7
  • 56
  • 71
  • Sorry, no - `TResult` is something like `Task` and `Execute` in this case would return an `int?` (except that in fact we'd get the unboxed value so it would just be `int`). – Gary McGill Jul 15 '21 at 16:28