一、引言
对于对象的比较,C#提供了很多种比较方法,以及==和!=等运算来进行对象的同一性和相等性的比较,但我们在实际的编写代码中,可能会混淆各个方法的用法,在此废话不多说,我们一起来看些这几种方法的具体用法及含义。
二、Object. Equals (object obj)
这个方法是Object提供的虚方法,对于该方法,我们先区别引用类型和值类型的用法
对于引用类型,如果没有重写Equals,则默认比较对象的同一性,即比较引用地址,C#中大多数引用类型都重写了该方法,例如String类型比较的字符串是否相等
对于值类型,默认比较的是对象的相等性(即struct中所有字段的相等性)因为struct类型继承于System.ValueType而ValueType类中重写了Object中的Equals方法,代码具体实现可通过Reflector工具查看
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Demo
{
public struct MyStruct
{
public int item;
public MyStruct(int i)
{
item = i;
}
}
public class MyClass
{
private int item;
public int Item
{
get { return item; }
set { item = value; }
}
public MyClass(int i)
{
item = i;
}
public MyClass() { }
}
public class MyNewClass
{
private int item1;
public int Item1
{
get { return item1; }
set { item1 = value; }
}
private float item2;
public float Item2
{
get { return item2; }
set { item2 = value; }
}
public MyNewClass(int i, float j)
{
item1 = i;
item2 = j;
}
public MyNewClass() { }
//重写了Object.Equals
public override bool Equals(object obj)
{
if (obj == null) return false;
MyNewClass o = obj as MyNewClass;
if (o == null) return false;
return item1 == o.item1 && item2 == o.item2;
}
}
class Program
{
static void Main(string[] args)
{
//没有重写Object.Equals的引用类型
MyClass mc1 = new MyClass(1);
MyClass mc2 = new MyClass(1);
MyClass mc3 = mc2;
//值类型
MyStruct stc1 = new MyStruct(1);
MyStruct stc2 = new MyStruct(1);
MyStruct stc3 = new MyStruct(2);
//重写了Object.Equals的引用类型
MyNewClass nc1 = new MyNewClass(1, 1.0f);
MyNewClass nc2 = new MyNewClass(1, 1.0f);
MyNewClass nc3 = new MyNewClass(2, 1.0f);
//比较没有重写Object.Equals的引用类型实例对象
Console.WriteLine(mc1.Equals(mc2));//比较引用地址 返回False
Console.WriteLine(mc2.Equals(mc3));//比较引用地址 返回True
//比较值类型的实例对象
Console.WriteLine(stc1.Equals(stc2));//ValueType重写了Equals方法,比较的是结构体中的所有字段 返回True
Console.WriteLine(stc2.Equals(stc3));//比较结构体中的所有字段 返回False
//比较重写了Object.Eqauls的引用类型实例对象
//比较实例对象中所有字段的相等性
Console.WriteLine(nc1.Equals(nc2));//比较对象中实例字段相等性 返回Ture
Console.WriteLine(nc2.Equals(nc3));//返回False
Console.Read();
}
}
}
输出结果:
三、Object. Equals (object objA,object objB)
这个方法是Object对象提供的静态方法
这个方法比较的过程是这样的:objA如果和objB引用相同实例或者两者同时为null时返回True,如果不满足前者情况,则返回objA.Equals(objB)的结果
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Demo
{
public struct MyStruct
{
public int item;
public MyStruct(int i)
{
item = i;
}
}
public class MyClass
{
private int item;
public int Item
{
get { return item; }
set { item = value; }
}
public MyClass(int i)
{
item = i;
}
public MyClass() { }
}
public class MyNewClass
{
private int item1;
public int Item1
{
get { return item1; }
set { item1 = value; }
}
private float item2;
public float Item2
{
get { return item2; }
set { item2 = value; }
}
public MyNewClass(int i, float j)
{
item1 = i;
item2 = j;
}
public MyNewClass() { }
//重写了Object.Equals
public override bool Equals(object obj)
{
if (obj == null) return false;
MyNewClass o = obj as MyNewClass;
if (o == null) return false;
return item1 == o.item1 && item2 == o.item2;
}
}
class Program
{
static void Main(string[] args)
{
MyClass mc1 = new MyClass(1);
MyClass mc2 = new MyClass(1);
MyClass mc3 = mc2;
MyStruct stc1 = new MyStruct(1);
MyStruct stc2 = new MyStruct(1);
object stc3 = stc2;
MyNewClass nc1 = new MyNewClass(1, 1.0f);
MyNewClass nc2 = new MyNewClass(1, 1.0f);
Console.WriteLine(object.Equals(null, null));//两者都为null 返回True
Console.WriteLine(object.Equals(mc1, mc2));//返回的是mc1.Equals(mc2)的结果 返回False
Console.WriteLine(object.Equals(mc2, mc3));//返回的是mc2.Equals(mc3)的结果 返回True
//对于值类型,调用object.Equals静态方法时,会发生装箱操作
//实际比较时,返回是MyStruct结构体中重写的Equals方法
Console.WriteLine(object.Equals(stc1, stc2));//返回的是stc1.Equals(stc2)的结果 返回True
//stc2发生两次装箱过程
//1、object stc3=stc2 stc2在GC堆上装箱,并将一个地址返回给stc3
//2、调用object.Equals(stc2,stc3),stc2在GC又装箱一次,并将引用地址返回给第一个形参
//两次装箱后,引用地址不同,实际比较时,返回的是MyStruct结构体中重写的Equals方法
Console.WriteLine(object.Equals(stc2, stc3));//返回的是stc2.Equals(stc3)的结果,返回True
Console.WriteLine(object.Equals(nc1, nc2));//返回的是nc1.Equals(nc2)的结果,返回True
Console.Read();
}
}
}
输出结果:
四、Object. ReferenceEquals (object objA,object objB)
这个方法是Object对象提供的静态方法,该方法只比较objA和objB是否引用同一个对象实例,且该方法对值类型的比较永远返回False.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Demo
{
public struct MyStruct
{
public int item;
public MyStruct(int i)
{
item = i;
}
}
public class MyClass
{
private int item;
public int Item
{
get { return item; }
set { item = value; }
}
public MyClass(int i)
{
item = i;
}
public MyClass() { }
}
class Program
{
static void Main(string[] args)
{
MyClass m1 = new MyClass(1);
MyClass m2 = new MyClass(1);
MyClass m3 = m2;
MyStruct s1 = new MyStruct(1);
MyStruct s2 = new MyStruct(1);
MyStruct s3 = s2;
Console.WriteLine(object.ReferenceEquals(m1, m2));//比较引用地址 返回False
Console.WriteLine(object.ReferenceEquals(m2, m3));//比较引用地址 返回True
Console.WriteLine(object.ReferenceEquals(s1, s2));//s1与s2装箱后引用的地址不一致,比较引用值永远返回False
Console.WriteLine(object.ReferenceEquals(s3, s2));//返回False
Console.Read();
}
}
}
输出结果:
五、==和!= 运算符重载
“==”和“!=”对于引用类型和值类型比较方式主要有以下不同
1、 对于引用类型,如果没有重载”==”和“!=”操作符的情况下,默认比较的是引用地址,很多.NET引用类型都重载了”==“和”!=“操作符,例如String类的”==“比较的是两个字符串是否相同
2、 对于.NET的基元值类型(short、Int16、Int32、Boolean等)在调用“==“和”!=”,CLR会实现内置的比较方式
3、 对于没有同重载“==”和“!=”的值类型,调用“==”和“!=”会引起编译器错误
通常我们设计一个类时,如果重写了Object.Equals(object o)时,一般也会重载“==“和”!=“操作符,并且重载“==”操作符调用重写的Equals方法.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Demo
{
public struct MyStruct
{
public int item;
public MyStruct(int i)
{
item = i;
}
}
public struct MyNewStruct
{
public int item;
public MyNewStruct(int i)
{
item = i;
}
public static bool operator ==(MyNewStruct m1, MyNewStruct m2)
{
return m1.Equals(m2);
}
public static bool operator !=(MyNewStruct m1, MyNewStruct m2)
{
return !m1.Equals(m2);
}
}
public class MyClass
{
private int item;
public int Item
{
get { return item; }
set { item = value; }
}
public MyClass(int i)
{
item = i;
}
public MyClass() { }
}
public class MyNewClass
{
private int item;
public int Item
{
get { return item; }
set { item = value; }
}
public MyNewClass(int i)
{
item = i;
}
//重写了Object.Equals方法
public override bool Equals(object obj)
{
if (obj == null) return false;
if (this.GetType() != obj.GetType()) return false;
MyNewClass m = obj as MyNewClass;
return item.Equals(m.item);
}
//重载“==”操作符编译器要求同时必须要重载"!="
public static bool operator ==(MyNewClass m1, MyNewClass m2)
{
return m1.Equals(m2);
}
public static bool operator !=(MyNewClass m1, MyNewClass m2)
{
return !m1.Equals(m2);
}
}
class Program
{
static void Main(string[] args)
{
//基元值类型
Int32 i = 3;
Int32 j = 3;
//自定义值类型(没能重载”==”和“!=”)
MyStruct s1 = new MyStruct(1);
MyStruct s2 = new MyStruct(1);
//自定义值类型(重载了“==”和“!=”)
MyNewStruct ns1 = new MyNewStruct(1);
MyNewStruct ns2 = new MyNewStruct(1);
//没有重载“==”和“!=”的引用类型
MyClass c1 = new MyClass(1);
MyClass c2 = new MyClass(1);
MyClass c3 = c2;
//重载了“==”和”!=“的引用类型
MyNewClass nc1 = new MyNewClass(1);
MyNewClass nc2 = new MyNewClass(1);
Console.WriteLine(i == j);//基元值类型==比较 返回True
//自定义值类型(汉有重载==)调用==比较时,编译器提示错误
//Console.WriteLine(s1 == s2);
Console.WriteLine(ns1 == ns2);//自定义结构体重载了==操作符,返回 True
Console.WriteLine(c1 == c2);//没有重载==操作符的引用类型比较,比较引用地址 返回 False
Console.WriteLine(c2 == c3);//没有重载==操作符的引用类型比较,比较引用地址 返回 True
Console.WriteLine(nc1 == nc2);//重载了==操作符的引用类型比较,调用nc1.Equals(nc2) 返回True
Console.Read();
}
}
}
输出结果:
此外我们要注意上面一段在代码在编译时,编译器提示有几个警告:
一个类型在重写了Equals方法或者重载了运算符“==、!=“之后,之所以编译器警告要同时定义GetHashCode,是因为在System.Collections.Hashtable类型、System.Collections.Genceric.Dictionary类型以及其他的一些集合中,要求两个对象为了相等,必须具胡相同的哈稀码,所以如果重写了Equals,编译器会提醒你,还应重写GetHashCode,确保相等性算法和对象哈稀算法一致,对于哈稀算法,在此篇不做讨论