-
Notifications
You must be signed in to change notification settings - Fork 1k
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Proposal: Enhanced switch statements #3038
Comments
I like. It may also be a step towards making block expressions, which I personally would prefer over sequence expressions as they have been proposed. For reference, here's the current preview
// as statement
switch (p) {
case 1 ,2, 3 -> System.out.println("Foo");
case 4, 5, 6 -> {
System.out.println("Bar");
}
};
// as expression
String result = switch (p) {
case 1 ,2, 3 -> "Foo";
case 4, 5, 6 -> {
yield "Bar";
}
}; |
I'd really prefer this approach and keep the switch expression as is.
Ideally, I'd love to mix them but because they differ syntactically and have different rules applied to them then I'd go with all-or-nothing.
This can be automated. |
It can be automated, but it's a very ungraceful process if you find yourself in the scenario where you need multiple |
See also the discussion in ldm around making the existing switch expression an expression statement: https://github.com/dotnet/csharplang/blob/master/meetings/2019/LDM-2019-12-16.md#switch-expression-as-a-statement-expression. |
@333fred It's very similar to LINQ where at times you need to transform query expression into a method syntax and sometimes even into a loop statement. |
I'm not saying there isn't precedent, I'm just saying that it's a very ungraceful degredation story.
An important note is that the differing syntax right now is due to my desire to start the discussion with exhaustive vs non-exhaustive in ldm. After we have discussion, if we come down on the non-exhaustive side my plan is to modify the syntax to be much more similar to the existing case labels, and that becomes less of a concern. |
I personally find it quite confusing, when reading the example code, I just thought of it as switch expression. I suggest we retain the |
@333fred , seems that mixing of value and type matching at the one level is not a good practice (may breaks possible compiler optimizations related with preffered type matching). Possible, there is a sense to split value and type matching expressions into two kinds of switch (Java style for values, true lambda style for types). Indeed, the following code very closely with your sample may be successfully compiled and run. Check how into dotnetfiddle.net online compiler o.Switch
(
(int i) => Console.WriteLine($"o is int {i}"),
(string s) => Console.WriteLine($"o is string {s}"),
(string[] l) => {
Console.WriteLine("o is an array of strings:");
foreach (var s in l)
{
Console.WriteLine($"\t{s}");
}
}
); var result = o.Match
(
(int i) => $"o is int {i}",
(string s) => $"o is string {s}",
(string[] l) => {
var result = "o is an array of strings:\n";
foreach (var s in l)
{
result += $"\t{s}";
}
return result;
}
); |
That example falls apart when combined with all of the various kinds of patterns supported in C# 8.0, not to mention the delegate invocation adds non-trivial overhead and additional allocations. |
@0x000000EF I have no idea what you're actually saying here. This proposal changes nothing with regards to how we emit code in any fashion, it only changes syntax and some variable lifetime rules. Everything in my examples are completely possible in switch statements today, just with a different syntax. |
@333fred , a false alarm. More year ago C# compiler had a problem. switch (o)
{
case 1: return "one";
case 2: return "two";
case int i when i > 2: return "too many";
case int i: return "too few";
case "abc": return "ABC";
case "xyz": return "zyx";
case string s: return default;
case null: return "is null";
default: return default;
} it had generated optimal code something like if (o is int) // type matching
{
… // value matchings
}
if (o is string) // type matching
{
… // value matchings
} But simple reordering of cases switch (o)
{
case 1: return "one";
case 2: return "two";
case "abc": return "ABC";
case "xyz": return "zyx";
case int i when i > 2: return "too many";
case int i: return "too few";
case string s: return default;
case null: return "is null";
default: return default;
} had caused non-optimal Currently I checked compiler output and seems that this problem has been fixed and the compiler automatically perform reordering for second case. Ok, there is not the problem anymore. |
Wait so did my comment and suggestions on #2636 make it into this (as it was recently closed), and if not, can it be? It is referring to being able to include block bodies for "expressional" switch branches in switch expressions as well, not just switch statements, via having another |
The area is pretty open for now, and the LDT is definitely considering whether blocks should be just on the statement form, on the expression form, or in general as expression blocks be allowed everywhere. |
Switch expression enhancements are covered by #3037. |
Lets make it like in Zig and make everything an expression:
|
Oof, removed from C# 10 plans... Any chance it'll be considered for the next version? |
The proposal says "Prototype: Complete" at the top. Is that accurate? Is there a feature branch where a prototype lives? |
It is not complete. |
Meh. This really doesn't add that much value. |
Cheer up, I like it very much! Please implement it as soon as possible! The old switch statement is too annoying, especially the grammatical rules of variables declaration scope, I always need to add parentheses to create local scope. The break keyword is a bit redundant, too. |
Maybe it would be better to use ";" instead of "," as the separator? var o = ...;
switch (o)
{
1 => Console.WriteLine("o is 1");
string s => Console.WriteLine($"o is string {s}");
List<string> l => {
Console.WriteLine("o is a list of strings:");
foreach (var s in l)
{
Console.WriteLine($"\t{s}");
}
}
} switch_block
: '{' switch_section* '}'
| '{' switch_statement_arms ';'? '}'
;
switch_statement_arms
: switch_statement_arm
| switch_statement_arms ';' switch_statement_arm
;
switch_statement_arm
: pattern case_guard? '=>' statement_expression
| pattern case_guard? '=>' block
; |
Any updates on this? We're currently using lambda helpers to replace this: switch( authIdentity ){
case AuthIdentity.PhoneNumberIdentity phoneNumberIdentity:
installation.SetPreAuthContextPhoneNumber( phoneNumberIdentity.PhoneNumber );
break;
case AuthIdentity.MessengerIdentity messengerIdentity:
installation.SetPreAuthContextMessengerPageScopedID( messengerIdentity.PageScopedID );
break;
default:
throw new NotSupportedException( $"Unknown auth identity {typeof(AuthIdentity)}" ),
} with this: object _ = authIdentity switch {
AuthIdentity.PhoneNumberIdentity phoneNumberIdentity => Do( () => {
installation.SetPreAuthContextPhoneNumber( phoneNumberIdentity.PhoneNumber );
}),
AuthIdentity.MessengerIdentity messengerIdentity => Do( () => {
installation.SetPreAuthContextMessengerPageScopedID( messengerIdentity.PageScopedID );
}),
_ => throw new NotSupportedException( $"Unknown auth identity {typeof(AuthIdentity)}" ),
} This uses the following lambda helper (which is useful in all sorts of other contexts): /// <summary>
/// Performs the given action, returning the 'no-op' result (fundamental C# limitation).
/// </summary>
/// <param name="action"></param>
/// <returns>NOTE: This object represents 'Void' – containing a "no result' result</returns>
[DebuggerStepThrough]
public static /*void*/object Do( Action action ){
action();
return new object();
} There is some ugliness needed with the It would be fantastic having this as out-of-box language support. It's not only used for multi-line statements, but even single-line statements that invoke different logic/methods, as shown in the example. |
Once again I think Rust can also be a great inspiration for other languages. What I am talking about: Read It shows how a language can just have 1 single kind of construct for everything. However, in Rust the last line where you leave the semicolon open is the one which yields a value, which I would not want to suggest for C# where as it is not idiomatic. I would just say the |
Is there any update on this? As @eduarddejong mentions, there are other languages that have none of these issues, where they either require everything to have a return value, or some kind of (or lack of) identifier to signal the return value. The |
@rutgersc When updates happen, these issues are updated. No need to ask :) |
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
Enhanced switch statments
Summary
This proposal is an enhancement to the existing switch statement syntax to permit switch-expression-style arms instead of using case statements. This feature is orthogonal to #3037 and can be done independently, but was conceived of as taking #3037 switch expressions and applying them to switch statements.
Motivation
This is an attempt to bring more recent C# design around switch expressions into a statement form, that can be used when there is nothing to return. The existing switch statement construct, while very familiar to C/C++ programmers, has several design choices that can feel dated by current C# standards. These include requiring
break;
statements in each case, even though there is no implicit fall-through in C#, and variable scopes that extend across all cases of the switch statement for variables declared in the body, but not for variables declared in patterns in the case labels. We attempt to modernize this by providing an alternate syntax based on the switch expression syntax added in C# 8.0 and improved with the first part of this proposal.Detailed design
We enhance the grammar of switch statements, creating an alternate form based on the grammar of switch expressions. This alternate form is not compatible with the existing form of switch statements: you must use either the new form or the old form, but not both.
We make the following changes to the grammar:
Unlike a switch expression, an enhanced switch statement does not require the switch_statement_arms to have a best common type or to be target-typed. Whether the arm conditions have to be exhaustive like a switch expression is currently an open design question. Block-bodied statement arms are not required to have the end of the block be unreachable, and while a
break;
in the body will exit the switch arm, it is not required at the end of the block like in traditional switch statements.Drawbacks
As with any proposals, we will be complicating the language further by doing these proposals. In particular, we will be adding another syntactic form to switch statements that has some very different semantics to existing forms.
Alternatives
#2632: The original issue proposed that we allow C# 8.0 switch expressions as
expression_statement
s. We had a few initial problems with this proposal:Unresolved questions
Should enhanced switch statement arms be an all-or-nothing choice? ie, should you be able to use an old-style
case
label and a new-style arrow arm in the same statement? This proposal takes the opinion that this should be an exclusive choice: you use one or the other. This enables a debate about the second unresolved question for enhanced switch, whether they should be exhaustive. If we decide that enhanced switch should not be exhaustive, then this debate becomes largely a syntactic question.when
clauses with fallthrough, like you can with traditional switch statements today. If this is an all-or-nothing choice, this means that the moment you need multiple when clauses you fall off the rails and must convert the whole thing back to a traditional switch statement.Should enhanced switch be exhaustive? Switch expressions, where enhanced switch statements are inspired from, are exhaustive. However, while the exhaustivity makes sense in an expression context where something must be returned in all cases, this makes less sense in a statement context. Until we get discriminated unions in the language, the only times we can be truly exhaustive in C# is when operating on a closed type heirarchy that includes a catch-all case, or we are operating on a value type. And if the user is required to add a catch-all do nothing case, then purpose of exhaustivity has been largely obviated.
Design meetings
https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-08-31.md#switch-expression-as-a-statement
https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-09-26.md#discriminated-unions
The text was updated successfully, but these errors were encountered: