前言:

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

参数加载指令,该加载指令以 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. 开源.NET8.0小项目伪微服务框架(分布式、EFCore、Redis、RabbitMQ、Mysql等)

    1.前言 为什么说是伪微服务框架,常见微服务框架可能还包括服务容错.服务间的通信.服务追踪和监控.服务注册和发现等等,而我这里为了在使用中的更简单,将很多东西进行了简化或者省略了. 年前到现在在开发一 ...

  2. TypeScript实践总结

    下文将TypeScript简称ts 一.为什么要学 1.1 减少bug,提高质量 强语言,语法等方面异常,编译阶段"提前"报错 支持面向对象,软件设计与工程化更为成熟,更容易做单元 ...

  3. .npmrc 项目的 默认安装配置

    .npmrc registry=http://192.168.77.105:8081/nexus/content/groups/npm-all/

  4. vscode git冲突 1. git stash 2. 更新代码 3. git stash pop 4.提交代码

    vscode git冲突 1. git stash 2. 更新代码 3. git stash pop 4.提交代码

  5. WPF之命令

    目录 命令系统的基本元素 基本元素之间的关系 小试命令 WPF的命令库 命令参数 命令与Binding的结合 近观命令 ICommand接口与RoutedCommand 自定义Command 定义命令 ...

  6. jquery之ajax中文乱码解决方案

    $.ajax({ dataType : 'json',type : 'POST',url : 'http://localhost/test/test.do',data : {id: 1, type: ...

  7. Ubuntu 16.04 LAMP(PHP7.0) 环境搭建并测试

    PS:要转载请注明出处,本人版权所有. PS: 这个只是基于<我自己>的理解, 如果和你的原则及想法相冲突,请谅解,勿喷. 前置说明   本文作为本人csdn blog的主站的备份.(Bl ...

  8. PAT甲级【1014 Waiting in Line】

    考察双向链表 import java.io.IOException; import java.io.InputStreamReader; import java.io.StreamTokenizer; ...

  9. 引领文旅新体验!3DCAT实时云渲染助力打造“永不落幕”的湾区文采会元宇宙

    2022年11月25日至27日,2022年粤港澳大湾区公共文化和旅游产品(东莞)采购会(简称"湾区文采会")在广东省东莞市文化馆举行. 文采会期间,文采会元宇宙线上虚拟展厅全新亮相 ...

  10. 3DCAT v2.1.3新版本发布,这三大功能更新你不容错过!

    3DCAT实时渲染云在近期发布了新的公有云v2.1.3的版本,本次主要更新了应用页的三项功能「语音交互设置」.「多点触控」.「音频信号位深」. 小编将对这三项更新进行讲解: 1. 调整语音通讯机制 新 ...