C# switch 语句限制 - 为什么?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/44905/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 09:55:31  来源:igfitidea点击:

C# switch statement limitations - why?

提问by ljs

When writing a switch statement, there appears to be two limitations on what you can switch on in case statements.

在编写 switch 语句时,在 case 语句中可以打开的内容似乎有两个限制。

For example (and yes, I know, if you're doing this sort of thing it probably means your object-oriented(OO) architecture is iffy - this is just a contrived example!),

例如(是的,我知道,如果你在做这种事情,这可能意味着你的面向对象(OO)架构是不确定的——这只是一个人为的例子!),

  Type t = typeof(int);

  switch (t) {

    case typeof(int):
      Console.WriteLine("int!");
      break;

    case typeof(string):
      Console.WriteLine("string!");
      break;

    default:
      Console.WriteLine("unknown!");
      break;
  }

Here the switch() statement fails with 'A value of an integral type expected' and the case statements fail with 'A constant value is expected'.

这里 switch() 语句失败并显示“预期整数类型的值”,case 语句失败并显示“预期有一个常量值”。

Why are these restrictions in place, and what is the underlying justification? I don't see any reason why the switch statement hasto succumb to static analysis only, and why the value being switched on has to be integral (that is, primitive). What is the justification?

为什么要实施这些限制,其根本理由是什么?我不明白为什么 switch 语句必须只屈服于静态分析,以及为什么被打开的值必须是整数(即原始值)。理由是什么?

采纳答案by Brian Ensink

This is my original post, which sparked some debate... because it is wrong:

这是我原来的帖子,引发了一些争论......因为它是错误的

The switch statement is not the same thing as a big if-else statement. Each case must be unique and evaluated statically. The switch statement does a constant time branch regardless of how many cases you have. The if-else statement evaluates each condition until it finds one that is true.

switch 语句与大的 if-else 语句不同。每个案例都必须是唯一的并且是静态评估的。无论您有多少个案例,switch 语句都会执行恒定时间分支。if-else 语句评估每个条件,直到找到一个为真。



In fact, the C# switch statement is notalways a constant time branch.

事实上,C# switch 语句并不总是一个常量时间分支。

In some cases the compiler will use a CIL switch statement which is indeed a constant time branch using a jump table. However, in sparse cases as pointed out by Ivan Hamiltonthe compiler may generate something else entirely.

在某些情况下,编译器将使用 CIL switch 语句,这确实是使用跳转表的常量时间分支。但是,在Ivan Hamilton指出的稀疏情况下,编译器可能会完全生成其他内容。

This is actually quite easy to verify by writing various C# switch statements, some sparse, some dense, and looking at the resulting CIL with the ildasm.exe tool.

这实际上很容易通过编写各种 C# switch 语句来验证,有些是稀疏的,有些是密集的,然后使用 ildasm.exe 工具查看生成的 CIL。

回答by markus

This is not a reason why, but the C# specification section 8.7.2 states the following:

这不是原因,但 C# 规范第 8.7.2 节声明如下:

The governing type of a switch statement is established by the switch expression. If the type of the switch expression is sbyte, byte, short, ushort, int, uint, long, ulong, char, string, or an enum-type, then that is the governing type of the switch statement. Otherwise, exactly one user-defined implicit conversion (§6.4) must exist from the type of the switch expression to one of the following possible governing types: sbyte, byte, short, ushort, int, uint, long, ulong, char, string. If no such implicit conversion exists, or if more than one such implicit conversion exists, a compile-time error occurs.

switch 语句的控制类型由 switch 表达式建立。如果 switch 表达式的类型是 sbyte、byte、short、ushort、int、uint、long、ulong、char、string 或枚举类型,那么这就是 switch 语句的控制类型。否则,从 switch 表达式的类型到以下可能的控制类型之一必须存在一个用户定义的隐式转换(第 6.4 节):sbyte、byte、short、ushort、int、uint、long、ulong、char、string . 如果不存在这样的隐式转换,或者存在多个这样的隐式转换,则会发生编译时错误。

The C# 3.0 specification is located at: http://download.microsoft.com/download/3/8/8/388e7205-bc10-4226-b2a8-75351c669b09/CSharp%20Language%20Specification.doc

C# 3.0 规范位于:http: //download.microsoft.com/download/3/8/8/388e7205-bc10-4226-b2a8-75351c669b09/CSharp%20Language%20Specification.doc

回答by Rob Walker

I suppose there is no fundamental reason why the compiler couldn't automatically translate your switch statement into:

我想编译器无法自动将您的 switch 语句转换为:

if (t == typeof(int))
{
...
}
elseif (t == typeof(string))
{
...
}
...

But there isn't much gained by that.

但这并没有太大的收获。

A case statement on integral types allows the compiler to make a number of optimizations:

整数类型的 case 语句允许编译器进行一些优化:

  1. There is no duplication (unless you duplicate case labels, which the compiler detects). In your example t could match multiple types due to inheritance. Should the first match be executed? All of them?

  2. The compiler can choose to implement a switch statement over an integral type by a jump table to avoid all the comparisons. If you are switching on an enumeration that has integer values 0 to 100 then it creates an array with 100 pointers in it, one for each switch statement. At runtime it simply looks up the address from the array based on the integer value being switched on. This makes for much better runtime performance than performing 100 comparisons.

  1. 没有重复(除非您复制了编译器检测到的 case 标签)。在您的示例中,由于继承,t 可以匹配多种类型。应该执行第一场比赛吗?他们都?

  2. 编译器可以选择通过跳转表在整型上实现 switch 语句,以避免所有的比较。如果您要打开一个整数值为 0 到 100 的枚举,那么它会创建一个包含 100 个指针的数组,每个 switch 语句一个。在运行时,它只是根据打开的整数值从数组中查找地址。这使得运行时性能比执行 100 次比较要好得多。

回答by fryguybob

According to the switch statement documentationif there is an unambiguous way to implicitly convert the the object to an integral type, then it will be allowed. I think you are expecting a behavior where for each case statement it would be replaced with if (t == typeof(int)), but that would open a whole can of worms when you get to overload that operator. The behavior would change when implementation details for the switch statement changed if you wrote your == override incorrectly. By reducing the comparisons to integral types and string and those things that can be reduced to integral types (and are intended to) they avoid potential issues.

根据switch 语句文档,如果有一种明确的方式将对象隐式转换为整数类型,那么它将被允许。我认为您期望一种行为,其中每个 case 语句都将替换为if (t == typeof(int)),但是当您使该运算符过载时,这会打开一整罐蠕虫。如果您错误地编写了 == 覆盖,那么当 switch 语句的实现细节发生变化时,行为会发生变化。通过减少与整数类型和字符串的比较以及那些可以减少到整数类型(并且打算)的东西,它们避免了潜在的问题。

回答by Judah Gabriel Himango

While on the topic, according to Jeff Atwood, the switch statement is a programming atrocity. Use them sparingly.

在谈到这个话题时,根据 Jeff Atwood的说法switch 语句是一种编程暴行。谨慎使用它们。

You can often accomplish the same task using a table. For example:

您通常可以使用表格完成相同的任务。例如:

var table = new Dictionary<Type, string>()
{
   { typeof(int), "it's an int!" }
   { typeof(string), "it's a string!" }
};

Type someType = typeof(int);
Console.WriteLine(table[someType]);

回答by mweerden

I have virtually no knowledge of C#, but I suspect that either switch was simply taken as it occurs in other languages without thinking about making it more general or the developer decided that extending it was not worth it.

我几乎对 C# 一无所知,但我怀疑这两种 switch 只是在其他语言中发生而没有考虑使其更通用,或者开发人员认为扩展它是不值得的。

Strictly speaking you are absolutely right that there is no reason to put these restrictions on it. One might suspect that the reason is that for the allowed cases the implementation is very efficient (as suggested by Brian Ensink (44921)), but I doubt the implementation is very efficient (w.r.t. if-statements) if I use integers and some random cases (e.g. 345, -4574 and 1234203). And in any case, what is the harm in allowing it for everything (or at least more) and saying that it is only efficient for specific cases (such as (almost) consecutive numbers).

严格来说,您完全正确,没有理由对其施加这些限制。有人可能怀疑原因是对于允许的情况,实现非常有效(如 Brian Ensink ( 44921)所建议的那样),但我怀疑如果我使用整数和一些随机情况,实现是否非常有效(wrt if 语句) (例如 345、-4574 和 1234203)。无论如何,允许它用于一切(或至少更多)并说它仅对特定情况(例如(几乎)连续数字)有效有什么危害。

I can, however, imagine that one might want to exclude types because of reasons such as the one given by lomaxx (44918).

但是,我可以想象,由于 lomaxx ( 44918)给出的原因,人们可能想要排除类型。

Edit: @Henk (44970): If Strings are maximally shared, strings with equal content will be pointers to the same memory location as well. Then, if you can make sure that the strings used in the cases are stored consecutively in memory, you can very efficiently implement the switch (i.e. with execution in the order of 2 compares, an addition and two jumps).

编辑:@Henk ( 44970):如果最大程度共享字符串,则具有相同内容的字符串也将是指向相同内存位置的指针。然后,如果可以确保案例中使用的字符串连续存储在内存中,则可以非常有效地实现切换(即按照 2 次比较、一次加法和两次跳转的顺序执行)。

回答by Henk

wrote:

"The switch statement does a constant time branch regardless of how many cases you have."

写道:

“无论您有多少案例,switch 语句都会执行恒定时间分支。”

Since the language allows the stringtype to be used in a switch statement I presume the compiler is unable to generate code for a constant time branch implementation for this type and needs to generate an if-then style.

由于该语言允许在 switch 语句中使用字符串类型,我认为编译器无法为这种类型的常量时间分支实现生成代码,并且需要生成 if-then 样式。

@mweerden - Ah I see. Thanks.

@mweerden - 啊,我明白了。谢谢。

I do not have a lot of experience in C# and .NET but it seems the language designers do not allow static access to the type system except in narrow circumstances. The typeofkeyword returns an object so this is accessible at run-time only.

我在 C# 和 .NET 方面没有很多经验,但似乎语言设计者不允许静态访问类型系统,除非在狭窄的情况下。该typeof运算所以这是仅在运行时访问的关键词返回一个对象。

回答by BCS

I think Henk nailed it with the "no sttatic access to the type system" thing

我认为 Henk 用“不能静态访问类型系统”来解决它

Another option is that there is no order to types where as numerics and strings can be. Thus a type switch would can't build a binary search tree, just a linear search.

另一种选择是,数字和字符串的类型没有顺序。因此,类型开关不能构建二叉搜索树,只能构建线性搜索。

回答by HS.

I agree with this commentthat using a table driven approach is often better.

我同意这种评论,即使用表格驱动的方法通常更好。

In C# 1.0 this was not possible because it didn't have generics and anonymous delegates. New versions of C# have the scaffolding to make this work. Having a notation for object literals is also helps.

在 C# 1.0 中这是不可能的,因为它没有泛型和匿名委托。新版本的 C# 具有实现此功能的脚手架。拥有对象文字的符号也有帮助。

回答by Ivan Hamilton

Mostly, those restrictions are in place because of language designers. The underlying justification may be compatibility with languange history, ideals, or simplification of compiler design.

大多数情况下,这些限制是因为语言设计者而存在的。潜在的理由可能是与语言历史、理想或编译器设计的简化的兼容性。

The compiler may (and does) choose to:

编译器可能(并且确实)选择:

  • create a big if-else statement
  • use a MSIL switch instruction (jump table)
  • build a Generic.Dictionary<string,int32>, populate it on first use, and call Generic.Dictionary<>::TryGetValue() for a index to pass to a MSIL switch instruction (jump table)
  • use a combination of if-elses & MSIL "switch" jumps
  • 创建一个大的 if-else 语句
  • 使用 MSIL 开关指令(跳转表)
  • 构建一个 Generic.Dictionary<string,int32>,在第一次使用时填充它,并调用 Generic.Dictionary<>::TryGetValue() 以获得传递给 MSIL 切换指令(跳转表)的索引
  • 使用 if-elses 和 MSIL“开关”跳转的组合

The switch statement IS NOT a constant time branch. The compiler may find short-cuts (using hash buckets, etc), but more complicated cases will generate more complicated MSIL code with some cases branching out earlier than others.

switch 语句不是恒定时间分支。编译器可能会找到捷径(使用哈希桶等),但更复杂的情况会生成更复杂的 MSIL 代码,其中一些情况会比其他情况更早地分支出来。

To handle the String case, the compiler will end up (at some point) using a.Equals(b) (and possibly a.GetHashCode() ). I think it would be trival for the compiler to use any object that satisfies these constraints.

为了处理 String 的情况,编译器将(在某些时候)使用 a.Equals(b) (可能还有 a.GetHashCode() )。我认为编译器使用任何满足这些约束的对象都是微不足道的。

As for the need for static case expressions... some of those optimisations (hashing, caching, etc) would not be available if the case expressions weren't deterministic. But we've already seen that sometimes the compiler just picks the simplistic if-else-if-else road anyway...

至于对静态 case 表达式的需求……如果 case 表达式不是确定性的,那么其中一些优化(散列、缓存等)将不可用。但是我们已经看到,有时编译器无论如何都会选择简单的 if-else-if-else 道路......

Edit: lomaxx- Your understanding of the "typeof" operator is not correct. The "typeof" operator is used to obtain the System.Type object for a type (nothing to do with its supertypes or interfaces). Checking run-time compatibility of an object with a given type is the "is" operator's job. The use of "typeof" here to express an object is irrelevant.

编辑:lomaxx- 您对“typeof”运算符的理解不正确。“typeof”运算符用于获取类型的 System.Type 对象(与其超类型或接口无关)。检查具有给定类型的对象的运行时兼容性是“is”运算符的工作。在这里使用“typeof”来表达一个对象是无关紧要的。