【问题标题】:Switch statement with non-constant-expression - Extends C#/IDE ability带有非常量表达式的 Switch 语句 - 扩展 C#/IDE 能力
【发布时间】:2012-04-10 09:53:58
【问题描述】:

在你开始批评和指点我C# specification 的§8.7.2 之前,请仔细阅读:)

我们都知道开关在 C# 中的样子。好的,所以考虑使用“讨厌” 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!

和调试视图

.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-statement 中使用非常量表达式 :)

好的,我知道这是一些“杂乱无章”的代码。但我的问题来了(最后: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 被转换为嵌套类。

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

【问题讨论】:

  • 似乎要付出很多努力才能解决一个不存在的问题。开关可以提供恒定的查找时间。在您有大量可能值的情况下,这是一件好事。只需使用一系列if/else if 并继续解决实际问题(或使用一种更注重表现力而非全面性能的语言)。
  • 不,我认为你是在浪费时间,而不是用它来解决实际问题。是的,有些语言可以做你建议的事情,它们也不能提供恒定的时间查找。你这一生的时间是有限的,我的建议是不要把它浪费在这样对任何人都没有明显好处的事情上。编程语言只是一种工具,而不是其自身的目标。至于你的其他问题......不,谢谢,我对 UI 怪癖不感兴趣,这就是为什么我不以构建 UI 为生:)
  • @EdS.:我不同意花时间思考编程语言是浪费时间。可以对代码执行许多有趣的转换,即使是那些没有转化为有用的东西的转换也是有价值的。这就是我们进行研究的原因。更具体地说,我注意到您说开关 can 提供恒定时间行为是正确的。我注意到他们没有保证这样做;实际上,可以使用对数或线性算法生成开关。
  • @EricLippert:当然,总的来说我同意,只是在这种情况下不同意。我实际上要提到的是,尽管我认为这是一个试图找到问题的解决方案,但调查 C# 扩展的行为通常是一个有趣的行为。是的...一定要说“可以”:)
  • 我的问题是,这并没有解决任何问题,它没有给我们提供一种方法来做我们以前做不到的事情。

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


【解决方案1】:

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

【讨论】:

  • 虽然我完全同意您的回答,但我不知道为什么在 switch 中使用字符串是个奇迹,因为您实际使用的是编译时常量(文字字符串值)。跨度>
  • 例如在C++ 中你不能在switch 语句中使用字符串。 typeclear comparison ability 的概念是 C#“免费”提供的。也就是说,我的意思只是要注意.Net 给我们带来的好处。
【解决方案2】:

目前还没有可以做这种事情的扩展。尽管值得指出的是,MS SQL 完全可以满足您的需求

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

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

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

【讨论】:

  • +1 指出两个案例标签不能具有相同的值
【解决方案3】:

众所周知,“开关”向if 公开了类似的功能。我们大多数人(包括我自己)将其视为语法糖——阅读某个switch 上的一堆案例然后通读一些 if/else if/.../else 会更容易。但事实是 switch 不是语法糖。

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

【讨论】:

  • 交换机可以在恒定时间有时运行。交换机绝对不能保证在恒定时间内运行,实际上它们经常不会。有很多因素会导致开关以对数或线性时间运行。
  • @EricLippert,我正在查看由 switch 语句生成的 IL,发现我脑海中的优化并不真正存在——我开始怀疑 OP 在问什么——它有道理。我可以推荐一篇关于这个问题的博客文章吗? :)
【解决方案4】:

一般来说,据我所知,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"));

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-01-16
    • 1970-01-01
    • 2020-01-14
    • 1970-01-01
    • 2011-04-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多