前言:

经过前面几篇的学习,我们了解到指令的大概分类,如:

参数加载指令,该加载指令以 Ld 开头,将参数加载到栈中,以便于后续执行操作命令。

参数存储指令,其指令以 St 开头,将栈中的数据,存储到指定的变量中,以方便后续使用。

创建实例指令,其指令以 New 开头,用于在运行时动态生成并初始化对象。

方法调用指令,该指令以 Call 开头,用于在运行时调用其它方法。

本篇介绍分支条件指令,该指令通常以 Br、或 B、C 开头,用于在运行分支条件时跳转指令。

分支条件指令介绍:

分支条件指令是在.NET Emit编程中关键的控制流程工具,用于在IL代码中实现条件判断和控制转移。

ILGenerator 类提供了一系列方法,用于生成这些分支条件指令,包括条件分支、无条件分支和Switch分支等。

条件分支指令(如brtrue和brfalse)根据栈顶的布尔值决定是否跳转到目标标签,而无条件分支指令(如br)则总是进行跳转。

Switch分支指令则用于在多个目标中选择一个跳转。

通过比较指令(如ceq、cgt和clt),还可以进行数值比较并根据比较结果执行相应的跳转操作。

这些指令的灵活运用可以实现复杂的控制逻辑,例如条件判断、循环和异常处理等。

深入理解分支条件指令将帮助开发者更好地掌握.NET Emit编程,提高代码生成的效率和灵活性。

常用分支条件指令:

条件跳转指令:

  1. beq:如果两个值相等,则跳转到指定的标签。
  2. bge:如果第一个值大于或等于第二个值,则跳转到指定的标签。
  3. bgt:如果第一个值大于第二个值,则跳转到指定的标签。
  4. ble:如果第一个值小于或等于第二个值,则跳转到指定的标签。
  5. blt:如果第一个值小于第二个值,则跳转到指定的标签.
  6. bne.un:如果两个无符号整数值不相等,则跳转到指定的标签。
  7. brtrue:如果值为 true,则跳转到指定的标签。
  8. brfalse:如果值为 false,则跳转到指定的标签。
  9. brtrue.s:如果值为 true,则跳转到指定的标签(短格式)。
  10. brfalse.s:如果值为 false,则跳转到指定的标签(短格式).

无条件跳转指令:

  1. br:无条件跳转到指定的标签。
  2. br.s:短格式的无条件跳转到指定的标签。
  3. leave:无条件跳转到 try、filter 或 finally 块的末尾。
  4. leave.s:短格式的无条件跳转到 try、filter 或 finally 块的末尾.

比较跳转指令:

  1. bgt.un:如果第一个无符号整数值大于第二个值,则跳转到指定的标签。
  2. bge.un:如果第一个无符号整数值大于或等于第二个值,则跳转到指定的标签。
  3. blt.un:如果第一个无符号整数值小于第二个值,则跳转到指定的标签。
  4. ble.un:如果第一个无符号整数值小于或等于第二个值,则跳转到指定的标签.

其他跳转指令:

  1. switch:根据给定的索引值跳转到不同的标签。
  2. brnull:如果值为 null,则跳转到指定的标签。
  3. brinst:如果对象是类的实例,则跳转到指定的标签。

这些指令可以帮助控制流程,在特定条件下跳转到指定的标签位置执行相应的代码。

从以上分类说明可以看出,该指令需要配置标签使用,对于标签的用法,

如有遗忘,可以回去补一下文章:.NET Emit 入门教程:第六部分:IL 指令:2:详解 ILGenerator 辅助方法

上面指令按使用方式,只分两种:

1、条件跳转指令:根据栈顶的数据,及指令的判断条件,来跳转标签。

2、Switch 分支跳转指令:根据给定的索引值,来跳转标签。

1、条件跳转指令:

条件分支指令是在IL代码中用于根据条件来执行跳转操作的指令。

它们可以根据栈顶的(布尔)值来决定是否跳转到目标标签:

示例指令:Brtrue

示例指令:Brfalse

该 true、false 指令,除了 bool 值,还兼容判断了空(引用)和数字(零),这个小细节要注意。

示例指令:Br

示例代码:

var dynamicMethod = new DynamicMethod("WriteAOrB", typeof(void), new Type[] { typeof(bool) }, typeof(AssMethodIL_Condition));

ILGenerator il = dynamicMethod.GetILGenerator();

var labelEnd = il.DefineLabel();
var labelFalse = il.DefineLabel(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Brfalse_S, labelFalse); il.EmitWriteLine("true.");
il.Emit(OpCodes.Br_S, labelEnd); il.MarkLabel(labelFalse);
il.EmitWriteLine("false"); il.MarkLabel(labelEnd);
il.Emit(OpCodes.Ret); // 返回该值

运行结果:

说明:

1、在 true、false 指令中,通常只会使用其中一个。 

2、在分支条件中,很多时候需要配合 Br 无条件指令跳出分支。

3、在定义标签时,除了定义分支标签,还要定义结束标签,以便 Br 无条件指令的跳出。 

4、其它条件指令的使用,和bool条件指令的使用是一样的,只需要理解指令的含义即可。

2、Switch 分支条件指令:

Switch 分支指令用于在多个目标中选择一个跳转,类似于在高级编程语言中的 switch 或者 case 语句。

在IL代码中,Switch 指令可以实现根据一个整数值来决定跳转到不同的目标标签。Switch 分支指令的主要指令是 switch,其作用如下:

  • switch: 该指令从标签数组中选择一个目标标签进行跳转。在 IL 代码中,标签数组通常在 switch 指令之前被定义,并且 switch 指令的操作数是标签数组的引用。switch 指令会从操作数指定的标签数组中根据整数值索引来选择目标标签,然后执行跳转操作。

Switch 分支指令的作用是根据一个整数值来决定跳转到不同的目标标签,这在处理具有多个选择的情况下非常有用,可以使得代码更加简洁和高效。

注意事项:

1、该 Switch 指令只是类似 switch 编程,但不等同

2、该 Switch 指令只能根据索引进行指令跳转。

同时,Switch 指令在IL代码编写起来,会相对复杂一点,特别是 case 一多,写起来可会要你命3000.

为了区分 Switch 指令和我们编写代码时的指令 Switch case 区别,我们来看以下示例:

这一次我们反过来,先写 C# 代码,再看它生成的 IL 代码。

下面给一个示例代码1:

static void TestString(string c)
{
switch(c)
{
case "a":
Console.WriteLine("A");break;
case "b":
Console.WriteLine("B");break;
default:
Console.WriteLine("C");break;
}
}

反编绎,看生成的 IL 代码:

.method private hidebysig static
void TestString (
string c
) cil managed
{
// Method begins at RVA 0x3808
// Code size 73 (0x49)
.maxstack 2
.locals init (
[0] string,
[1] string
) IL_0000: nop
IL_0001: ldarg.0
IL_0002: stloc.1
IL_0003: ldloc.1
IL_0004: stloc.0
IL_0005: ldloc.0
IL_0006: ldstr "a"
IL_000b: call bool [mscorlib]System.String::op_Equality(string, string)
IL_0010: brtrue.s IL_0021 IL_0012: ldloc.0
IL_0013: ldstr "b"
IL_0018: call bool [mscorlib]System.String::op_Equality(string, string)
IL_001d: brtrue.s IL_002e IL_001f: br.s IL_003b IL_0021: ldstr "A"
IL_0026: call void [mscorlib]System.Console::WriteLine(string)
IL_002b: nop
IL_002c: br.s IL_0048 IL_002e: ldstr "B"
IL_0033: call void [mscorlib]System.Console::WriteLine(string)
IL_0038: nop
IL_0039: br.s IL_0048 IL_003b: ldstr "C"
IL_0040: call void [mscorlib]System.Console::WriteLine(string)
IL_0045: nop
IL_0046: br.s IL_0048 IL_0048: ret
} // end of method Program::TestString

从该生成的 IL 代码中,可以看出并没有 Switch 指令,而是常规调用字符串比较后,用 bool 条件指令进行跳转。

那是不是用数字类型就会得到 Switch 指令呢?

再用数字型来一次:

        static void TestInt(int c)
{
switch (c)
{
case 1:
Console.WriteLine("AAA"); break;
case 2:
Console.WriteLine("BBB"); break;
default:
Console.WriteLine("CCC"); break;
}
}

得到的 IL 代码如下:

.method private hidebysig static
void TestInt (
int32 c
) cil managed
{
// Method begins at RVA 0x3860
// Code size 57 (0x39)
.maxstack 2
.locals init (
[0] int32,
[1] int32
) IL_0000: nop
IL_0001: ldarg.0
IL_0002: stloc.1
IL_0003: ldloc.1
IL_0004: stloc.0
IL_0005: ldloc.0
IL_0006: ldc.i4.1
IL_0007: beq.s IL_0011 IL_0009: br.s IL_000b IL_000b: ldloc.0
IL_000c: ldc.i4.2
IL_000d: beq.s IL_001e IL_000f: br.s IL_002b IL_0011: ldstr "AAA"
IL_0016: call void [mscorlib]System.Console::WriteLine(string)
IL_001b: nop
IL_001c: br.s IL_0038 IL_001e: ldstr "BBB"
IL_0023: call void [mscorlib]System.Console::WriteLine(string)
IL_0028: nop
IL_0029: br.s IL_0038 IL_002b: ldstr "CCC"
IL_0030: call void [mscorlib]System.Console::WriteLine(string)
IL_0035: nop
IL_0036: br.s IL_0038 IL_0038: ret
} // end of method Program::TestInt

依旧是 br 跳转指令。

再试一下用枚举呢?

        static void TestEnum(DataAccessKind c)
{
switch (c)
{
case DataAccessKind.Read:
Console.WriteLine("AAA"); break;
case DataAccessKind.None:
Console.WriteLine("BBB"); break;
default:
Console.WriteLine("CCC"); break;
}
}

得到 IL 代码如下:

.method private hidebysig static
void TestEnum (
valuetype [System.Data]Microsoft.SqlServer.Server.DataAccessKind c
) cil managed
{
// Method begins at RVA 0x38a8
// Code size 56 (0x38)
.maxstack 2
.locals init (
[0] valuetype [System.Data]Microsoft.SqlServer.Server.DataAccessKind,
[1] valuetype [System.Data]Microsoft.SqlServer.Server.DataAccessKind
) IL_0000: nop
IL_0001: ldarg.0
IL_0002: stloc.1
IL_0003: ldloc.1
IL_0004: stloc.0
IL_0005: ldloc.0
IL_0006: brfalse.s IL_001d IL_0008: br.s IL_000a IL_000a: ldloc.0
IL_000b: ldc.i4.1
IL_000c: beq.s IL_0010 IL_000e: br.s IL_002a IL_0010: ldstr "AAA"
IL_0015: call void [mscorlib]System.Console::WriteLine(string)
IL_001a: nop
IL_001b: br.s IL_0037 IL_001d: ldstr "BBB"
IL_0022: call void [mscorlib]System.Console::WriteLine(string)
IL_0027: nop
IL_0028: br.s IL_0037 IL_002a: ldstr "CCC"
IL_002f: call void [mscorlib]System.Console::WriteLine(string)
IL_0034: nop
IL_0035: br.s IL_0037 IL_0037: ret
} // end of method Program::TestEnum

还是没有见 Switch 指令。

可见,编程中的Switch,和 IL 的 Switch 指令是不同的。

所以很容易陷入一个误区,以为代码用 Switch 写分支,对应的IL就得用 Switch 指令。

下面演示一个用 Switch 指令的示例:

 var dynamicMethod = new DynamicMethod("WriteAOrB", typeof(void), new Type[] { typeof(int) }, typeof(AssMethodIL_Condition));

 ILGenerator il = dynamicMethod.GetILGenerator();

 var labelEnd = il.DefineLabel();

 Label labelIndex_0 = il.DefineLabel();
Label labelIndex_1 = il.DefineLabel();
Label labelIndex_2 = il.DefineLabel();
//BreakOp None=-1,Null=0,Empty=1,NullOrEmpty=2
var lables = new Label[] { labelIndex_0, labelIndex_1, labelIndex_2 };//0、1、2 il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Switch, lables); il.MarkLabel(labelIndex_0);
il.EmitWriteLine("0.");
il.Emit(OpCodes.Br_S, labelEnd); il.MarkLabel(labelIndex_1);
il.EmitWriteLine("1.");
il.Emit(OpCodes.Br_S, labelEnd); il.MarkLabel(labelIndex_2);
il.EmitWriteLine("2.");
il.Emit(OpCodes.Br_S, labelEnd); il.MarkLabel(labelEnd);
il.Emit(OpCodes.Ret); // 返回该值 dynamicMethod.Invoke(null, new object[] { 1 }); Console.Read();

运行结果:

从上面的示例可以看出,使用 Switch 指令,其实是在 IL 中编写类似于 Switch 语法的条件分支,而不是和C# 语法的 Switch 对应。

总结:

本篇介绍了在IL(Intermediate Language)代码中常见的两种指令类型:条件跳转指令和Switch 分支跳转指令。

条件跳转指令则用于执行数值比较操作,根据比较结果执行相应的跳转操作或将比较结果压入栈中。

由于其使用方式是一致,因此示例仅展示bool条件指令的使用,没有对其它指令展开示例,但其它条件指令的含义,也是需要仔细了解一下的。

Switch 分支跳转指令用于根据一个整数值选择不同的目标标签进行跳转,类似于高级编程语言中的 switch 或者 case 语句。

通过 Switch 指令,可以使代码更加简洁、高效,并提高可读性和可维护性。在处理具有多个选择的情况下特别有用,例如枚举类型或者状态机的处理。

它们在条件分支指令和循环控制中起着关键作用,通过灵活运用比较指令,可以实现各种复杂的算法和逻辑。

综上所述,Switch 分支跳转指令和条件跳转指令是IL代码中常用的两种控制流指令,它们在编写和优化IL代码时起着重要作用,能够使代码更加简洁、高效和易于理解。

.NET Emit 入门教程:第六部分:IL 指令:7:详解 ILGenerator 指令方法:分支条件指令的更多相关文章

  1. Golang入门教程(十三)延迟函数defer详解

    前言 大家都知道go语言的defer功能很强大,对于资源管理非常方便,但是如果没用好,也会有陷阱哦.Go 语言中延迟函数 defer 充当着 try...catch 的重任,使用起来也非常简便,然而在 ...

  2. Docker入门教程(六)另外的15个Docker命令

    Docker入门教程(六)另外的15个Docker命令 [编者的话]DockerOne组织翻译了Flux7的Docker入门教程,本文是系列入门教程的第六篇,继续介绍Docker命令.之前的第二篇文章 ...

  3. 无废话ExtJs 入门教程十六[页面布局:Layout]

    无废话ExtJs 入门教程十六[页面布局:Layout] extjs技术交流,欢迎加群(201926085) 首先解释什么是布局: 来自百度词典的官方解释:◎ 布局 bùjú: [distributi ...

  4. Photoshop入门教程(六):通道

    学习心得:当大部分人听到通道.心里可能会有一种很害怕的感觉,因为“通道”并不像“图层”这样易于理解,望而生畏.”通道“的本质其实是存储图片的信息,把一张图片比作一个为网站,那么通道就是网站的后台,存储 ...

  5. RabbitMQ入门教程(十六):RabbitMQ与Spring集成

    原文:RabbitMQ入门教程(十六):RabbitMQ与Spring集成 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https: ...

  6. ASP.NET MVC 5 学习教程:Edit方法和Edit视图详解

    原文 ASP.NET MVC 5 学习教程:Edit方法和Edit视图详解 起飞网 ASP.NET MVC 5 学习教程目录: 添加控制器 添加视图 修改视图和布局页 控制器传递数据给视图 添加模型 ...

  7. SpringMVC强大的数据绑定(2)——第六章 注解式控制器详解

    SpringMVC强大的数据绑定(2)——第六章 注解式控制器详解 博客分类: 跟开涛学SpringMVC   6.6.2.@RequestParam绑定单个请求参数值 @RequestParam用于 ...

  8. 转:JAVAWEB开发之权限管理(二)——shiro入门详解以及使用方法、shiro认证与shiro授权

    原文地址:JAVAWEB开发之权限管理(二)——shiro入门详解以及使用方法.shiro认证与shiro授权 以下是部分内容,具体见原文. shiro介绍 什么是shiro shiro是Apache ...

  9. SaltStack 入门到精通第二篇:Salt-master配置文件详解

    SaltStack 入门到精通第二篇:Salt-master配置文件详解     转自(coocla):http://blog.coocla.org/301.html 原本想要重新翻译salt-mas ...

  10. 【入门】广电行业DNS、DHCP解决方案详解(三)——DNS部署架构及案

    [入门]广电行业DNS.DHCP解决方案详解(三)——DNS部署架构及案 DNS系统部署架构 宽带业务DNS架构 互动业务DNS架构 案例介绍 案例一 案例二 本篇我们将先介绍DNS系统部署架构体系, ...

随机推荐

  1. ants - 目前开源最优的协程池

    ants - 目前开源最优的协程池 目前我们的项目重度使用 ants 协程池,在开启一个 go 的时候并不是用 go 关键字,而是用一个封装的 go 函数来开启协程.框架底层,则是使用 ants 项目 ...

  2. Twitter推特 api接口 获取trending趋势搜索关键词 python数据采集

    iDataRiver平台 https://www.idatariver.com/zh-cn/ 提供开箱即用的Twitter公开数据采集API,供用户按需调用. 接口使用详情请参考Twitter接口文档 ...

  3. C++入门编程----C++运算符(8)

    什么是运算符 运算符是让程序执行特定的数学或逻辑操作的符号,用来表示针对数据的特定操作,也称之为操作符.C++运算符分别有算术运算符.关系运算符.逻辑运算符.赋值运算符.位运算符.移位运算符.size ...

  4. RIPEMD算法:多功能哈希算法的瑰宝

    一.RIPEMD算法的起源与历程 RIPEMD(RACE Integrity Primitives Evaluation Message Digest)算法是由欧洲研究项目RACE发起,由Hans D ...

  5. pdf 等所有文件通过blog强制下载函数 downloadFileFromBlobByToken

    downloadFileFromBlobByToken pdf 等所有文件通过blog强制下载函数 downloadFileFromBlobByToken import { getToken } fr ...

  6. KETTLE实战视频教程--免费白嫖(本贴持续更新)

    KETTLE实战视频教程 欢迎关注笔者的公众号: java大师, 每日推送java.kettle运维等领域干货文章,关注即免费无套路附送 100G 海量学习.面试资源哟!!个人网站: http://w ...

  7. python读取文本多行合并一行

    # 需要合并的行数 col = 4 # 创建新文件 nf = open("*.txt", "w+") # 读取初始文件 with open("*.tx ...

  8. NJUPT自控第一次积分赛的小总结(二)基于simpleFOC的无刷电机控制

    新人一枚,写的比较水,欢迎大佬指正! 先说一下我用的物料与开发环境吧: 无刷电机:makerbase的2804电机(带AS5600磁编码器) 电机驱动板:simpleFOCmini(学校推荐的) 电池 ...

  9. 更智能的广告素材生成!看A/B测试如何驱动AIGC素材调优

    更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群 前言:AIGC大爆发,引发广告营销行业变革 ChatGPT等AI产品引发的AIGC大爆发引起了各行业的震动,其中以 ...

  10. 记录--JS-SDK页面打开提示realAuthUrl错误

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 测试环境好好地功能,上了生产,莫名其妙报错,开始以为是没有设置Js安全接口域名,结果让相应人员一查,已经设置了相应的域名,再看下公众号内的 ...