前言:

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

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

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

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

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

方法调用指令介绍:

在.NET Emit 中,方法调用指令是一种关键的操作,它允许我们在运行时动态地调用各种方法。

这些指令提供了一种灵活的方式,可以在程序执行期间创建、修改和调用方法,从而实现了动态代码生成和操作的功能。

方法调用指令包括了一系列不同的操作码,每个操作码都代表了一种不同的调用方式,比如调用实例方法、静态方法或委托。

通过理解和应用这些方法调用指令,我们可以实现诸如动态代理、AOP(面向切面编程)、方法重写等高级功能,从而扩展了.NET平台的能力和灵活性。

在本文中,我们将深入探讨ILGenerator 指令方法中与方法调用相关的内容,包括不同调用指令的详细解释、示例和实践应用场景。

1、常用指令:Call 指令及 Callvirt 指令

以下是两种常见的方法调用指令及其详细说明:

  1. Call 指令:

    • 作用:用于调用静态方法、实例方法以及基类的虚拟方法
    • 使用方法:需要提供方法的签名和目标对象(如果是实例方法)。
    • 示例:
      // 调用静态方法
      IL.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) })); // 调用实例方法
      IL.Emit(OpCodes.Call, typeof(MyClass).GetMethod("InstanceMethod"));
  2. Callvirt 指令:

    • 作用:用于调用虚方法,会在运行时根据对象的实际类型进行分派。
    • 使用方法:需要提供方法的签名,调用时会自动获取对象的类型。
    • 示例:
      // 调用虚方法
      IL.Emit(OpCodes.Callvirt, typeof(BaseClass).GetMethod("VirtualMethod"));

这些指令提供了灵活的方法调用功能,可以在动态生成的代码中使用,也可以用于实现诸如反射、AOP等功能。

通过深入理解这些指令的工作原理和使用方法,我们可以更加灵活地操作.NET平台上的方法调用行为。

2、Call 指令和 Callvirt 指令的区别:

在面向对象的编程语言中,"Call" 和 "CallVirt" 通常用于描述方法(函数)的调用方式,它们之间的区别在于是否进行虚拟方法调用(Virtual Method Invocation)。

  1. Call(直接调用):当使用 "Call" 调用方法时,编译器会在编译时确定要调用的方法,这意味着它会直接调用指定类的方法,而不考虑实际运行时对象的类型。这种方式通常用于非虚方法(non-virtual method)或静态方法(static method),因为这些方法在编译时就已经确定了调用的目标。

  2. CallVirt(虚拟方法调用):而当使用 "CallVirt" 调用方法时,编译器会生成一段代码,在运行时根据实际对象的类型来确定要调用的方法。这意味着即使在编译时使用的是基类的引用或指针,但在运行时实际上调用的是子类的方法(如果子类重写了该方法)。这种方式通常用于虚方法(virtual method),以实现多态性(polymorphism)。

总的来说,"Call" 是在编译时确定调用的方法,而 "CallVirt" 则是在运行时根据对象的实际类型确定调用的方法,从而实现了多态性。

使用及其性能说明:

在多数实例方法的调用,使用 Call 方法调用,会有更优的性能(实例方法时:它减少了对象的Null检查与虚方法重写的寻找)。

3、辅助方法:EmitCall

看一下说明:

从参数的说明中,可以看出,它提供了基于Call、Callvirt、Newobj 三类指令的封装调用。

在使用过程中,对使用者容易造成混乱,代码也不美观,可以无视它。

4、方法指针(委托)调用:Calli 指令

在C#语法中,除了 unsafe 方法可以操作指针外,其它涉及指针(引用地址)的被封装后提供给使用的安全类型只有 ref、out、委托。

而涉及调用的只有委托,因此,下面来一个调用委托的示例代码:

单独的使用 Emit 的Calli 指令无法直接调用委托方法,我们需要使用它封装的辅助方法来使用。

看一下说明:

该方法提供基于 Calli 指令的封装,提供针对委托的调用,下面看一组示例代码。

调用示例:

 public static void PrintHello()
{
Console.WriteLine("Hello, world!");
} //...... ILGenerator il = methodBuilder.GetILGenerator(); // 加载一个委托实例到栈上
il.Emit(OpCodes.Ldftn, typeof(AssMethodIL_Call).GetMethod("PrintHello"));
// 使用 Calli 指令调用委托所指向的方法
il.EmitCalli(OpCodes.Calli, CallingConventions.Standard, typeof(void), null, null);
il.Emit(OpCodes.Ret); // 返回该值

生成的对照代码:

有点偏离我们理解的代码了,好在它能正常执行。

我们在动态方法中运行它:

说明:

Ldftn 指令:Load Function 的简写,加载方法的引用地址。 

总结:

本文探讨了.NET Emit 入门教程的第六部分,聚焦于ILGenerator中的方法调用指令。

通过详细分析 ILGenerator 的使用方法和方法调用指令,读者可以更深入地了解.NET平台下动态生成代码的实现机制。

通过本文的阅读,读者可以更加熟练地使用 ILGenerator 来动态生成高效、灵活的代码,为.NET应用程序的开发和优化提供更多可能性。

下一篇,我们继续探讨其它 IL 指令。

.NET Emit 入门教程:第六部分:IL 指令:6:详解 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. SaltStack 入门到精通第二篇:Salt-master配置文件详解

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

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

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

  10. kafka实战教程(python操作kafka),kafka配置文件详解

    kafka实战教程(python操作kafka),kafka配置文件详解 应用往Kafka写数据的原因有很多:用户行为分析.日志存储.异步通信等.多样化的使用场景带来了多样化的需求:消息是否能丢失?是 ...

随机推荐

  1. 【LeetCode栈与队列#01】队列的基本操作:用栈模拟队列和用队列模拟栈

    用栈实现队列 力扣题目链接(opens new window) 使用栈实现队列的下列操作: push(x) -- 将一个元素放入队列的尾部. pop() -- 从队列首部移除元素. peek() -- ...

  2. 【Azure 存储服务】App Service 访问开启防火墙的存储账号时遇见 403 (This request is not authorized to perform this operation.)

    问题描述 需要 App Service 访问开启防火墙的存储账号.存储账号中设置为允许选中的VNET访问,同时允许了信任的Azure服务的访问,但是仍然报错 "403 (This reque ...

  3. 【Azure 应用服务】"App Service"如何能判断自身网路没有问题?

    问题描述 当创建了一个App Service服务后,如何能判断服务自身网络链路路没有问题? 如果是用VM,通常用Ping可以判断.但是"网站应用App Service" 的Kudu ...

  4. SpringCloud zookeeper和consul 的介绍和基本搭建

    1. Spring Cloud 集成 zookeeper Spring Cloud 集成了 zookeeper ,通过一些简单的注释,就可以快速注册到zookeeper 服务中 并且支持Spring ...

  5. 问答:C程序为何for循环和while循环无法相互替代?

    百鸡百钱问题: C代码: include <stdio.h> main() { int cock, hen, chicken; for(cock=0;cock<=20;cock++) ...

  6. React函数式组件使用@emotion时一定要注意的问题!

    怎么说呢,一个坑,踩了两天,总觉得是useSate和input的传值方法问题 在useMemo和useCallback反复测试问题 最后没办法,通过最傻方式,一点点注释代码,发现了问题 const C ...

  7. Mysql使用limit深度分页优化

    1.背景: mysql使用select * limit offset, rows分页在深度分页的情况下.性能急剧下降. 例如:select * 的情况下直接⽤limit 600000,10 扫描的是约 ...

  8. springboot打印启动信息

    打印启动信息 转载自:www.javaman.cn 1 spring Bean实例化流程 基本流程: 1.Spring容器在进行初始化时,会将xml或者annotation配置的bean的信息封装成一 ...

  9. 22_播放器之使用SDL显示YUV视频

    简介 使用SDL实现简单的YUV播放器. 这里还需要使用到像素格式和计算图片大小,这两个我们直接使用ffmpeg来实现,因此需要使用ffmpeg的libavutil/avutil.h和libavuti ...

  10. 浅谈React与SolidJS对于JSX的应用

    React将JSX这一概念深入人心.但,并非只有React利用了JSX,VUE.SolidJS等JS库或者框架都使用了JSX这一概念.网上已经有大量关于JSX的概念与形式的讲述文章,不在本文的讨论范围 ...