透过IL看C# (1)
switch语句(上)

原文地址:http://www.cnblogs.com/AndersLiu/archive/2008/11/03/csharp-via-il-switch-1.html

原创:Anders Liu

摘要:switch语句是C#中常用的跳转语句,可以根据一个参数的不同取值执行不同的代码。本文介绍了当向switch语句中传入不同类型的参数时,编译器为其生成的IL代码。这一部分介绍的是,在switch语句中使用整数类型和枚举类型的情况。

switch语句是C#中常用的跳转语句,可以根据一个参数的不同取值执行不同的代码。switch语句可以具备多个分支,也就是说,根据参数的N种取值,可以跳转到N个代码段去运行。这不同于if语句,一条单独的if语句只具备两个分支(这是因为if语句的参数只能具备true或false两种取值),除非使用嵌套if语句。

switch语句能够接受的参数是有限制的,简单来说,只能是整数类型、枚举或字符串。本文就从整数、枚举和字符串这三种类型的switch语句进行介绍。

switch指令

在进入正题之前,先为大家简要介绍一下IL汇编语言中的switch指令。switch指令(注意和C#中的switch语句区分开)是IL中的多分支指令,它的基本形式如下:

switch (Label_1, Label_2, Label_3…)

其中switch是IL关键字,Label_1~Label_N是一系列标号(和goto语句中用到的标号一样),标号指明了代码中的位置。这条指令的运行原理是,从运算栈顶弹出一个无符号整数值,如果该值是0,则跳转到由Label_1指定的位置执行;如果是1,则跳转到Labe_2;如果是2,则跳转到Label_3;以此类推。

如果栈顶弹出的值不在标号列表的范围之内(0~N-1),则忽略switch指令,跳到switch指令之后的一条指令开始执行。因此,对于switch指令来说,其 “default子句”是在最开头的。

此外,Label_x所引用的标号位置只要位于当前方法体就可以,不必非要在switch指令的后面。

好了,后面我们会看到switch指令的实例的。

使用整数类型的switch语句

代码1 - 使用整数类型参数的switch语句,取值连续

static void TestSwitchInt(int n) { switch(n) { case 1: Console.WriteLine("One"); break; case 2: Console.WriteLine("Two"); break; case 3: Console.WriteLine("Three"); break; } }

代码1中的switch语句接受的参数n是int类型的,并且我们观察到,在各个case子句中的取值都是连续的。将这段代码写在一个完整的程序中,并进行编译。之后使用ildasm打开生成的程序集,可以看到对应的IL代码如代码2所示。

代码2 – 代码1生成的IL代码

.method private hidebysig static void TestSwitchInt(int32 n) cil managed { // Code size 56 (0x38) .maxstack 2 .locals init (int32 V_0) IL_0000: ldarg.0 IL_0001: stloc.0 IL_0002: ldloc.0 IL_0003: ldc.i4.1 IL_0004: sub IL_0005: switch ( IL_0017, IL_0022, IL_002d) IL_0016: ret IL_0017: ldstr "One" IL_001c: call void [mscorlib]System.Console::WriteLine(string) IL_0021: ret IL_0022: ldstr "Two" IL_0027: call void [mscorlib]System.Console::WriteLine(string) IL_002c: ret IL_002d: ldstr "Three" IL_0032: call void [mscorlib]System.Console::WriteLine(string) IL_0037: ret } // end of method Program::TestSwitchInt

我们可以看到,首先IL_0000和IL_0001两行代码将参数n存放到一个局部变量中,然后IL_0002到IL_0004三行将这个变量的值减去1,并将结果留在运算栈顶。啊哈,参数值减去1,要进行判断的几种情况不就变成了0、1、2了么?是的。在接下来的switch指令里,针对这三种取值给出了三个地址IL_0017、IL_0022和IL_002d。这三个地址处的代码,分别就是取值为1、2、3时需要执行的代码。

以上是取值连续的情形。如果各个case子句中给出的值并不连续呢?我们来看一下下面的C#代码:

代码3 – 使用整数类型参数的switch语句,取值不连续

static void TestSwitchInt2(int n) { switch(n) { case 1: Console.WriteLine("1"); break; case 3: Console.WriteLine("3"); break; case 5: Console.WriteLine("5"); break; } }

代码3编译生成的程序集中,编译器生成的IL代码如下:

代码4 – 代码3生成的IL代码

.method private hidebysig static void TestSwitchInt2(int32 n) cil managed { // Code size 64 (0x40) .maxstack 2 .locals init (int32 V_0) IL_0000: ldarg.0 IL_0001: stloc.0 IL_0002: ldloc.0 IL_0003: ldc.i4.1 IL_0004: sub IL_0005: switch ( IL_001f, // 0 IL_003f, // 1 IL_002a, // 2 IL_003f, // 3 IL_0035) // 4 IL_001e: ret IL_001f: ldstr "1" IL_0024: call void [mscorlib]System.Console::WriteLine(string) IL_0029: ret IL_002a: ldstr "3" IL_002f: call void [mscorlib]System.Console::WriteLine(string) IL_0034: ret IL_0035: ldstr "5" IL_003a: call void [mscorlib]System.Console::WriteLine(string) IL_003f: ret } // end of method Program::TestSwitchInt2

看到代码4,第一感觉就是switch指令中跳转地址的数量和C#程序中switch语句中的取值数不相符。但仔细观察后可以发现,switch指令中针对0、2、4(即switch语句中的case 1、3、5)这三种取值给出了不同的跳转地址。而对于1、3这两种取值(在switch语句中并没有出现)则给出了同样的地址IL_003f,看一下这个地址,是语句ret。

也就是说,对于取值不连续的情况,编译器会自动用“default子句”的地址来填充switch指令中的“缝隙”。当然,代码4因为过于简单,所以“缝隙值”直接跳转到了方法的结尾。

那么,如果取值更不连续呢?那样的话,switch指令中就会有大量的“缝隙值”。要知道,switch指令和之后的跳转地址列表都是指令的一部分,缝隙值的增加势必会导致程序集体积的增加啊。呵呵,不必担心,编译器很聪明,请看下面的代码:

代码5 – 使用整数类型参数的switch语句,取值非常不连续

static void TestSwitchInt3(int n) { switch(n) { case 10: Console.WriteLine("10"); break; case 30: Console.WriteLine("30"); break; case 50: Console.WriteLine("50"); break; } }

在代码5中,switch语句的每个case子句中给出的取值之间都相差20,这意味着如果再采用前面所述“缝隙值”的做法,switch指令中将有多达41个跳转地址,而其中有效的只有3个。但现代的编译器明显不会犯这种低级错误。下面给出编译器为代码5 生成的IL:

代码6 – 代码5生成的IL代码

.method private hidebysig static void TestSwitchInt3(int32 n) cil managed { // Code size 51 (0x33) .maxstack 2 .locals init (int32 V_0) IL_0000: ldarg.0 IL_0001: stloc.0 IL_0002: ldloc.0 IL_0003: ldc.i4.s 10 IL_0005: beq.s IL_0012 IL_0007: ldloc.0 IL_0008: ldc.i4.s 30 IL_000a: beq.s IL_001d IL_000c: ldloc.0 IL_000d: ldc.i4.s 50 IL_000f: beq.s IL_0028 IL_0011: ret IL_0012: ldstr "10" IL_0017: call void [mscorlib]System.Console::WriteLine(string) IL_001c: ret IL_001d: ldstr "30" IL_0022: call void [mscorlib]System.Console::WriteLine(string) IL_0027: ret IL_0028: ldstr "50" IL_002d: call void [mscorlib]System.Console::WriteLine(string) IL_0032: ret } // end of method Program::TestSwitchInt3

从代码6中我们会发现,switch指令不见了,在IL_0005、IL_000a和IL_000f三处分别出西安了beq.s指令,这个指令是beq指令的简短形式。当跳转位置和当前位置之差在一个sbyte类型的范围之内时,编译器会自动选择简短形式,目的是缩小指令集的体积。而beq指令的作用是从运算栈中取出两个值进行比较,如果两个值相等,则跳转到目标位置(有beq指令后面的参数指定)执行,否则继续从beq指令的下一条指令开始执行。

由此可见,当switch语句的取值非常不连续时,编译器会放弃使用switch指令,转而用一系列条件跳转来实现。这有点类似于if-else if-...-else语句。

使用枚举类型的switch语句

.NET中的枚举是一种特殊的值类型,它必须以某一种整数类型作为其底层类型(underlying type)。因此在运算时,枚举都是按照整数类型对待的,switch指令会将栈顶的枚举值自动转换成一个无符号整数,然后进行判断。

因此,在switch语句中使用枚举和使用整数类型没有太大的区别。请看下面一段代码:

代码7 - 在switch语句中使用枚举类型

static void TestSwitchEnum(Num n) { switch(n) { case Num.One: Console.WriteLine("1"); break; case Num.Two: Console.WriteLine("2"); break; case Num.Three: Console.WriteLine("3"); break; } }

其中的Num类型是一个枚举,定义为public enum Num { One, Two, Three }

下面是编译器为代码7生成的IL代码:

代码8 - 代码7生成的IL代码

.method private hidebysig static void TestSwitchEnum(valuetype AndersLiu.CSharpViaIL.Switch.Num n) cil managed { // Code size 54 (0x36) .maxstack 1 .locals init (valuetype AndersLiu.CSharpViaIL.Switch.Num V_0) IL_0000: ldarg.0 IL_0001: stloc.0 IL_0002: ldloc.0 IL_0003: switch ( IL_0015, IL_0020, IL_002b) IL_0014: ret IL_0015: ldstr "1" IL_001a: call void [mscorlib]System.Console::WriteLine(string) IL_001f: ret IL_0020: ldstr "2" IL_0025: call void [mscorlib]System.Console::WriteLine(string) IL_002a: ret IL_002b: ldstr "3" IL_0030: call void [mscorlib]System.Console::WriteLine(string) IL_0035: ret } // end of method Program::TestSwitchEnum

可以看到,代码8和代码2没有什么本质区别。这是因为枚举值就是按照整数对待的。并且,如果枚举定义的成员取值不连续,生成的代码也会和代码4、代码6类似。

小结

本文介绍了编译器如何翻译使用整数类型的switch语句。如果你很在乎微乎其微的效率提升的话,应记得:

  • 尽量在switch中使用连续的取值;
  • 如果取值不连续,则使用尽量少的case子句,并将出现频率高的case放在前面(因为此时switch语句和if-else if-else语句是类似的)。

返回目录:透过IL看C#

转载于:https://www.cnblogs.com/AndersLiu/archive/2008/11/03/csharp-via-il-switch-1.html

透过IL看C# (1)——switch语句(上)相关推荐

  1. 为什么不能在字符串上使用switch语句?

    此功能是否将在以后的Java版本中使用? 有人可以解释为什么我不能这样做吗,例如Java的switch语句的技术方式? #1楼 Groovy轻而易举: 我嵌入了groovy jar并创建了一个groo ...

  2. switch语句能否作用在byte上,能否作用在long上,能否作用在String上?

    可以用 char, byte, short, int,Character, Byte, Short, Integer String, enum 不能用 Long,boolean,float,doubl ...

  3. 【汇编语言与计算机系统结构笔记08】如何实现循环(Loops),gcc历史上经历了多种转换模式(微体系结构角度解释),Switch语句,跳转表

    本次笔记内容: 09.控制流-2 文章目录 练习题:条件转移指令局限性 如何实现循环(Loops) "Do-While"循环实例 "While"循环版本 &qu ...

  4. java习题4.1-将学生的学习成绩按不同的分数段分为优、良、中、及格和不及格五个登记,从键盘上输入一个0~100的成绩,输出相应的等级。要求用switch语句实现

    将学生的学习成绩按不同的分数段分为优.良.中.及格和不及格五个登记,从键盘上输入一个0~100的成绩,输出相应的等级.要求用switch语句实现 import java.util.Scanner;pu ...

  5. switch语句作用在byte上却不能作用在String和long上

    在switch(exprl)语句中,exprl必须是一个整数表达式或者枚举常量.而byte short char都可以隐式转换为int类型,整数表达式可以是int或者包装类Integer,所以byte ...

  6. 我去,你写的 switch 语句也太老土了吧

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 昨天早上通过远程的方式 review 了两名新来同事的代码,大部分 ...

  7. 你真的懂switch吗?聊聊switch语句中的块级作用域

      最近在代码中不小心不规范的,在switch里面定义了块级变量,导致页面在某些浏览器中出错,本文讨论以下switch语句中的块级作用域. switch语句中的块级作用域 switch语句中的块级作用 ...

  8. Go switch语句

    10. switch 语句 switch 是一个条件语句,用于将表达式的值与可能匹配的选项列表进行比较,并根据匹配情况执行相应的代码块.它可以被认为是替代多个 if else 子句的常用方式. 看代码 ...

  9. java 圈复杂度_关于Java:降低Switch语句的循环复杂度-Sonar

    我想减少开关盒的圈复杂度 我的代码是: public String getCalenderName() { switch (type) { case COUNTRY: return country = ...

最新文章

  1. LA 3353 最优巴士线路设计
  2. Javascript 类型转换
  3. 运行caffe自带的两个简单例子
  4. python会不会出4_无极4网人生苦短,Python会不会被取代?国外网友
  5. Python数据清洗 - 洗什么?怎么洗?看完就明白了
  6. 软件项目文档_什么是软件项目的好的文档?
  7. 【SpringCloud】Nacos 自定义登录用户名和密码
  8. 安装VCSA6.7(vCenter Server Appliance 6.7) 2019.7.9
  9. 【logstash】使用logstash拉取数据到kerberos+SSL认证的kafka集群中遇到的坑
  10. 视频全程:哈萨比斯首次公开解读AlphaZero
  11. 8.1 模型压缩的方法
  12. 2022电工杯B题思路模型分析
  13. 杭电计算机组成原理实验RISC-V 实验 实现运算及传送指令的CPU设计实验 实现访存指令的CPU设计实验 实现转移指令的CPU设计实验
  14. 【源码分析】Spring Boot中Relaxed Binding机制的不同实现
  15. c语言1 2.5*3,若有如下变量定义并赋值:inta=1,b=2,c=3,k;float f=2.5,e;doubled=2.4,g;则下列符合C语言语法的...
  16. 您似乎与家庭管理员不在同一个国家/地区,油管换区过程记录
  17. OpenCV - Universal intrinsics 统一指令集
  18. 期货开户公司行情资讯及时高效
  19. R COOKBOOK 学习笔记
  20. m277打印机 重置_惠普M277n说明书

热门文章

  1. 英语学习—每天进步一丢丢系列(一)
  2. 你遇到过最尴尬的糗事是什么?
  3. 电动车爬坡时究竟应该用最快档还是用最慢档?
  4. 如果第一次见面,投资人就能给创业者提出建设性的意见
  5. 不要根据自己的喜好创业
  6. 今天tiktok小社群更新 第5个项目行业案例
  7. 昨天和一位成功的创业者聊天,他说了一番话,有价值,与大家分享
  8. 两大思维,就可以让你轻松完成任意一个目标
  9. 支付宝相关信息会被泄露出去吗?
  10. SQL Server中的快照隔离