带有非常量表达式的Switch语句-扩展了C#/IDE的功能

本文关键字:扩展 功能 IDE 语句 非常 常量 表达式 Switch | 更新日期: 2023-09-27 18:29:01

在开始批评和指责我C#规范的§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/VVisualStudio/编译器的功能来做到这一点,但要使用更优雅的代码
像这样的

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

我知道这将是一些扩展,代码将不一样(性能不一样)。但我想知道这是否有可能在动态中"更改"代码——比如匿名函数或yield return is转换为嵌套类。

我希望有人仔细阅读上面的文字并留下一些线索。

带有非常量表达式的Switch语句-扩展了C#/IDE的功能

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

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

但是,也许你可以使用语言中已经存在的东西,即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"));

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

目前还没有这样的扩展。尽管值得指出的是,MS SQL允许您所寻找的

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

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

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

众所周知,"Switches"向if公开了类似的功能。我们大多数人(包括我自己)都将其视为语法糖——在某个switch上读取一堆事例,然后通读一些if/else-if//其他的但事实是,switch并不是语法糖。

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