65.9K
CodeProject 正在变化。 阅读更多。
Home

Equality in .NET 故事 - 第 5 部分

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2016 年 9 月 6 日

CPOL

6分钟阅读

viewsIcon

19207

本文是关于 .NET 中相等性工作原理的一系列文章的继续,它专门讨论相等性运算符如何处理引用类型。

引言

我希望在阅读了之前的文章后,您现在对 C# 相等性运算符如何处理 `int`、`float`、`double` 等原始类型有了更好的理解。在本文中,我们将重点介绍 C# 相等性运算符和 `Equals` 方法对于框架类库类型或用户定义的自定义类型的引用类型是如何表现的。

背景

本文是关于 .NET 中相等性工作原理的一系列文章的继续。目的是让开发人员更清楚地了解 .NET 如何处理不同类型的相等性。如果您还没有阅读过前面的部分,并且有兴趣从头开始阅读这个主题,您可以在这里找到所有已发布的部分。

 

上一篇文章(即第 4 部分)中,我们比较了使用 `==` 运算符和 `Object.Equals` 方法进行值类型相等性比较的结果,结果是相同的,但评估相等性的机制是不同的,因为为 `Object.Equals` 和 `==` 运算符生成的 IL 是不同的,这意味着 `==` 运算符在后台并不调用 `Object.Equals`,而是使用 CPU 寄存器来确定两个值类型变量是否相等。

 

== 运算符和引用类型

如果您还记得上一篇文章,我们看到了一个使用引用类型进行相等性比较的例子,它检查引用相等性。现在我们将修改同一个例子,看看在引用类型的情况下,相等性运算符会编译成什么。

class Program
{ 
    static void Main(String[] args)
    {
        Person p1 = new Person();
        p1.Name = "Ehsan Sajjad";
 
        Person p2 = new Person();
        p2.Name = "Ehsan Sajjad";
        
        Console.WriteLine(p1.Equals(p2));
        Console.WriteLine(p1 == p2);
        Console.ReadKey();
    } 
}

现在,我们将使用这两种检查相等性的方法进行测试,即 C# 的相等性运算符和 `Object` 类型的 `Equals` 方法。如果我们运行这个例子,我们将看到控制台打印出两次 `false`,这是预期的结果,因为 `Person` 是一个引用类型,而且那是 `person` 的两个不同实例,内存引用不同。

从输出中,我们可以轻松推断出,相等性运算符和 `Equals` 方法都检查引用类型的引用相等性,而这实际上就是正在发生的事情。因此,相等性运算符也检查引用相等性,而不是引用类型的价值相等性,这与 `Equals` 方法完全相同。

幕后发生了什么?

现在让我们检查为此示例生成的 IL 代码。要做到这一点,请打开 Visual Studio 命令提示符,打开它的方法是:转到“开始”菜单 >> “所有程序” >> “Microsoft Visual Studio” >> “Visual Studio Tools”>> “Developer Command Prompt”

在命令提示符中键入 `ildasm`,这将启动 `ildasm`,用于查看程序集中包含的 IL 代码。它会在您安装 Visual Studio 时自动安装,因此您无需安装它。

浏览您的可执行文件所在的文件夹,并使用“文件”菜单打开它。这将显示您的可执行文件的 IL 代码。

上述 C# 代码的 IL 代码如下所示:

如果我们查看 `p1.Equals(p2)` 的 IL 代码,没有惊喜,它通过调用 `Object` 的虚拟 `Equals` 方法来比较相等性,并且方法签名非常清晰,因为它们表明它需要一个对象,所以这实际上是对 `Object.Equals` 方法的虚拟调用,这是 C# 编译器选择的最佳类型的匹配方法。

现在,如果我们查看为对象相等性运算符比较生成的 IL 代码,它使用的指令与我们在上一部分中看到的整数示例中的指令完全相同。它没有调用任何方法来执行比较,只是将两个参数加载到求值堆栈上,并执行 `ceq`,这是一个专用的 IL 指令,用于检查相等性,可能使用 CPU 硬件。

您可能在想,这如何实现引用相等性?我们知道 `Person` 是一个类,它是一个引用类型,这意味着 `Person` 类型的任何变量包含的都是 `Person` 对象在托管堆上的内存地址。

参数 `p1` 和 `p2` 都包含内存地址,而您知道内存地址只是数字,这意味着它们可以使用 `ceq` 语句进行相等性比较,就像您在代码中声明的整数一样。事实上,这个 `ceq` 正在比较地址,以查看它们是否相等,换句话说,引用是否指向同一内存地址就是引用相等性。

摘要

  • 我们看到,`==` 运算符和 `Object.Equals` 方法调用在后台的工作方式不同,我们可以通过检查生成的 IL 代码来验证这一点。
  • 我们看到,对于引用类型,使用 `==` 运算符的结果与调用 `Object.Equals` 相同,但 IL 中 `==` 运算符的底层机制与 `Object.Equals` 不同,即它不使用 `Object.Equals`,而是使用 `ceq` 指令,该指令使用硬件指令比较内存地址。

您可能还想阅读

© . All rights reserved.