How to properly return TypedResult from non-async Task [duplicate]
Asked Answered
P

2

2

I have an interface that requires me to return Task<T>, but there is nothing awaitable called, hence it I could return Task.FromResult(instanceofT), like in following simple situation:

public Task<bool> Get()
{
    return Task.FromResult(true);
}

But I have TypedResults as return type. And I get cast compiler errors:

public Task<Results<Ok, NotFound>> ReceiveCommand(ICommand command)
{
    ....

    if (condition) return Task.FromResult(TypedResults.NotFound());

    return Task.FromResult(TypedResults.Ok());
}

The only way I could get this done is by forcefully awaiting something:

public async Task<Results<Ok, NotFound>> ReceiveCommand(ICommand command)
{
    ....

    if(condition) return await Task.FromResult(TypedResults.NotFound());

    return await Task.FromResult(TypedResults.Ok());
}

or

public async Task<Results<Ok, NotFound>> ReceiveCommand(ICommand command)
{
    ...

    await Task.CompletedTask;

    if(condition) return TypedResults.NotFound();

    return TypedResults.Ok();
}

It will most likely be optimized away, but I still find it ugly. I should most likely use an explicit type parameter for the generic FromResult, but I have no clue what to use, except Results<Ok, NotFound> which is again ugly. I find it weird that the implicit casting works for the async version but not here. What am I missing?

Piane answered 13/12, 2023 at 14:49 Comment(18)
@MarcGravell That's the one I am looking for. It is a quite new built in asp.net core type: learn.microsoft.com/en-us/dotnet/api/… It implements three different interfaces, none seems to be enough on its own.Piane
@GuruStron In what way did the post fail to answer t he question asked? This has literally everything to do with the fact that task isn't covariant. The question is asking why Task<Derived> isn't implicitly convertible to Task<Base>. That's called covariance. The fact that you posted an answer that makes no attempt to answer the question doesn't make the question any less of a duplicate.Alberto
@Alberto Result<T1, T2> is not derived from NotFound and Ok classes, it is implicitly convertible.Jokjakarta
@Alberto Me reopening the question has nothing to do with the fact that I have already posted the answer before it was closed.Jokjakarta
@GuruStron And yet the OP wants to use Task covariant. And it's failing. That it wouldn't work even if the type supported the limited covariance interfaces support doesn't change the fact that it can't be used covariant at all, and that's what they're trying to do.Alberto
@GuruStron The fact that you posted an answer that makes no attempt to answer the question asked suggests you don't understand it sufficiently to reopen it.Alberto
@Alberto not exactly. If the return type was Task<IResult> then I would completely agree with your suggested duplicate. But in this case covariance is not related. Even if return type would be some ITask<out T> the code would not work since Result<T, T1>, Ok and NotFound do not share inheritance hierarchy (though they all implement IResult).Jokjakarta
@GuruStron As I said before, they are trying to use Task Covariantly. That there is an additional reason why it wouldn't here, beyond just the fact that Task doesn't support covariance at all, doesn't change that. Note that, in principle, covariance, as a concept, can be made to apply to any implicit conversion, not merely identity-preserving ones. The existing covariance of interfaces only supports identity-preserving covariant conversions, but they didn't have to do that. They could support non-identity preserving covariant conversions next version if they wanted to.Alberto
@GuruStron If the OP was using some interface, and not as Task and wondering why they couldn't use it covariantly with Result, all of your points would be valid, because the question I brought up is about the fact that no covariance is supported, not that only certain types are supported by interfaces. If they had asked that hypothetical different question it would still be a duplicate, just of a different question.Alberto
@Alberto I can agree that my reopening comment is somewhat harsh. It arguably is somewhat related. But I disagree with the duplicate for the question. As you said that covariance could be made to apply to any implicit conversion but it does not in C#. So the suggested duplicate does not answer this question.Jokjakarta
@GuruStron That there is an unrelated piece of trivial not relevant to the question asked that you thought of (but for some reason didn't post in your "answer" to the question) doesn't make the question not a duplicate. The question was why Task can't be used covariantly. The answer is that task doesn't support any covariance at all. That other places that do support some covariance don't support this kind of covariance is perhaps interesting, but not the answer to the question. The answer to the question is that task doesn't support any covariance.Alberto
@Alberto From my point of view "because Task is not covariant" being duplicate implies that if Task was covariant the code above would work. But it still would not. So I hope you can see why I don't agree that "because Task is not covariant" is a duplicate here. Thank you for discussion - it was interesting for me and I glad that you have spent time to explain your point of view!Jokjakarta
@GuruStron It does not imply that. Again, the question is why can Task not be used covariantly. The answer is it supports no covariance at all. That is the answer. If the question was asking about an interface, then there would be different duplicate relating to non-identity preserving implicit conversions. But that wasn't the question. That Task supports no covariance at all doesn't logically imply that if it supported the same covariance interfaces currently do this would work. You've explained why your inferences from the answer are false, not why the answer is false.Alberto
@Alberto TBH question was not "Why" at all. It was "How" =)Jokjakarta
@GuruStron They posted some code that didn't compile and asked why it didn't compile. The question itself shoed multiple means of solving the underlying problem. They have the solution to how to do what they're trying to do, just not why the first snippet doesn't work. That's what they asked.Alberto
@Alberto "I find it weird that the implicit casting works for the async version but not here. What am I missing?" - TBH does not sound like "because Task is not covariant" is the answer which fully explains the situation here. Again - thank you for discussion and excuse me if any of what I have written seemed harsh/inappropriate, had no such intentions. If you'll find the duplicate for reference preserving conversions for covariance I think we can close the question providing those too.Jokjakarta
@GuruStron And yet it is. The thing that they were missing from why one worked and one didn't was that one attempted a covariant conversation of Task, which isn't allowed, and another didn't. It turns out the answer was not in fact that they should have changed the return type of the method and not implement the interface that they claimed they were trying to implement. Even though that sounds like it would be the answer to the question, it turns out that isn't.Alberto
@GuruStron Since you asked, here is the first question about non-identity conversions with covariance I found. I'm sure there are others. But again, that is not an answer to the question. It is a tangential piece of trivia. If someone asks why they can't drive a car over the Atlantic Ocean the answer is that there's no road, not that there aren't enough gas stations to get there. In this case covariance doesn't work at all for tasks. That's the answer. Considering hypotheticals about what if it did is a separate question.Alberto
J
2

If it is possible - scrap the Task from the return type (assuming you can change the interface and you will not have async implementations):

public Results<Ok, NotFound> ReceiveCommand(ICommand command)
{
    ...

    if (...) return TypedResults.NotFound();

    return TypedResults.Ok();
}

Other than that you will need either to use one of the workarounds you don't like (async-await or manually specifying task type) or you can introduce local function which will produce the correct Result type:

public Task<Results<Ok, NotFound>> ReceiveCommand(ICommand command)
{
    // ...
    return Task.FromResult(GetResult());

    Results<Ok, NotFound> GetResult()
    {
        if (condition) return TypedResults.NotFound();
        return TypedResults.Ok();
    }
}

Or via explicitly typed result variable:

public Task<Results<Ok, NotFound>> ReceiveCommand(ICommand command)
{
    Results<Ok, NotFound> result = condition
        ? TypedResults.NotFound()
        : TypedResults.Ok();

    return Task.FromResult(result);
}

Note that await approach (and suggested workarounds) function due to the implicit conversion defined in Results<TResult1,TResult2> and async machinery (as far as I understand compiler will do something similar to the last snippet - it will declare variable of resulting type and assign corresponding values "triggering" implicit conversion).

Jokjakarta answered 13/12, 2023 at 14:57 Comment(5)
The interface requires me to use Task :(Piane
@Piane see the updated suggestion.Jokjakarta
Interesting approach.Piane
@Piane added small explanation why this approach works (and async-await)Jokjakarta
It seems that implicit operators are ignored altogether when returning from non-asyc tasks with Task.FromResult. And this is as expected as the result of the FromResult is generic and not vice versa. This has indeed nothing to do with any kind of variance.Piane
B
1

In the case of an ambiguous return value, you can specify what the type of Task should be.

...
public Task<Results<Ok, NotFound>> ReceiveCommand(ICommand command)
{
    ....

    if (condition) return Task.FromResult<Results<Ok, NotFound>>(TypedResults.NotFound());

    return Task.FromResult<Results<Ok, NotFound>>(TypedResults.Ok());
}
Bramble answered 13/12, 2023 at 14:55 Comment(2)
As I wrote at the end: this is working, for sure, but it is ugly.Piane
Beauty is in the eye of the beholder. You could always go with your own suggestion of await Task.CompletedTaskBramble

© 2022 - 2024 — McMap. All rights reserved.