This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
Permalink
Show file tree
Hide file tree
3 comments
on commit
sign in to comment.
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Remove implicit casts from ValueTask<T>
The C# team is considering adding a feature to C# that would allow for arbitrary types to be used as the return type from async methods, with ValueTask<T> being a key motivator. As part of this, they're considering overload resolution in a situation like: ```C# async Task Method(Func<Task<int>> f); ... Method(async () => { await Blah(); return 3; }); ``` where a developer previously had a method that accepted a ```Func<Task<int>>```, and then with ```ValueTask<T>``` adds an overload like: ```C# async Task Method(Func<ValueTask<int>> f); ``` Presumably the ```ValueTask<int>``` overload should be preferred in this case. However, if a developer added another overload: ```C# async Task Method(Func<IAsyncOperation<int>> f); ``` (assuming IAsyncOperation could be used in a return position), we would not want that one preferred. Thus, if this situation should not be considered ambiguous and require the developer to cast/type at the call site to specify which overload should be used (which might be the right answer), there would need to be some tie-breaking mechanism. The current favorite mechanism of the rest of the C# design team besides me is using implicit conversions as the way to signal this to the compiler, such that if there's an implicit conversion from ```B``` to ```A``` and overloads that take a ```Func<B>``` and ```Func<A>```, ```Func<B>``` would be preferred. For this to work with ```Task<T>``` and ```ValueTask<T>```, ```ValueTask<T>``` would need an implicit cast to ```Task<T>``` and could not have one in the other direction from ```Task<T>``` to ```ValueTask<T>```. Today, ```ValueTask<T>``` has an implicit cast from ```Task<T>``` to ```ValueTask<T>```, just as it has an implicit cast from ```T``` to ```ValueTask<T>```. A ```ValueTask<T>``` is just a wrapper for both a ```T``` or a ```ValueTask<T>``` (it has a field for each), and so this is just a quick way of creating that wrapper from either of the two members that make up the discriminated union. In this direction, no information is lost, there are no heap allocations, etc. In the other direction, implicitly casting a ```ValueTask<T>``` to a ```Task<T>``` may or may not be allocating: if the ```ValueTask<T>``` was created from a ```Task<T>```, then implicitly casting to a ```Task<T>``` would just return the stored task. But if the ```ValueTask<T>``` was created from a value (which is the most common case in scenarios where ```ValueTask<T>``` is used, as it's used in places where you expect synchronous completion and thus to have a value rather than a task), then implicitly casting to a ```Task<T>``` would result in needing to allocate that task as part of the implicit cast, leading to implicit and silent allocations. I very much dislike the idea of adding such an implicit cast from ```ValueTask<T>``` to ```Task<T>```. However, by having the implicit cast in the opposite direction, we shut the door on being able to add such an implicit cast in the future for such overload resolution purposes. Thus, this commit removes the current implicit casts in order to allow us to decide to add back either (or both) directions in the future.
- Loading branch information
Showing
3 changed files
with
45 additions
and
120 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
eecacdc
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @stephentoub.
I'm struggling to understand how ValueTask<T> can be converted from a given T value without the implicit conversions.
When I return an integer from a ValueTask<int> returning method, it works flawlessly, but I don't know how it converts an int into a ValueTask<int> without the operators.
Could you give me an idea what is going on behind the scene?
Thanks in advance.
eecacdc
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The C# compiler translates an async method into a state machine implementation that uses a "builder", and the return statement is actually calling a SetResult(result) value method on the builder.
e.g.
https://sharplab.io/#v2:EYLgtghglgdgPgAQEwEYCwAoBAGABAlAVgG5NMEBmfJXAYVwG9NcX8qEAOXANQgBsArgFMEANgA8sAC4A+XAFkAFAEpGzVhoQBOfKIB0ATShC+AExWkMGzQHZcAFiSWNAX0wugA=
eecacdc
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@stephentoub Clearly explained, thanks!