“世界上不会有两片完全相同的树叶”,这句话适用于现实世界。而在软件世界中,这句话变成了"世界上必须有两片完全相同的树叶",否则,很多事情无以为继。
当比较2个对象是否相等时,通常情况下:==操作符用来比较值类型,比较的是值;实例方法Equals和静态方法Object.ReferenceEquals比较引用类型,比较的是对象的地址。
在实际项目中,当比较2个引用类型对象时,我们的需求变为:通过依次比较2个对象的所有属性来判断是否相等。这时候,IEquatable接口就有了展示自己的机会。本篇主要包括:
- 使用==操作符比较值类型是否相等
- 实例方法Equals比较引用类型地址是否相等
- 实现IEquatable接口重写实例方法Equals()
使用==操作符比较值类型是否相等
class Program
{
static void Main(string[] args)
{
ComplexNumber a = new ComplexNumber(){Real = 4.5D, Imaginary = 8.4D};
ComplexNumber b = new ComplexNumber() { Real = 4.5D, Imaginary = 8.4D };
Console.WriteLine("{0} 是否等于{1}:{2}",a, b, CompareTwoComplexNumbers(a, b));
Console.ReadKey();
}
static bool CompareTwoComplexNumbers(ComplexNumber a, ComplexNumber b)
{
return ((a.Real == b.Real) && (a.Imaginary == b.Imaginary));
}
}
public class ComplexNumber
{
public double Real { get; set; }
public double Imaginary { get; set; }
public override string ToString()
{
return String.Format("{0}{1}{2}i",
Real,
Imaginary >= 0 ? "+" : "-",
Math.Abs(Imaginary));
}
}
以上,比较诸如int,double,DateTime,struct等值类型的时候,==操作符当然是不二之选。
实例方法Equals比较引用类型地址是否相等
class Program
{
static void Main(string[] args)
{
Guy darren1 = new Guy("Darren", 37, 100);
Guy darren2 = darren1;
Console.WriteLine(Object.ReferenceEquals(darren1,darren2));
Console.WriteLine(darren1.Equals(darren2));
Console.WriteLine(Object.ReferenceEquals(null, null));
darren2 = new Guy("Darren", 37, 100);
Console.WriteLine(Object.ReferenceEquals(darren1, darren2));
Console.WriteLine(darren1.Equals(darren2));
Console.ReadKey();
}
}
public class Guy
{
private readonly string name;
public string Name{get { return name; }}
private readonly int age;
public int Age{get { return age; }}
public int Cash { get; private set; }
public Guy(string name, int age, int cash)
{
this.name = name;
this.age = age;
Cash = cash;
}
public override string ToString()
{
return String.Format("{0} 今年 {1} 岁了,身价{2}", Name, Age, Cash);
}
}
以上,实例方法Equals()和静态方法Object.ReferenceEquals()适用于比较引用类型,而且比较的是对象的地址。
可是,如果我们想使用Equals()方法比较引用类型对象的各个属性,怎么办呢?
实现IEquatable接口重写实例方法Equals()
写一个派生于Guy的类EquatableGuy,并且实现IEquatable
class Program
{
static void Main(string[] args)
{
Guy darren1 = new EquatableGuy("Darren", 37, 100);
Guy darren2 = new EquatableGuy("Darren", 37, 100);
Console.WriteLine(Object.ReferenceEquals(darren1, darren2)); //False
Console.WriteLine(darren1.Equals(darren2)); //True
Console.ReadKey();
}
}
public class Guy
{
private readonly string name;
public string Name{get { return name; }}
private readonly int age;
public int Age{get { return age; }}
public int Cash { get; private set; }
public Guy(string name, int age, int cash)
{
this.name = name;
this.age = age;
Cash = cash;
}
public override string ToString()
{
return String.Format("{0} 今年 {1} 岁了,身价{2}", Name, Age, Cash);
}
}
public class EquatableGuy : Guy, IEquatable
{
public EquatableGuy(string name, int age, int cash) : base(name, age, cash){}
public bool Equals(Guy other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Equals(other.Name, Name) && other.Age == Age && other.Cash == Cash;
}
public override bool Equals(object obj)
{
if (!(obj is Guy)) return false;
return Equals((Guy) obj);
}
public override int GetHashCode()
{
const int prime = 397;
int result = Age;
result = (result * prime) ^ (Name != null ? Name.GetHashCode() : 0);
result = (result * prime) ^ Cash;
return result;
}
}
以上,值得注意的是:
当实现IEquatable
而且,使用IEquatable
如果我们项使用==操作符比较引用对象是否相等呢?我们可以通过重写操作符来实现。
我们再写一个EquatableGuy的子类EquatableGuyWithOverload,并且重写==操作符。
class Program
{
static void Main(string[] args)
{
var darren1 = new EquatableGuyWithOverload("Darren", 37, 100);
var darren2 = new EquatableGuyWithOverload("Darren", 37, 100);
Console.WriteLine(Object.ReferenceEquals(darren1, darren2)); //False
Console.WriteLine(darren1 == darren2); //True
Console.ReadKey();
}
}
public class EquatableGuyWithOverload : EquatableGuy
{
public EquatableGuyWithOverload(string name, int age, int cash) : base(name, age, cash){}
public static bool operator ==(EquatableGuyWithOverload left, EquatableGuyWithOverload right)
{
if (Object.ReferenceEquals(left, null)) return false;
else return left.Equals(right);
}
public static bool operator !=(EquatableGuyWithOverload left, EquatableGuyWithOverload right)
{
return !(left == right);
}
public override bool Equals(object obj)
{
return base.Equals(obj);
}
public override int GetHashCode()
{
return base.GetHashCode();
}
}
以上,子类EquatableGuyWithOverload重写了==操作符,由于其父类EquatableGuy已经重写了基类Object的Equals方法,所以在这里可以直接调用。
总结:通常情况下,使用==操作符比较值类型对象;使用实例方法Equals或静态方法Object.ReferenceEquals比较引用类型对象地址;如果想自定义比较逻辑,可以考虑实现IEquatable<>泛型接口,避免装箱、拆箱。
参考资料:
防止装箱落实到底,只做一半也是失败Understanding C#: Equality, IEquatable, and Equals()