.Net中关于相等的问题
作者:Sweet-Tang 发布时间:[ 2017/6/28 10:45:43 ] 推荐标签:测试开发技术 .NET
在.Net框架中,如果您查看所有类型的的基类:System.Object类,将找到如下4个与相等判断的方法:
●static Equals()
●virtual Equals()
●static ReferenceEquals()
virtual GetHashCode()
除此之外,Microsoft已经提供了9个不同的接口,用于比较类型:
●IEquatable<T>
●IComparable
●IComparable<T>
●IComparer
●IComparer<T>
●IEqualityComparer
●IEqualityComparer<T>
●IStructuralEquatable
●IStructuralComparable
您是否真的理解方法这些方法和接口?如果使用不当,可能会产生致命的错误,并且还会破坏依赖于这些接口的集合。
接下来我们几篇博客来讨论这些方法和接口,重点关注的是如何正确使用这些方法和接口。
等于的疑惑
因为存在以下四种原因,会阻碍我们理解相等比较是如何执行:
引用相等与值相等
判断值相等的多种方式
浮点数的准确性
与OOP存在的冲突
引用相等与值相等
众所周知,在.Net框架中,引用类型在存储时不包含实际的值,它们包含一个指向内存中保存实际值位置的指针,这意味着对于引用类型,有两种方式来衡量相等性;两个变量都是指向内存中相同的位置,我们称为引用相等,也可以说是同一个对象;两个变量指定的位置包括相同的值, 即使它们指向内存中不同的位置,我们称其之为值相等。
我们可以使用如下示例来说明上述几点:
1 class Program
2 {
3 static void Main(String[] args)
4 {
5 Person p1 = new Person();
6 p1.Name = "Sweet";
7
8 Person p2 = new Person();
9 p2.Name = "Sweet";
10
11 Console.WriteLine(p1 == p2);
12 }
13 }
我们实例化了两个Person对象,并且都包含相同的Name属性;显然,上述两个Person类的实例是相同的,它们包含相同的值, 但是运行示例代码时,控制台打印输出的是False,这意味着它们不相等。
这是因为在.Net框架中,对于引用类型默认判断方式是引用相等,换句话说,"=="运算符会判断这两个变量是否指向内存中相同的位置,因此在本示例中,尽管Person类的两个实例包含的值相同,但它们是单独的实例,变量p1和p2两者分别指内存不同的位置。
引用相等执行速度非常快,因为只需检查两个变量是否指向内存中相同的地址,而对于值相等要慢一些。例如,如果Person类不是只有一个字段和属性,而是具有很多,想检查Person类的两个实例是否具有相同的值,您必须检查每个字段或属性。C#中并没有提供运算符用于检查两个类型实例的值是否相等,如果由于某种原因想要实现这种功能,您需要自己编写代码来做到这一点。
现在来看另一个例子:
1 class Program
2 {
3 static void Main(String[] args)
4 {
5 string s1 = "Sweet";
6
7 string s2 = string.Copy(s1);
8
9 Console.WriteLine(s1 == s2);
10 }
11 }
上面的代码与前一个示例代码非常相拟,但是在这个示例中,我们使用"=="运算符判断两个相同的String类型的变量。我们先给变量s1付值后,然后将变量s1的值复制并付给另一个变量s2,运行这段代码,在控制台打印输出为True,我们可以说两个String类型的变量是相等的。
如果"=="运算符判断的方式使用的是引用相等, 程序运行时控制台打印输出的应该是False,但是用于String类型时"==" 运算符判断方式是值相等。
引用相等与值类型
引用相等和值相等的问题仅适用于引用类型,对于未装箱的值类型,如整数,浮点型等,变量存储时已经包含了实际的值,这里没有引用的概念,意味着相等是比较值。
以下代码比较两个整数,两者是相等的,因为"=="运算符将比较变量实际的值。
1 class Program
2 {
3 static void Main(String[] args)
4 {
5 int num1 = 2;
6
7 int num2 = 2;
8
9 Console.WriteLine(num1 == num2);
10 }
11 }
在上面的代码中,"=="运算符是将变量num1存储的值与变量num2存储的值进行比较。但是,如果我们修改此代码并将这两个变量转换为Object类型,代码如下:
1 int num1 = 2;
2
3 int num2 = 2;
4
5 Console.WriteLine((object)num1 == (object)num2);
运行示例代码,您看到结果将是False,与上一次代码的结果相反。这是因为Object类型是引用类型,所以当我们将整数转换为Object类型,实际是两个整数被装箱后两个不同的引用实例,"=="运行符比较的是两个对象的引用,而不是值。
好像上面的例子很少见,因为通常情况下我们不会将值类型转换为引用类型,但是存在另一种常见的情况,我们需要将值类型转换为接口。
1 Console.WriteLine((IComparable<int>)num1 == (IComparable<int>)num2);
为了说明这种情况,我们修改示例代码,将int类型的变量转换为接口ICompareable<int>;这是.Net框架提供的一个接口,int类型实现这个接口(关于这个接口我们将其它的博客中讨论)。
在.Net框架中,接口实际上是引用类型,如果我们运行这段代码,返回的结果是False。因此,在将值类型转换为接口时,您需要特别小心,如果您进行相等检查,返回的结果比较的是引用相等。
"=="运算符
如果C#对值类型和引用类型分别提供不同的运算符来判断相等,也许这些代码都不是问题,可惜C#只提供一个"=="运算符,也没有显示的方式来告诉运算符实际判断的类型是什么。例如,下面这一行代码:
1 Console.WriteLine(var1 == var2)
我们不知道上述的"=="运算符采用的是引用相等还是值相等,因为需要知道"=="运行算判断的是什么类型,事实上C#也是这样设计的。
在上述内容中,我们详细介绍了"=="运算符的作用及判断方式,在阅读完这篇博客之后,我希望您能比其他开发者更多的了解当使用"=="判断条件的时候到底发生了什么,您也能够更进一步了解两个对象之间的是如何判断相等的。
判断值相等的多种方式
复杂的值相等的还存在另一个问题,通常存在多种方式来比较指定类型的值,String类型是一个好的例子。
经常存在这样一种情况,字符串比较时,可能需要忽略字母的大小写;例如:在一个电商平台中搜索一个英文名称的商品,此时比较商品名称时,我们需要忽略大小写,幸运的是在Sql Server数据库中,默认使用的是这种比较方式,在.Net框架中有没有办法满足我们的要求?幸运的是在String类型中提供了一个Equals方法的重载,看下面的示例:
1 string s1 = "SWEET";
2
3 string s2 = "sweet";
4
5 Console.WriteLine(s1.Equals(s2,StringComparison.OrdinalIgnoreCase));
在程序中运行上面的示例,在控制台打印输出的是True。
当然.Net框架也提供了多种方式来判断类型的值相等。常见方法,类型可以通过实现IEquatable<T>接口定义类型默认值相等的判断方式。如果您不想重新定义自己的类型,.Net框架也提供了其另一种机制来实现一点,通过实现IEqualityComparer<T>接口来自定义一个比较器,用于判断同一种类型的两个实例是否相等。例如:如果您想忽略String类型中的空格进行比较,可以自己定义一个比较器,来实现这一功能。
.Net还提供了一个接口ICompareable<T>,用于判断当前类型大于或小于的比较,也可以通过IComparer<T>接口来实现一个比对器,一般在对象排序时,会用到这些接口。
相关推荐
更新发布
功能测试和接口测试的区别
2023/3/23 14:23:39如何写好测试用例文档
2023/3/22 16:17:39常用的选择回归测试的方式有哪些?
2022/6/14 16:14:27测试流程中需要重点把关几个过程?
2021/10/18 15:37:44性能测试的七种方法
2021/9/17 15:19:29全链路压测优化思路
2021/9/14 15:42:25性能测试流程浅谈
2021/5/28 17:25:47常见的APP性能测试指标
2021/5/8 17:01:11