dotnet / roslyn

The Roslyn .NET compiler provides C# and Visual Basic languages with rich code analysis APIs.
https://docs.microsoft.com/dotnet/csharp/roslyn-sdk/
MIT License
19.11k stars 4.04k forks source link

Proposal: allow comparison operators in switch case labels C# #11312

Closed JFMous closed 7 years ago

JFMous commented 8 years ago

It would be nice if the case labels in switch statement supported comparison operators, much like the Select Case statement in Visual Basic does.

Now, there are only two options for the switch labels:

switch-label: case constant-expression : default :

This could be extended to:

case [< | > | <= | >=] constant-expression [ to ] constant-expression : default :

So you could write code as in this example:

int iq = DoIqTest();

switch (iq)
{
    case <= 69:
        ProcedureExtremelyLow();
        break;
    case 70 to 79:
        BorderlineProcedure();
        break;
    case 80 to 89:
        LowAverageProcedure();
        break;
    case 90 to 99:
    case 101 to 109:
        AverageProcedure();
        break;
    case 100:
        ExactlyMedianProcedure();
        break;
    case 110 to 119:
        HighAverageProcedure();
        break;
    case 120 to 129:
         SuperiorProcedure();
         break;
    case >= 130:
        VerySuperiorProcedure();
        break;
}

The 'to' keyword would be used to specify a range. In the statement switch (value), case x to y: would be equivalent to the boolean expression value >= x && value <= y

HaloFour commented 8 years ago

Case guards in pattern matching #206 will open this up to allowing for arbitrary conditions:

int iq = DoIqTest();

switch (iq)
{
    case * when iq <= 69:
        ProcedureExtremelyLow();
        break;
    case * when iq <= 79:
        BorderlineProcedure();
        break;
    case * when iq <= 89
        LowAverageProcedure();
        break;
    case * when iq == 100:
        ExactlyMedianProcedure();
        break;
    case * when iq <= 109:
        AverageProcedure();
        break;
    case * when iq <= 119:
        HighAverageProcedure();
        break;
    case * when iq <= 129:
         SuperiorProcedure();
         break;
    default:
        VerySuperiorProcedure();
        break;
}

The wildcard pattern would match on anything but then the case guard would contain the specific conditions. They would be evaluated in lexical order.

alrz commented 8 years ago

would it make sense to make case optional or perhaps switch expression?

switch {
  when id <= 69: ... break;
  ...
}

might be preferrable over if else.

bondsbw commented 8 years ago

@alrz I like that somewhat, feels like a reasonable simplification.

But I feel this may be even more useful in pattern matching. In match expressions, the roughly equivalent form might be:

var x = match
{
    when iq <= 69: ProcedureExtremelyLow(),
    when iq <= 79: BorderlineProcedure(),
    when iq <= 89: LowAverageProcedure(),
    when iq <= 99 || (iq <= 109 && iq >= 101): AverageProcedure(),
    when iq == 100: ExactlyMedianProcedure(),
    when iq <= 119: HighAverageProcedure(),
    when iq <= 129: SuperiorProcedure(),
    default: VerySuperiorProcedure()
};

I like the possibilities but if iq is always the comparison, it really feels like it should be the subject of the match expression. Something like the following feels more like pattern matching (although it would require a bit of rethinking what a relational expression is):

var x = iq match
{
    when <= 69: ProcedureExtremelyLow(),
    when <= 79: BorderlineProcedure(),
    when <= 89: LowAverageProcedure(),
    when == 100: ExactlyMedianProcedure(),
    when <= 109: AverageProcedure(),
    when <= 119: HighAverageProcedure(),
    when <= 129: SuperiorProcedure(),
    default: VerySuperiorProcedure()
};

And I'm not sure I like changing

when iq <= 99 || (iq <= 109 && iq >= 101):

into something like

when <= 99 || (<= 109 && >= 101):
Unknown6656 commented 8 years ago

@bondsbw, @JFMous : I would like to see something like the following implemented with the match-pattern:

Func<int, string> func1, func2, func3, ...;

int iq = ...;
string result = iq match
{
    when <= 100: func1,
    when == 100: func2,
    when >= 100: func3,
    // ....
    default: funcx()
}(iq);

Meaning, the possibility to use the match-statement inside of expressions.

HaloFour commented 8 years ago

@Unknown6656 The proposal is that match is an expression, so of course you'd be able to use it within other expressions. You can think of it like a ternary op on steroids.

As for your specific example, there have been no proposed range patterns. Nor has there been a proposal to allow for only case guards with an implied wildcard pattern. So as of now your example would be:

string result = iq match (
    case * when iq < 100: func1,
    case * when iq == 100: func2,
    case * when iq > 100: func3,
    case *: funcx
)(iq);
gafter commented 7 years ago

Issue moved to dotnet/csharplang #812 via ZenHub