if (this == null) Console.WriteLine("this is null"); 这句话一写,大家一定觉得荒谬,然而 if 内代码的执行却是可能的!本文讲介绍到底发生了什么。


 

制造一个 this 可以为 null 的程序

请看代码,这是我们的库函数:

namespace Walterlv.Demo
{
public class Foo
{
public void Test()
{
if (this == null) Console.WriteLine("this is null");
else Console.WriteLine("this is not null");
}
}
}

外面是这样调用的:

namespace Walterlv.Demo
{
public class Program
{
private static void Main()
{
Foo p = null;
p.Test();
}
}
}

这代码写出来,当然毫不犹豫地说——这会发生 NullReferenceException

然而……

现在我们改一改 Program 的 IL:

将关注重点放在图中红框标注的部分,那是调用 p.Test 的地方。

现在,我们将它从 callvirt 修改成 call

第一步:反编译 exe 成 IL:


# ildasm 在 C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.7.1 Tools\x64 路径下 ildasm /out=D:\Desktop\wdemo.il D:\Desktop\Walterlv.Demo\wdemo\bin\Debug\wdemo.exe

第二步:修改 IL,将 callvirt 修改成 call

IL_0004:  call   instance void Walterlv.Demo.Foo::Test()

第三步:重新编译 IL 成 exe


# ilasm 在 C:\Windows\Microsoft.NET\Framework64\v4.0.30319 路径下 lvyi> ilasm /out:D:\Desktop\wdemo.exe D:\Desktop\wdemo.il Microsoft (R) .NET Framework IL Assembler. Version 4.7.2556.0
Copyright (c) Microsoft Corporation. All rights reserved.
Assembling 'D:\Desktop\wdemo.il' to EXE --> 'D:\Desktop\wdemo.exe'
Source file is ANSI Assembled method Walterlv.Demo.Program::Main
Assembled method Walterlv.Demo.Program::.ctor
Assembled method Walterlv.Demo.Foo::Test
Assembled method Walterlv.Demo.Foo::.ctor
Creating PE file Emitting classes:
Class 1: Walterlv.Demo.Program
Class 2: Walterlv.Demo.Foo Emitting fields and methods:
Global
Class 1 Methods: 2;
Class 2 Methods: 2;
Resolving local member refs: 1 -> 1 defs, 0 refs, 0 unresolved Emitting events and properties:
Global
Class 1
Class 2
Resolving local member refs: 0 -> 0 defs, 0 refs, 0 unresolved
Writing PE file
Operation completed successfully

结果,现在再执行程序时,输出是 this is null

为什么此时 this 是 null

从名字上看,call 是为了调用非虚方法、静态方法或者基类方法的;而 callvirt 是为了调用虚方法的。前者在编译时就将确认调用了某个类的某个方法,而后者将在运行时动态决定应该调用哪个。

然而,当 IL 试图调用某个变量实例的一个方法时,由于不确定这个变量到底是不是实际的类型(还是基类型),所以都采用 callvirt 进行调用。call 在编译时就已确定调用,所以也没有加入 null 的判断;callvirt 却需要,因为通常都是实例使用。

于是,此次便出现了 null.Test() 这样诡异的调用。

一些建议和总结

虽然我们制造出了一个 this 可能为 null 的情况,即便库和调用方是分开开发的,但实际开发中其实并不需要考虑这样的问题。


参考资料

(C#)if (this == null)?你在逗我,this 怎么可能为 null!用 IL 编译和反编译看穿一切的更多相关文章

  1. Mysql命令-以NULL做where条件过滤时应该写 IS NULL;

    以NULL做where条件过滤时应该写 IS NULL;SELECT * FROM pet WHERE death IS NULL; SELECT * FROM pet WHERE death IS ...

  2. 特例模式(Special Case Pattern)与空对象模式(Null Pointer Pattern)—— 返回特例对象而非 null

    返回 null 值,基本上是在给自己增加工作量,也是给调用者添乱.只有一处没有检查返回的是否为 null,程序就会抛 NullPointerException 异常. 如果你打算在方法中返回 null ...

  3. 问题:oracle 排序 null值放在最后;结果: ORACLE中null的排序问题

    ORACLE中null的排序问题 关键字: oracle nulls 问题描述:    在平时的业务处理中,经常遇到要对业务数据进行排序,并且要对null值也做相应的排序.在Oracle中,进行Ord ...

  4. 【java】在controller层使用的检查单一字段不能为null和检查属性中某些字段不能为null的工具

    ========================================================================================= 代码参考地址:Git ...

  5. “equals”有值 与 “==”存在 “equals”只是比较值是否相同,值传递,==地址传递,null==a,避免引发空指针异常,STRING是一个对象==null,对象不存在,str.equals("")对象存在但是包含字符‘''

    原文链接:http://www.cnblogs.com/lezhou2014/p/3955536.html "equals" 与 "==" "equa ...

  6. scala(一)Nothing、Null、Unit、None 、null 、Nil理解

    相对于java的类型系统,scala无疑要复杂的多!也正是这复杂多变的类型系统才让OOP和FP完美的融合在了一起! Nothing: 如果直接在scala-library中搜索Nothing的话是找不 ...

  7. C# 很少人知道的科技

    本文来告诉大家在C#很少有人会发现的科技.即使是工作了好多年的老司机也不一定会知道,如果觉得我在骗你,那么请看看下面 因为C#在微软的帮助,已经从原来很简单的,到现在的很好用.在10多年,很少人知道微 ...

  8. 2019-11-29-C#-很少人知道的科技

    title author date CreateTime categories C# 很少人知道的科技 lindexi 2019-11-29 10:12:43 +0800 2018-03-16 08: ...

  9. 2019-5-27-C#-很少人知道的科技

    title author date CreateTime categories C# 很少人知道的科技 lindexi 2019-05-27 19:33:36 +0800 2018-03-16 08: ...

随机推荐

  1. STOMP协议规范【转】

    STOMP协议规范英文原文:http://stomp.github.io/stomp-specification-1.2.html STOMP协议规范译文原文:http://simlegate.com ...

  2. hand first python 选读(1)

    列表(list) 基本操作 比如说我要整理一个近期热映的电影列表: movies = ["venom", "My Neighbor Totor", " ...

  3. Linux命令详解-cp

    cp 命令用来复制文件或者目录,是Linux系统中最常用的命令之一.一般情况下,shell会设置一个别名,在命令行下复制文件时,如果目标文件已经存在, 就会询问是否覆盖,不管你是否使用-i参数.但是如 ...

  4. ansible入门七(实战)

    Ansible实战:部署分布式日志系统   本节内容: 背景 分布式日志系统架构图 创建和使用roles JDK 7 role JDK 8 role Zookeeper role Kafka role ...

  5. echarts在miniUI和ajax下动态渲染数据

    <script src="echarts.js"></script> <script src="jquery-3.3.1.min.js&qu ...

  6. LeetCode OJ:Search a 2D Matrix II(搜寻二维矩阵)

    Write an efficient algorithm that searches for a value in an m x n matrix. This matrix has the follo ...

  7. Redis数据库的学习及与python的交互

    1. 数据库简介: 当前主要使用两种类型的数据库:关系型数据库(RDBMS).非关系型数据库(NoSQL); (1). 关系型数据库RDBMS: 是建立在关系模型基础上的数据库,借助于集合代数等数学概 ...

  8. APUE学习笔记——3.文件共享与fcntl介绍

    基本概念 内核使用3个数据结构描述一个打开的文件:进程表.文件表.V节点表 首先了解3种数据结构的概念     1 进程表         每一个进程有一个进程表.进程表里是一组打开的文件描述符,如标 ...

  9. shell脚本实例二

    练习题一:对已经存在的用户做密码的修改等操作 vim  user_ctrl.sh                 ##进行如下编写 #!/bin/bashShow(){        read -p ...

  10. Linux:wc命令详解

    wc 用来计算数字 利用wc指令我们可以计算文件的Byte数.字数或是列数,若不指定文件名称,或是所给予的文件名为“-”,则wc指令会从标准输入设备读取数据. 语法 wc(选项)(参数) 选项 -c或 ...