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

早过忘川 提交于 2020-12-29 13:10:23

问题


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.


回答1:


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"));



回答2:


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.




回答3:


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.




回答4:


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.



来源:https://stackoverflow.com/questions/9879849/switch-statement-with-non-constant-expression-extends-c-ide-ability

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!