ReferenceEquals(object a, object b)
静态方法,判断两个引用类型对象是否指向同一个地址。即适用范围只能对于引用类型操作。对于任何两个值类型数据比较,即使是与自身比较都会返回False,因为调用这个方法时,值类型数据要进行装箱操作,转换为object类型,两次装箱,堆上会产生两个对象。
Console.WriteLine(ReferenceEquals(1, 1)); // Fals
这个方法可以用来验证字符串驻留,当字符串驻留时,不会产生新的字符串对象。
Console.WriteLine(ReferenceEquals("hello", "hello")); // True
Equals(object a, object b)
静态方法。如果两个比较对象均为null或者ReferenceEquals(a, b)返回True,则返回True。
Equals(object o)
虚方法,实例方法,可以重写。调用结果和调用Equals(object a, object b)是一样的,即如果两个对象均为null或者具有相同的引用就返回True,否则返回False。
任何类型都可以重写这个方法。
对于引用类型,即使引用类型包含很多成员,使用Equals比较引用类型时,仅仅考虑两个对象是否具有相同的引用,而不是逐个成员进行比较。所以对于引用类型,除非有其他判等逻辑,否则也不需要重写该方法。
System.ValueType值类型重写了该方法,仅仅比较值是否相等。如果值类型包含很多成员,例如结构体,则使用反射,取得所有成员然后逐个进行比较。为了避免反射造成性能损失,必须重写这个方法,即只需要遍历所有结构的属性并一一进行比较即可。倘若只是需要部分属性相等即可,那就更应该重写了。
struct Rectangle
{
double width { get; set; }
double height { get; set; }
public override bool Equals(object obj)
{
if (obj != null && obj is Rectangle)
{
// 强转为Rectangle类型
Rectangle rect = (Rectangle)obj;
// 遍历所有属性
return (rect.width == width) && (rect.height == height);
}
return false;
}
}
虽然字符串是引用类型,但它重写了该方法,行为和值类型一样,仅仅比较值是否相等。这是字符串行为看起来和值类型差不多的一个原因。
如果重写了Equals,还应该重写GetHashCode方法,否则会有警告消息。这两个方法是有连带关系的,如果两个对象相等,那么他的哈希码必须也相等,不过反过来,两个对象不相等,哈希码也有可能相等。
==和上面判等方法的关系
==是运算符重载,如果两边都是引用类型,则等同于ReferenceEquals。但string是特例,如果两边都是字符串或者值类型,==比较值是否相等。但是==不能用于结构体,除非重载它。
struct Rectangle
{
double Width { get; set; }
double Height { get; set; }
public Rectangle(double w, double h)
{
Width = w;
Height = h;
}
public override bool Equals(object obj)
{
if (obj != null && obj is Rectangle)
{
// 强转为Rectangle类型
Rectangle rect = (Rectangle)obj;
// 假如面积相等就算相等
return Width * Height == rect.Width * rect.Height;
}
return false;
}
}
此时
Rectangle rect1 = new Rectangle(1, 2);
Rectangle rect2 = new Rectangle(2, 1);
Console.WriteLine(ReferenceEquals(rect1, rect2)); // False
Console.WriteLine(rect1.Equals(rect2)); // True
如果没有重写Equals方法,则应该输出两个False,但是我们不能直接使用==判等。
Console.WriteLine(rect1==rect2); // ×
我们必须重载它:
public static bool operator ==(Rectangle rect1, Rectangle rect2)
{
return rect1.Equals(rect2);
}
public static bool operator !=(Rectangle rect1, Rectangle rect2)
{
return !rect1.Equals(rect2);
}
此时,可以通过编译并输出正确的值。重载==时,也必须重载!=。
GetHashCode
CLR中,任何对象任何实例都对应一个哈希码。GetHashCode能获取任意对象的哈希码。相同的对象哈希码必然相同,所以重写Equals必须重写GetHashCode,保证两者有相同的语义。
重写GetHashCode除了保证相同对象哈希码必然相同之外,还要保证哈希码是不可变的,所以如果哈希码基于对象的一些成员,那成员也应该是不可变的。通常借助于类型的唯一识别成员,例如id等。
重写Equals完整版本:
1. 重写Equals覆盖Object.Equals
2. 重写GetHashCode
3. 重载等号不等号
4. 实现IEquatable<T>接口,使得值类型比较大小时不会被隐式装箱
struct Rectangle : IEquatable<Rectangle>
{
double Width { get; set; }
double Height { get; set; }
public override bool Equals(object obj)
{
if (obj != null && obj is Rectangle)
{
// 强转为Rectangle类型
Rectangle rect = (Rectangle)obj;
// 遍历属性
return (rect.Width == Width) && (rect.Height == Height);
}
return false;
}
/// <summary>
/// 实现接口的方法,该方法会在传入参数为Rectangle时优先于Object.Equals方法
/// 避免了装箱
/// </summary>
/// <param name="other"></param>
/// <returns></returns>
public bool Equals(Rectangle other)
{
return (other.Width == Width) && (other.Height == Height);
}
public override int GetHashCode()
{
// 保证语义一致
return Width.GetHashCode() * Height.GetHashCode();
}
public static bool operator ==(Rectangle rect1, Rectangle rect2)
{
return rect1.Equals(rect2);
}
public static bool operator !=(Rectangle rect1, Rectangle rect2)
{
return !rect1.Equals(rect2);
}
}
ToString
虚方法,重写的可能性比较大,例如希望遍历对象的所有属性,然后打印。
GetType
返回对象指向的类型对象,返回值类型为System.Type。得到类型对象之后可以通过反射方式获得类型对象的成员,例如字段属性方法等。
Finalize
GC决定回收对象之后会调用这个方法,如果这时候要做一些额外的事,例如回收对象的非托管属性或对象,则应重写这个方法。