使用非常量表达式切换语句 - 扩展C#/ IDE能力

时间:2012-03-26 21:03:50

标签: c# .net compiler-construction expression switch-statement

在你批评并指出C# specification的§8.7.2之前,请仔细阅读:)

我们都知道C#中的switch是怎样的。好的,请考虑使用“讨厌的” Bar 方法

的班级MainWindow
static int barCounter = 0;
public static int Bar()
{
    return ++barCounter;
}

在这个课程的某个地方我们有这样的代码

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

switchCode();

switchCode();

在控制台窗口中,我们将看到

First
Second

在C#中使用表达式我们也可以这样做 - 编写相同的代码

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

在DebugView中,我们可以看到“这个表达式背后的代码”

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

嗯,如果我们使用Expression.Call代替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();

控制台窗口显示,正如我们预期的那样

Second
OK!

和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")
}

因此可以在case语句中使用非常量表达式:)

好的,我知道这是一个很“混乱”的代码。但这是我的问题(最后:P):
有没有办法扩展IDE / VisualStudio /编译器的功能来实现这一点,但有更优雅的代码?
像这样的东西

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

我知道这将是一些扩展,代码将不一样(性能不一样)。但我想知道这是否有可能即时“改变”代码 - 就像匿名函数或yield return一样转换为嵌套类。

我希望有人通过上述文字并留下一些线索。

4 个答案:

答案 0 :(得分:2)

一般来说,据我所知,Microsoft C#编译器中没有扩展点(甚至Roslyn也不打算改变它)。但是没有什么可以阻止你编写自己的C#编译器,或者更实际地,修改开源Mono C# compiler

无论如何,我认为这比它的价值要麻烦得多。

但也许你可以用语言中的东西,即lambdas和方法调用来做你想做的事情,以形成一个“流畅的开关”:

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

如果您不介意每次都会评估所有条件值,那么您可以简化一下:

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

答案 1 :(得分:1)

不,这是不可能的,也不是我所知道的。它已经是奇迹,您可以在string内部使用switch语句(具有不可变行为的引用类型)。对于这类情况,只需使用ifif/elseif/elseif组合。

答案 2 :(得分:1)

目前没有扩展程序可以执行此类操作。虽然值得指出的是MS SQL可以准确地提供您正在寻找的内容

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

这个问题变成了理解的优先顺​​序;当两个条件都成立时会发生什么?在SQL中,case语句返回第一个语句为true的值,并忽略其他情况,但该行为可能不是您想要的

C#的优势在于不可能以这样的方式对开关进行编码,即情况1和情况2都可以同时为真,因此只保证一个正确的答案。

答案 3 :(得分:0)

众所周知,“开关”公开了与if类似的功能。我们大多数人(我自己包括)将其视为语法糖 - 在某个switch上阅读大量案例更容易,然后通过if / else if / else if / else读取。但事实是,切换不是语法糖。

您必须意识到,为switch(无论是IL还是机器代码)生成的代码与为顺序ifs生成的代码不同。 Switch有一个很好的优化,正如@Ed S.已经指出的那样,它能够在一个恒定的时间内运行。