[Proposal] System.Void as a first-class type #1603
Replies: 20 comments
-
I strongly back this proposal. This becomes very prominent when working with async code as Task vs |
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
-
Also a literal duplicate of dotnet/roslyn#234. |
Beta Was this translation helpful? Give feedback.
-
There are numerous conversations here as to why The duplicate delegate situation has been mentioned numerous times, but neither this proposal nor any of the other proposals could solve that. The IL of the method body has to be constant regardless of the generic type arguments, and that's not possible if the method alternately pushes or doesn't push a result onto the stack as the return value. |
Beta Was this translation helpful? Give feedback.
-
I'm going to fall back to #696 for conversation. |
Beta Was this translation helpful? Give feedback.
-
I'm just going to mention #1604 for completeness |
Beta Was this translation helpful? Give feedback.
-
If void arg = default; // or what does "the void value" look like?
Func<void> action = a => Console.WriteLine(a); // or would () be allowed for the parameter list?
action(arg); // or should the arg list be empty? I agree that the delegate type duplication ( I believe .NET and the whole ecosystem has come much too far for this proposal to go through only now. I believe at this point, adding PS: The "delegate duplicates" problem wouldn't be completely solved for library authors even if the |
Beta Was this translation helpful? Give feedback.
-
That makes sense, System.Unit in netstandard2.1
…On Mon, Jun 4, 2018 at 12:53 PM stakx ***@***.***> wrote:
If void were to become a first-class type (against which the CLI standard
currently has rules, btw.) you would have to be able to declare a void
variable. Do we really want to be able to do this? And what would that look
like?
void arg = default; // or what does "the void value" look like?Func<void> action = a => Console.WriteLine(a); // or would () be allowed for the parameter list?action(arg); // or should the arg list be empty?
I agree that the delegate type duplication (Action vs. Func) can be
annoying and cause code duplication. Introducing an empty System.Unitstruct
would be mostly sufficient to mitigate this problem, and not require us to
solve the above (and further) language design problems.
I believe .NET and the whole ecosystem has come much too far for this
proposal to go through only now. I believe at this point, adding
System.Unit to the BCL would be more reasonable.
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<https://github.com/dotnet/csharplang/issues/1603#issuecomment-394442298>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAGYMmxMfD6umoL06SaJP2fbRXUOKsCiks5t5XQZgaJpZM4UZQK8>
.
|
Beta Was this translation helpful? Give feedback.
-
In your example, why not just use @stakx Well they could make it abstract, but then it couldn't be sealed, and we don't want any |
Beta Was this translation helpful? Give feedback.
-
There are too many overloads of Task ContinueWith(Action<Task<TResult>>)
Task ContinueWith(Action<Task<TResult>>, CancellationToken)
Task ContinueWith(Action<Task<TResult>>, TaskScheduler)
Task ContinueWith(Action<Task<TResult>>, TaskContinuationOptions)
Task ContinueWith(Action<Task<TResult>>, CancellationToken, TaskContinuationOptions, TaskScheduler)
Task ContinueWith(Action<Task<TResult>,Object>, Object)
Task ContinueWith(Action<Task<TResult>,Object>, Object, CancellationToken)
Task ContinueWith(Action<Task<TResult>,Object>, Object, TaskScheduler)
Task ContinueWith(Action<Task<TResult>,Object>, Object, TaskContinuationOptions)
Task ContinueWith(Action<Task<TResult>,Object>, Object, CancellationToken, TaskContinuationOptions, TaskScheduler)
Task<TNewResult> ContinueWith<TNewResult>(Func<Task<TResult>,TNewResult>)
Task<TNewResult> ContinueWith<TNewResult>(Func<Task<TResult>,TNewResult>, CancellationToken)
Task<TNewResult> ContinueWith<TNewResult>(Func<Task<TResult>,TNewResult>, TaskScheduler)
Task<TNewResult> ContinueWith<TNewResult>(Func<Task<TResult>,TNewResult>, TaskContinuationOptions)
Task<TNewResult> ContinueWith<TNewResult>(Func<Task<TResult>,TNewResult>, CancellationToken, TaskContinuationOptions, TaskScheduler)
Task<TNewResult> ContinueWith<TNewResult>(Func<Task<TResult>,Object,TNewResult>, Object)
Task<TNewResult> ContinueWith<TNewResult>(Func<Task<TResult>,Object,TNewResult>, Object, CancellationToken)
Task<TNewResult> ContinueWith<TNewResult>(Func<Task<TResult>,Object,TNewResult>, Object, TaskScheduler)
Task<TNewResult> ContinueWith<TNewResult>(Func<Task<TResult>,Object,TNewResult>, Object, TaskContinuationOptions)
Task<TNewResult> ContinueWith<TNewResult>(Func<Task<TResult>,Object,TNewResult>, Object, CancellationToken, TaskContinuationOptions, TaskScheduler)
Task ContinueWith(Action<Task>)
Task ContinueWith(Action<Task>, CancellationToken)
Task ContinueWith(Action<Task>, TaskScheduler)
Task ContinueWith(Action<Task>, TaskContinuationOptions)
Task ContinueWith(Action<Task>, CancellationToken, TaskContinuationOptions, TaskScheduler)
Task ContinueWith(Action<Task,Object>, Object)
Task ContinueWith(Action<Task,Object>, Object, CancellationToken)
Task ContinueWith(Action<Task,Object>, Object, TaskScheduler)
Task ContinueWith(Action<Task,Object>, Object, TaskContinuationOptions)
Task ContinueWith(Action<Task,Object>, Object, CancellationToken, TaskContinuationOptions, TaskScheduler)
Task<TResult> ContinueWith<TResult>(Func<Task,TResult>)
Task<TResult> ContinueWith<TResult>(Func<Task,TResult>, CancellationToken)
Task<TResult> ContinueWith<TResult>(Func<Task,TResult>, TaskScheduler)
Task<TResult> ContinueWith<TResult>(Func<Task,TResult>, TaskContinuationOptions)
Task<TResult> ContinueWith<TResult>(Func<Task,TResult>, CancellationToken, TaskContinuationOptions, TaskScheduler)
Task<TResult> ContinueWith<TResult>(Func<Task,Object,TResult>, Object)
Task<TResult> ContinueWith<TResult>(Func<Task,Object,TResult>, Object, CancellationToken)
Task<TResult> ContinueWith<TResult>(Func<Task,Object,TResult>, Object, TaskScheduler)
Task<TResult> ContinueWith<TResult>(Func<Task,Object,TResult>, Object, TaskContinuationOptions)
Task<TResult> ContinueWith<TResult>(Func<Task,Object,TResult>, Object, CancellationToken, TaskContinuationOptions, TaskScheduler) If we have a unit type, only quarter of these are needed. We can replace |
Beta Was this translation helpful? Give feedback.
-
Totally agree that reducing redundancy would be nice, but what are you going to do about all the existing code that has been compiled against the redundant overloads? Is removing any overloads at this point feasible? |
Beta Was this translation helpful? Give feedback.
-
I often write similar codes, especially extension methods to |
Beta Was this translation helpful? Give feedback.
-
So what would be the benefit of using |
Beta Was this translation helpful? Give feedback.
-
The benefit is to designers, and some edge cases where your code has to slightly change to handle returns vs no return, when otherwise the code could have been shared. In an API you could then have a single overload instead of two, one for action and one for Func of TResult, instead you just one for Func of TResult. |
Beta Was this translation helpful? Give feedback.
-
and what is worse, the number of overloads are exponential. static class TaskTupleExtensions
{
// combination of 2 tasks = 4
public static TaskAwaiter GetAwaiter<T1, T2>((Task<T1>, Task<T2>) t) ...;
public static TaskAwaiter GetAwaiter<T1>((Task<T1>, Task) t) ...;
public static TaskAwaiter GetAwaiter<T2>((Task, Task<T2>) t) ...;
public static TaskAwaiter GetAwaiter((Task, Task) t) ...;
// combination of 3 tasks = 8
public static TaskAwaiter GetAwaiter<T1, T2, T3>((Task<T1>, Task<T2>, Task<T3>) t) ...;
public static TaskAwaiter GetAwaiter<T1, T2>((Task<T1>, Task<T2>, Task) t) ...;
public static TaskAwaiter GetAwaiter<T1, T3>((Task<T1>, Task, Task<T3>) t) ...;
public static TaskAwaiter GetAwaiter<T2, T3>((Task, Task<T2>, Task<T3>) t) ...;
public static TaskAwaiter GetAwaiter<T1>((Task<T1>, Task, Task) t) ...;
public static TaskAwaiter GetAwaiter<T2>((Task, Task<T2>, Task) t) ...;
public static TaskAwaiter GetAwaiter<T3>((Task, Task, Task<T3>) t) ...;
public static TaskAwaiter GetAwaiter((Task, Task, Task) t) ...;
// combination of n tasks = 2^n
} |
Beta Was this translation helpful? Give feedback.
-
@psibernetic Okay, that was my guess. @ufcpp What would that code look like with |
Beta Was this translation helpful? Give feedback.
-
@ufcpp But since It's not like there's a reasonable way to get a tuple-resulting awaiter back out as a result (since we don't have |
Beta Was this translation helpful? Give feedback.
-
This is just simpified version. To get results, full version is: public static TupleTaskAwaiter<T1, T2> GetAwaiter<T1, T2>((Task<T1>, Task<T2>) t) ...;
public static TupleTask1Awaiter<T1> GetAwaiter<T1>((Task<T1>, Task) t) ...;
public static TupleTask2Awaiter<T2> GetAwaiter<T2>((Task, Task<T2>) t) ...;
public static TupleTaskAwaiter GetAwaiter((Task, Task) t) ...; |
Beta Was this translation helpful? Give feedback.
-
These I disagree with: public static TupleTask1Awaiter<T1> GetAwaiter<T1>((Task<T1>, Task) t) ...;
public static TupleTask2Awaiter<T2> GetAwaiter<T2>((Task, Task<T2>) t) ...; I don't think you should be getting a result if any of the things in the tuple has no result. |
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Today, you cannot use
System.Void
as a first-class type. That is, it is illegal in C# to do:This gives you the compiler error:
error CS0673: System.Void cannot be used from C# -- use typeof(void) to get the void type object
In other languages and frameworks, there exists the concept of the
Unit
type (F# and Rx). The lack of a true void type means duplication of delegates -Func
andAction
, and typically API duplication to handle things that return or things that don't return. This means overloaded types, duplicate methods, helper classes and more, even though the API itself does not care if the underlying delegates return values or not.Beta Was this translation helpful? Give feedback.
All reactions