问题
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