Switch statement with non-constant-expression - Extends C#/IDE ability
Asked Answered
C

4

6

Before you start criticizing and pointing me §8.7.2 of C# specification, read carefully :)

We all know how switch looks like in C#. Ok so consider the class MainWindow with "nasty" Bar method

static int barCounter = 0;
public static int Bar()
{
    return ++barCounter;
}

Somewhere in this class we have code like this

Action switchCode = () =>
{
    switch (Bar())
    {
        case 1:
            Console.WriteLine("First");
            break;
        case 2:
            Console.WriteLine("Second");
            break;
    }
};

switchCode();

switchCode();

In the console window we will see

First
Second

Using Expressions in C# we could do the same – writing much same code

var switchValue = Expression.Call(typeof(MainWindow).GetMethod("Bar"));

var WriteLine = typeof(Console).GetMethod("WriteLine", new Type[] { typeof(String) });

var @switch = Expression.Switch(switchValue,
    Expression.SwitchCase(
        Expression.Call(WriteLine, Expression.Constant("First")),
        Expression.Constant(1)
        ),
    Expression.SwitchCase(
        Expression.Call(WriteLine, Expression.Constant("Second")),
        Expression.Constant(2)
        )
    );

Action switchCode = Expression.Lambda<Action>(@switch).Compile();

switchCode();

switchCode();

In DebugView we could see "code behind" this expression

.Switch (.Call WpfApplication1.MainWindow.Bar()) {
.Case (1):
        .Call System.Console.WriteLine("First")
.Case (2):
        .Call System.Console.WriteLine("Second")
}

Ehmm, what if we use Expression.Call instead Expression.Constant?

public static bool foo1() { return false; }

public static bool foo2() { return true; }

// .....

var foo1 = Ex.Call(typeof(MainWindow).GetMethod("foo1"));
var foo2 = Ex.Call(typeof(MainWindow).GetMethod("foo2"));
var switchValue = Ex.Call(typeof(MainWindow).GetMethod("Bar"));

var WriteLine = typeof(Console).GetMethod("WriteLine", new Type[] { typeof(String) });

var @switch = Ex.Switch(Ex.Constant(true),
    Ex.SwitchCase(
        Ex.Call(WriteLine, Ex.Constant("First")),
        foo1
        ),
    Ex.SwitchCase(
        Ex.Call(WriteLine, Ex.Constant("OK!")),
        Ex.Equal(switchValue, Ex.Constant(2))
        ),
    Ex.SwitchCase(
        Ex.Call(WriteLine, Ex.Constant("Second")),
        foo2
        )
    );

Action switchCode = Ex.Lambda<Action>(@switch).Compile();

switchCode();

switchCode();

Console window shows, as we expected

Second
OK!

And DebugView

.Switch (True) {
.Case (.Call WpfApplication1.MainWindow.foo1()):
        .Call System.Console.WriteLine("First")
.Case (.Call WpfApplication1.MainWindow.Bar() == 2):
        .Call System.Console.WriteLine("OK!")
.Case (.Call WpfApplication1.MainWindow.foo2()):
        .Call System.Console.WriteLine("Second")
}

So it is possible to use non-constant expression in case-statement :)

Ok, I known this is little ‘messy’ code. But here comes my question (finally :P):
Is there any way to extends functionality of IDE/VisualStudio/compiler to do this, but with more elegant code?
Something like this

switch (true)
{
    case foo1():
        Console.WriteLine("First");
        break;
    case Bar() == 2:
        Console.WriteLine("OK!");
        break;
    case foo2():
        Console.WriteLine("Second");
        break;
}

I know this will be some extension and code will be not the same (not the same performance). But I wonder if this is even possible to "change" code on-the-fly – like anonymous function or yield return is transform to nested class.

I hope someone goes through the above text and leave some clue.

Citole answered 26/3, 2012 at 21:3 Comment(8)
Seems like a whole lot of effort to solve a problem that doesn't exist. Switches can offer constant lookup time. This is a great thing in scenarios where you have a large number of possible values. Just use a series of if/else if's and move on to solving real problems (or use a language which favors expressiveness over performance across the board).Humankind
@EdS. You think that I don’t known about <code>if/else if</code>? This is more “challenge” how to do this, not problem “how can I use bunch of boolean-condition-statement”. In fact I used PHP and JavaScript, there is something like above a lot. If you want some real problem: https://mcmap.net/q/1780158/-wpf-datagrid-handle-keys-and-no-data/1245315Citole
No, I think you are wasting your time and not using it to solve real problems. Yes, there are languages that can do what you propose, they also cannot offer constant time lookup. You only have a finite amount of time in this life, my advice is to not squander it on stuff like this that has no appreciable benefit to anyone. A programming language is just a tool, not a goal unto itself. As for your other question... no thanks, UI quirks don't interest me, which is why I don't build UI's for a living :)Humankind
@EdS.: I disagree that time spent thinking about programming languages is time wasted. There are lots of interesting transformations one can perform on code and there is value in pursuing even those that don't pan out into something useful. That's why we do research. More specifically, I note that you are correct to say that switches can offer constant time behaviour. I note that they are not guaranteed to do so; switches may in practice be generated using a logarithmic or a linear algorithm.Aseptic
@EricLippert: Of course, I agree in general, just not in this case. I was actually going to mention that, even though I think this is a solution trying to find a problem, the act of investigating an extension to C# is in general an interesting one. And yeah... made sure to say "can" :)Humankind
The problem I have is that this solves nothing, it does not give us a way to do something we could not before.Humankind
@EdS., many (most?) of the features of C# added since C# 2 do not give you anything you couldn't do before. They just make certain things easier.Greyback
@svick: I'm not sure what this makes easier though. Is it really easier to write switch(x) { case y + 1: case y + 2:} than if(x == y + 1) { } else if(x == y + 2) { }?Humankind
G
3

In general, there are no extension points in the Microsoft C# compiler as far as I know (and even Roslyn isn't planning to change that). But nothing is stopping you from writing your own C# compiler, or, more realistically, modifying the open source Mono C# compiler.

In any case, I think it's much more trouble than what it's worth.

But maybe you can do what you want using something that's already in the language, namely, lambdas and method calls, to form a “fluent switch”:

Switch.Value(true)
    .Case(() => Foo(), () => Console.WriteLine("foo"))
    .Case(() => Bar() == 2, () => Console.WriteLine("bar == 2"));

If you don't mind that all the condition values will be evaluated every time, you simplify that a bit:

Switch.Value(true)
    .Case(Foo(), () => Console.WriteLine("foo"))
    .Case(Bar() == 2, () => Console.WriteLine("bar == 2"));
Greyback answered 28/3, 2012 at 9:49 Comment(0)
T
1

No, it's not possible, nor that I'm aware of. It's already kind of miracle that you can use a string inside switch statement (reference type with immutable behavior). For these kind of cases just use if, if/else, if/elseif combinations.

Tubercle answered 26/3, 2012 at 21:9 Comment(2)
While I completely agree with your answer, I have no idea why it's a miracle to use strings inside switch, because what you are actually using is a compile-time constant (literal string value).Jinni
For example in C++ you can not use a string in switch statement. The notion of a type, clear comparison ability is something that comes in C# "for free". That is, my point was just to note the goodness that .Net bring to us.Tubercle
P
1

There aren't currently extensions that do that sort of thing. Although it's worth pointing out that MS SQL allows for exactly what you are looking for

SELECT
  Column1, Column2,
  CASE
    WHEN SomeCondition THEN Column3
    WHEN SomeOtherCondition THEN Column4
  END AS CustomColumn
FROM ...

The problem with this becomes understanding precedence; what happens when both conditions are true? In SQL the case statement returns the value from the first statement that is true, and ignores other cases, but that behavior might not be what you wanted.

The strength of C# is that it's impossible to code switches in such a way that both case 1 and case 2 can be true at the same time, so you are guaranteed only one correct answer.

Piker answered 26/3, 2012 at 21:32 Comment(1)
+1 for pointing out that two case labels cannot have the same valueUpsydaisy
J
0

As we all know "Switches" exposes a similar functionality to an if. Most of us (myself include) see it as a syntax sugar -- it's easier to read a bunch of cases on a certain switch then read through a number of if/else if/.../else. But the fact is that switch is no syntax sugar.

What you must realize is that the code generated for switch (be it IL or machine code) is not the same as the code generated for sequential ifs. Switch has a nice optimization which, as @Ed S. already pointed out, enables it to run in a constant time.

Jinni answered 26/3, 2012 at 21:32 Comment(2)
Switches can run in constant time sometimes. Switches are absolutely not guaranteed to run in constant time, and in practice they frequently do not. There are a great many factors that can cause a switch to run in logarithmic or linear time.Aseptic
@EricLippert, I was looking at the IL generated by a switch statement and found that the optimizations in my mind aren't really there -- I started to wonder about what the OP was asking -- it made sense. May I suggest a blog post about this matter? :)Jinni

© 2022 - 2024 — McMap. All rights reserved.