.NET中的Object类学习1_Object类简介&Equals方法

17 篇文章 10 订阅


1 Object类简介

1.1 定义 Definition

命名/名称空间:System
程序集:System.Runtime.dll

支持.NET类层次结构中的所有类,并为派生类提供低级/底层服务。(即对.NET中的所有类都提供底层支持)
它是所有.NET类的最终基类;是类型层次结构的根(the root of the type hierarchy)。

注意
这边说Object是类型层次结构的根,为什么是类型(type)而不是类(class)。
大概是因为Object是.NET系统原生的一部分。

public class Object

1.2 示例 Examples

下面示例定义了一个派生(derived)自Object类的Point类型,并重写(override)了Object类的多个虚方法。此外,该示例还演示了如何调用Object类的静态方法和实例方法。

using System;
// Point类派生自 System.Object
class Point
{
	public int x, y;

	public Point(int x, int y)
	{
		this.x = x;
		this.y = y;
	}

	public override bool Equals(object obj)
	{
		// 若类型不同,那必然不等价
		if (obj.GetType() != this.GetType()) return false;
		// 若x 和 y 字段匹配,则返回true
		var other = (Point)obj;
		return (this.x == other.x) && (this.y == other.y);
	}
	
	// 返回 x和y字段的XOR(eXclusive OR,异或)
	public override int GetHashCode()
	{
		return x ^ y;
	}

	// 返回point值为string
	public override String ToString()
	{
		return $"({x}, {y})";
	}

	// 通过简单字段的赋值来返回point对象的一份拷贝/副本 
	public Point Copy()
	{
		return (Point) this.MemberwiseClone();
	}
}

public sealed class App
{
	static void Main()
	{
		// 构造一个Point对象
		var p1 = new Point(1,2);
		
		// 使另一个Point对象为第一个Point的副本
		var p2 = p1.Copy();

		// 使另一个Point变量引用第一个Point对象
		var p3 = p1;

		// 下面行会显示false,因为p1和p2引用了两个不同对象
		Console.WriteLine(Object.ReferenceEquals(p1, p2));

		// 下面行显示true,因为虽然p1和p2引用了不同对象,但对象有着相同的值
		Console.WriteLine(Object.Equals(p1, p2));
		
		// 下面行显示true,因为p1和p3引用了一个对象
		Console.WriteLine(Object.ReferenceEquals(p1, p3));

		// 下面行显示 p1的值为 (1, 2)
		Console.WriteLine($"p1的值为{p1.ToString()}");	 
	}
}
// 该代码示例产生以下输出:
// False
// True
// True
// p1的值为(1, 2)

1.3 备注 Remarks

编程语言通常不要求一个类声明继承自Object,因为该继承是隐式
在这里插入图片描述

比如,你在代码中随便定义一个.NET中的类:

FileInfo f = new FileInfo(@"yourPath");
f.GetType();

F12追踪上面 FileInfo 的类层次关系,你会发现,顶层类并不是 Object

但是,它可以调用GetType()方法(以及上面提到的Object中的各种方法),通过追踪GetType()可以进入到Object内。
在MSDN上查询文档,也确实说它是Object的。

这就是所谓的隐式继承吧。

由于.NET中所有类均派生自Object,因此Object类中定义的每个方法在系统中的所有对象都可用。
派生类可以重写其中一些方法,包括:

  • Equals - 支持对象间的比较。
  • Finalize - 在对象自动回收前执行清理操作。
  • GetHashcode - 生成与对象的值相对应的数字以支持哈希表的使用。
  • ToString - 生成一个人可读(human-readable)的文本字符串来描述该类的实例。

1.3.1 性能考虑因素 Performance Considerations

若你正在设计一个必须处理任何类型对象的类(比如集合),你可以创建接受Object类实例的类成员。不过,装箱(boxing)和拆箱(unboxing)一个类型的过程会带来性能成本。若你知道你的新类会经常处理某些值类型,你可以用两种策略来最小化装箱的成本:

  • 创建一个通用的方法来接受Object类型,以及一组特定类型(type-specific)的方法重载(overload),这些重载方法用来接受你所希望类频繁处理的每个值类型。如果存在接受调用参数类型的特定类型的方法,则不会发生装箱,并且特定类型的方法会被调用。如果没有与调用参数类型匹配的方法参数,则对参数进行装箱并调用通用方法。
  • 设计你的类型和它的成员以使用泛型(generics)。当你创建类的实例并指定泛型类型参数时,公共语言运行时(CLR)会创建封闭的泛型类型。泛型方法是特定于类型的,无需装箱调用参数即可调用。

尽管有时需要开发通用的类,以接受和返回Object类型的通用类,但你还是可以通过提供特定类型的类来处理常用类型以提高性能。例如,提供特定于setting和getting Boolean值的类可以消除装箱和拆箱布尔值的成本。

2 构造函数 Constructor

用于初始化一个新的Object类的实例。

public Object();

3 方法

3.1 Equals

3.1.1 定义

判断两个对象实例是否相等(相等性)。

3.1.2 重载

有两种重载,

Equals(Object)  用法: a.Equals(b);
Equals(Object, Object) 用法: Equals(a, b);

3.1.3 Equals(Object)

确定指定对象与当前对象是否相等。

// 参数 Object obj
// 表示要与当前对象比较的对象
// 返回值 Boolean
// 若指定对象和当前对象相等,返回true;否则,返回false

public virtual bool Equals(Object? obj);
3.1.3.1 示例

下面示例展示了一个重写了 Equals 方法以提供值相等性的 Point 类,以及一个从 Point 类派生的 Point3D 类。

由于 Point 重写 Object.Equals(Object) 来测试值是否相等,因此不会调用 Object.Equals(Object) 方法。不过, Point3D.Equals 调用 Point.Equals ,因为 Point 实现了 Object.Equals(Object) (参考基类,base关键字) 。
在这里插入图片描述

using System;

class Point
{
	protected int x, y;

	public Point() : this(0, 0)
	{ }
	
	public Point(int x, int y)
	{
		this.x = x;
		this.y = y;
	}

	public override bool Equals(Object obj)
	{
		// 检查是否为null并比较运行时(run-time)类型
		if ((obj == null) || !this.GetType().Equals(obj.GetType()))
		{
			return false;
		}
		else
		{
			Point p = (Point) obj;
			return (x == p.x) && (y == p.y);
		}
	}

	public override int GetHashCode()
	{
		return (x <<2) ^ y;
	}

	public override string ToString()
	{
		return String.Format("Point({0}, {1})", x, y);
	}	
}

sealed class Point3D : Point
{
	int z;
	
	public Point3D(int x, int y, int z) : base(x, y)
	{
		this.z = z;
	}
	
	public override bool Equals(Object obj)
	{
		Point3D pt3 = obj as Point3D;
		if (pt3 == null)
			return false;
		else
			return base.Equals((Point)obj) && z == pt3.z;
	}

	public override int GetHashCode()
	{
		return (base.GetHashCode() << 2) ^ z;
	}
	
	public override String ToString()
	{
		return String.Format("Point({0}, {1}, {2})", x, y, z);
	}
}

class Example
{
	public static void Main()
	{
		Point point2D = new Point(5, 5);
		Point3D point3Da = new Point3D(5, 5, 2);
		Point3D point3Db = new Point3D(5, 5, 2);
		Point3D point3Dc = new Point3D(5, 5, -1);

		Console.WriteLine("{0} = {1}: {2}",
			point2D, point3Da, point2D.Equals(point3Da));
		Console.WriteLine("{0} = {1}: {2}",
			point2D, point3Db, point2D.Equals(point3Db));
		Console.WriteLine("{0} = {1}: {2}",
			point3Da, point3Db, point3Da.Equals(point3Db));
		Console.WriteLine("{0} = {1}: {2}",
			point3Da, point3Dc, point3Da.Equals(point3Dc));
	}
}

// 示例输出如下:
//       Point(5, 5) = Point(5, 5, 2): False
//       Point(5, 5) = Point(5, 5, 2): False
//       Point(5, 5, 2) = Point(5, 5, 2): True
//       Point(5, 5, 2) = Point(5, 5, -1): False

Point.Equals 方法会检查以确保 obj 参数不为null,并且它引用与此对象相同类型的实例。如果任一检查失败,该方法将返回false。

Point.Equals 方法调用 GetType 方法来确定两个对象的运行时类型是否相同。

运行时类型 run-time type
运行时类型与编译时类型(Compile-time type)放一起说。
变量可以具有不同的编译时类型和运行时类型。编译时类型是源代码中变量的声明或推断类型。运行时类型是该变量引用实例的类型。通常两者是一样的,如下所示:

string message = "This is a string of characters";

下面的例子中,编译时类型不同于运行时类型:

object anotherMessage = "This is another string of characters";
IEnumerable<char> someCharacters = "abcdefghijklmnopqrstuvwxyz";

上面例子中,运行时类型都是 string 。第一行的编译时类型是 object ,第二行是 IEnumerable
若一个变量的两种类型不同,那么理解编译时类型和运行时类型何时适用非常重要。编译时类型决定编译器采取的所有动作。这些编译器动作包括调用解析、重载解析以及可用的隐式和显式强制转换。运行时类型确定在运行时解析的所有动作(actions)。这些运行时动作包括调度虚方法调用、评估 is 和 switch表达式,以及其他类型的测试API。

若方法使用C#中 obj is Point或VB中 TryCast(obj, Point)形式的检查,则在 objPoint 派生类的实例的情况下,检查将返回 true ,即使 obj 和当前实例不属于同一运行时类型。验证两个对象的类型相同后,该方法将 obj 转换为 Point 类型,并返回比较两个对象的实例字段的结果。

Point3D.Equals 中,继承的 Point.Equals 方法重写了 Object.Equals(Object)
因为 Point3D 是密封类(sealed class,在VB中是 NotInheritable ),因此以C#中的 obj is Point或 VB中的 TryCast(obj, point)形式的检查足以确保 objPoint3D 对象。如果它是 Point3D 对象,则将其转换为 Point 对象并传递给 Equals 的基类实现。仅当继承的 Point.Equals 方法返回true时,该方法才会比较派生类中引入的 z 字段。

下面示例定义了一个 Rectangle 类,该类在内部将矩形实现为两个 Point 对象。 Rectangle 类也重写了 Object.Equals(Object) 以提供值相等的比较。

using System;

Class Rectangle
{
	private Point a, b;
	public Rectangle(int upLeftX, int upLeftY, int downRightX, int downRightY)
	{
		this.a = new Point(upLeftX, upLeftY);
		this.b = new Point(downRightX, downRightY);
	}

	public override bool Equals(Object obj)
	{
		// 对两个矩形执行相等性检查(一对Point对象)
		if (obj == null || GetType() != obj.GetType())
			return false;
		Rectangle r = (Rectangle)obj;
		return a.Equals(r.a) && b.Equals(r.b);
	}

	public override int GetHashCode()
	{
		return Tuple.Create(a, b).GetHashCode();
	}

	public override String ToString()
	{
		return String.Format("Rectangle({0}, {1}, {2}, {3})",
			a.x, a.y, b.x, b.y);
	}
}

class Example
{
	public static void Main()
	{
		Rectangle r1 = new Rectangle(0, 0, 100, 200);
		Rectangle r2 = new Rectangle(0, 0, 100, 200);
		Rectangle r3 = new Rectangle(0, 0, 150, 200);

		Console.WriteLine("{0} = {1}: {2}", r1, r2, r1.Equals(r2));
		Console.WriteLine("{0} = {1}: {2}", r1, r3, r1.Equals(r3));
		Console.WriteLine("{0} = {1}: {2}", r2, r3, r2.Equals(r3));
	}
}
// 示例输出如下:
//    Rectangle(0, 0, 100, 200) = Rectangle(0, 0, 100, 200): True
//    Rectangle(0, 0, 100, 200) = Rectangle(0, 0, 150, 200): False
//    Rectangle(0, 0, 100, 200) = Rectangle(0, 0, 150, 200): False

某些语言(例如C#和VB)支持运算符重载(operator overloading)。当类型重载等号运算符时,还必须重写 Equals(Object) 方法以提供相同的功能。这通常是根据重载的等号运算符编写Equals(Object)方法来完成的,如下所示:

using System;

public struct Complex
{
	public double re, im;

	public override bool Equals(Object obj)
	{
		return obj is Complex && this == (Complex)obj;
	}

	public override int GetHashCode()
	{
		return Tuple.Create(re, im).GetHashCode();
	}

	public static bool operator == (Complex x, Complex y)
	{
		return x.re == y.re && x.im == y.im;
	}

	public static bool operator != (Complex x, Complex y)
	{
		return !(x == y);
	}

	public override String ToString()
	{
		return String.Format("({0}, {1})", re, im);
	}
}

class MyClass
{
	public static void Main()
	{
		Complex cmplx1, cmplx2;

		cmplx1.re = 4.0;
		cmplx1.im = 1.0;

		cmplx2.re = 2.0;
		cmplx2.im = 1.0;

		Console.WriteLine("{0} <> {1}: {2}", cmplx1, cmplx2, cmplx1 != cmplx2);
		Console.WriteLine("{0} = {1}: {2}", cmplx1, cmplx2, cmplx1.Equals(cmplx2));

		cmplx2.re = 4.0;

		Console.WriteLine("{0} = {1}:{2}", cmplx1, cmplx2, cmplx1 == cmplx2);
		Console.WriteLine("{0} = {1}:{2}", cmplx1, cmplx2, cmplx1.Equals(cmplx2));
	}
}
// 示例输出如下:
// 		 (4, 1) <> (2, 1): True
//       (4, 1) = (2, 1): False
//       (4, 1) = (4, 1): True
//       (4, 1) = (4, 1): True

由于Complex是值类型,因此无法被派生。因此,重写 Equals(Object) 方法不需要调用 GetType 来确定每个对象确切的运行时类型,而是可以用C#中的 is 运算符或VB中的 TypeOf 运算符来检查 obj 参数的类型。

3.1.3.2 备注

当前实例与 obj 参数之间的比较类型取决于 当前实例是引用类型还是值类型

  • 若当前实例是引用类型,则 Equals(Object) 方法将测试引用相等性,并且对 Equals(Object) 方法的调用等效于 ReferenceEquals 方法的调用。引用相等意味着比较的对象变量引用同一个对象。以下示例说明了比较的结果。定义一个 Person 类,它是一个引用类型,并调用Person类构造函数来实例化两个新的Person对象,person1a和person2,它们有着相同的值。它还将person1a分配给另一个对象变量person1b。就如示例输出所示,person1a和person1b相等,因为它们引用相同的对象。然而,person1a和person2并不相等,尽管它们有相同的值。

    using System;
    
    // 定义一个不重写Equals的引用类型
    public class Person
    {
    	private string personName;
    	public Person(string name)
    	{
    		this.personName = name;
    	}
    
    	public override string ToString()
    	{
    		return this.personName;
    	}
    }
    
    public class Example
    {
    	public static void Main()
    	{
    		Person person1a = new Person("John");
    		Person person1b = person1a;
    		Person person2 = new Person(person1a.ToString());
    		Console.WriteLine("Calling Equals:");
    		Console.WriteLine("person1a and person1b: {0}", person1a.Equals(person1b));
    		Console.WriteLine("person1a and person2:{0}", person1a.Equals(person2));
    
    		Console.WriteLine("\nCasting to an Object and calling Equals:");
    		Console.WriteLine("person1a and person1b:{0}"((object) person1a).Equals((object) person1b));
    		Console.WriteLine("person1a and person2: {0}", ((object) person1a).Equals((object) person2));
    	}
    }
    
    // 示例输出以下结果:
    // person1a and person1b: True
    // person1a and person2: False
    
    // 转为Object,并调用Equals
    // person1a and person1b: True
    // person1a and person2: False
    
  • 若当前实例是值类型,则 Equals(Object) 方法测试值是否相等。值相等意味着:

    • 这两个对象属于同一类型。如下所示,值为12的 Byte 不等于值为12的 Int32 对象,因为这两个对象具有不同的运行时类型
      byte value1 = 12;
      int value2 = 12;
      
      object object1 = value1;
      object object2 = value2;
      
      Console.WriteLine("{0} ({1}) = {2} ({3}): {4}",
      	object1, object1.GetType().Name,
      	object2, object2.GetType().Name,
      	object1.Equals(object2));
      // 示例输出如下:
      // 12 (Byte) = 12 (Int32): False
      
    • 两个对象的public和private字段的值均相等。以下示例测试值的相等性。示例定义了一个 Person 结构体(它是一个值类型),并调用 Person 类构造函数来实例化两个新的 Person 对象,它们具有相同的值。如示例输出所示,尽管两个对象变量引用不同的对象,但 person1person2 是相等的,因为它们的 private personName 字段具有相同的值。
using System;

// 定义一个不重写Equals的值类型
public struct Person
{
	private string personName;

	public Person(string name)
	{
		this.personName = name;
	}
	
	public override string ToString()
	{
		return this.personName;
	}
}

public struct Example
{
	public static void Main()
	{
		Person person1 = new Person("John");
		Person person2 = new Person("John");

		Console.WriteLine("Calling Equals:");
		Console.WriteLine(person1.Equals(person2));
		
		Console.WriteLine("\nCasting to an Object and calling Equals:");
		Console.WriteLine(((Object)person1).Equals((Object)person2));	
	}
}

// 示例输出如下:
// Calling Equals:
//	True
// Casting to an Object and calling Equals:
// 	True

由于 Object 类是 .NET Framework中所有类型的基类,因此 Object.Equals(Object) 方法为所有其它类型提供默认的相等比较。不过,类型通常会重写 Equals 方法以实现值相等比较。

3.1.3.3 Windows运行时注意项

当你在Windows运行时(Windows Runtime)中的类上调用 Equals(Object) 方法重载时,它会为不重写 Equals(Object) 的类提供默认行为。这是 .NET Framework 为Windows运行时提供的支持的一部分。Windows运行时中的类不继承 Object ,并且目前不实现 Equals(Object) 方法。不过,当你在C#或VB代码中使用它们(指的是Windows运行时中的类)时,它们似乎具有 ToString、 Equals(Object)、 GetHashCode() 方法,并且 .NET Framework为这些方法提供了默认行为。

注意
用C#或VB编写的Windows运行时类可以重写 Equals(Object) 方法重载。

3.1.3.4 调用者注意项

派生类经常会重写 Object.Equals(Object) 方法以实现值的相等判断。此外,类型也经常为 Equals 方法提供额外的强类型重载,通常是通过实现 IEquatable<T> 接口。当你调用 Equals 方法来测试相等性时,你应该先了解当前实例是否重写 Object.Equals 并了解对 Equals 方法的特定调用的解析方式。否则,你执行的相等性测试可能与你的预期不同,并且该方法可能会返回意外的值。

以下示例进行了说明。它使用相同的字符串实例化三个 StringBuilder 对象,然后四次调用 Equals 方法。第一个方法调用返回 true ,其余三个方法返回 false

using System;
using System.Text;

public class Example
{
	public static void Main()
	{
		StringBuilder sb1 = new StringBuilder("building a string...");
		StringBuilder sb2 = new StringBuilder("building a string...");

		Console.WriteLine("sb1.Equals(sb2): {0}", sb1.Equals(sb2));
		Console.WriteLine("((Object) sb1).Equals(sb2):{0}",
						((Object) sb1).Equals(sb2));
		Console.WriteLine("Object.Equals(sb1, sb2): {0}",
                        Object.Equals(sb1, sb2));

		Object sb3 = new StringBuilder("building a string...");
     	Console.WriteLine("\nsb3.Equals(sb2): {0}", sb3.Equals(sb2));
	}
}

在第一种情况下,调用强类型 StringBuilder.Equals(StringBuilder) 方法重载,该方法重载测试值是否相等。由于分配给两个 StringBuilder 对象的字符串相等,因此该方法返回 true 。但是, StringBuilder 没有重写 Object.Equals(Object) 。所以,当 StringBuilder 对象强制转换为 Object 时,当将 StringBuilder 实例分配给 Object 类型的变量时,以及当 Object.Equals(Object, Object) 方法传递两个 StringBuilder 对象时,默认的 Object.Equals(Object) 方法被调用。由于 StringBuilder 是引用类型,因此这相当于两个 StringBuilder 对象传递给 ReferenceEquals 方法。尽管所有三个 StringBuilder 对象都包含相同的字符串,但它们引用三个不同的对象。结果,这三个方法调用返回 false

你可以通过调用 ReferenceEquals 方法将当前对象与另一个对象进行引用相等性比较。在VB中,还可以使用 is 关键字(如,If Me Is otherObject Then ...)。

3.1.3.5 继承注意

当你定义自己的类型时,该类型将继承其基类型中 Equals 方法所定义的功能。下表列出了 .NET Framework 中主要类型类别/种类(type category)的 Equals 方法的默认实现。

类型类别定义相等性的方法说明
直接继承Object的类Object.Equals(Object)引用相等性;等价于调用Object.ReferenceEquals
StructureValueType.Equals值相等性;直接逐字节比较或使用反射进行逐字段比较
EnumerationEnum.Equals值必须具有相同的枚举类型和相同的底层值
DelegateMulticastDelegate.Equals委托必须具有相同的类型和相同的调用列表
InterfaceObject.Equals(Object)引用相等性

对于值类型,你应该始终重写 Equals ,因为依赖于反射的相等性检查性能较差。你还可以重写引用类型的 Equals 默认实现,以测试值相等而不是引用相等,并定义值相等的精确含义。如,若两个对象具有相同的值,即使它们不是同一个实例, Equals 的实现也会返回 true 。对象的值构成由类型的实现者决定,但通常是存储在对象实例变量中的部分或全部数据。例如,String 对象的值基于字符串中的字符;String.Equals(Object) 方法会重写 Object.Equals(Object) 方法,以便对于包含相同顺序相同字符的任意两个字符串实例返回 true

以下示例演示了如何重写 Object.Equals(Object) 方法以测试值是否相等。它重写 Person 类的 Equals 方法。如果 Person 接受由其基类实现的相等性判断,则两个 Person 对象仅当它们引用同个对象时才相等。在重写后,如果两个 Person 对象具有相同的 Person.Id 属性值,则它们相等。

public class Person
{
	private string idNumber;
	private string personName;

	public Person(string name, string id)
	{
		this.personName = name;
		this.idNumber = id;
	}

	public override bool Equals(Object obj)
	{
		Person personObj = obj as Person;
		if (personObj == null)
			return false;
		else
			return idNumber.Equals(personObj.idNumber);
	}

	public override int GetHashCode()
	{
		return this.idNumber.GetHashCode();
	}
}

public class Example
{
	public static void Main()
	{
		Person p1 = new Person("John", "63412895");
		Person p2 = new Person("John", "63412895");

		Console.WriteLine(p1.Equals(p2));
		Console.WriteLine(Object.Equals(p1, p2));
	}
}

// 示例输出如下:
//		True
//		True

除了重写 Equals 外,你还可以实现 IEquatable<T> 接口来提供强类型的相等性检测。

对于 Equals(Object) 方法的所有实现,下面语句都必须成立。 x, y, z 表示不为 null 的对象引用。

  • x.Equals(x)返回 true
  • x.Equals(y)y.Equals(x)返回值相等。
  • x.Equals(y)若x和y都是 NaN 的话,就返回 true
  • (x.Equals(y) && y.Equals(z))返回 true ,则 x.Equals(z)返回 true
  • 只要 x 和 y引用的对象未被修改,连续调用 x.Equals(y)就会返回相同的值。
  • x.Equals(null)返回 false

Equals 的实现不得抛出异常;它们应该总是返回一个值。例如,若 objnull ,则 Equals 方法应返回 false ,而不是抛出一个 ArgumentNullException

重写 Equals(Object) 时,请遵循以下准则:

  • 实现 IComparable 的类型必修重写 Equals(Object)
  • 重写 Equals(Object) 的类型也必须重写 GetHashCode ;否则,哈希表可能无法正常起作用。
  • 考虑实现 IEquatable <T> 接口来支持强类型相等性检查。IEquatable <T>.Equals 实现返回结果应与 Equals 一致。
  • 若你的编程语言支持运算符重载,并且你为给定类型重载了等号运算符,则还必须重写 Equals(Object) 方法以返回与等号运算符相同的结果。这有助于确保使用 Equals 的类库代码(如ArrayList和Hashtable)行为方式与应用程序代码使用等号运算符的方式一致。
3.1.3.5.1 引用类型指南

以下准则适用于重写引用类型的 Equals(Object)

  • 如果类型的语义基于该类型表示的某些值的事实(即与该类型的实际含义相关),请考虑重写 Equals
  • 大多数引用类型不得重载等号运算符,即使它们重写了 Equals 。但是,如果你要实现旨在具有值语义的引用类型(如复数类型),则必须重写等号运算符。
  • 你不应该在可变引用类型上重写 Equals 。这是因为重写 Equals 要求你还得重写 GetHashCode 方法,上节中有讲到。这意味着可变引用类型实例的哈希码在其生命周期内可能发生变化,这可能会导致对象在哈希表中丢失。
3.1.3.5.2 值类型指南

以下准则适用于重写值类型的 Equals(Object)

  • 如果你定义的值类型包含一个或多个值为引用类型的字段,则应重写 Equals(Object)ValueType 提供的 Equals(Object) 实现对字段均为值类型的值类型进行逐字节比较,但它使用反射对字段包含引用类型的值类型进行逐字段比较。
  • 如果你重写 Equals 并且你的开发语言支持运算符重载,则必须重载相等运算符。
  • 你应该实现 IEquatable<T> 接口。调用强类型 IEquatable <T>.Equals 方法可以避免装箱 obj 参数。

3.1.4 Equals(Object, Object)

确定指定对象实例是否相等。

// 参数 Object objA 要比较的第一个对象
// 参数 Object objB 要比较的第二个对象
// 返回值 Boolean 若俩对象相等,返回 true;否则,返回 false。若 objA和objB都为null,方法返回 true
public static bool Equals(Object? objA, Object? objB);
3.1.4.1 示例

以下示例说明了 Equals(Object, Object) 方法并将其与 ReferenceEquals 方法进行比较。

using System;

public class Example
{
	public static void Main()
	{
		Dog m1 = new Dog("Alaskan Malamute");
		Dog m2 = new Dog("Alaskan Malamute");
		Dog g1 = new Dog("Great Pyrenees");
		Dog g2 = g1;
		Dog d1 = new Dog("Dalmation");
		Dog n1 = null;
		Dog n2 = null;

		Console.WriteLine("null = null: {0}", Object.Equals(n1, n2)); // true
		Console.WriteLine("null Reference Equals null:{0}\n", Object.ReferenceEquals(n1, n2));	// true

		Console.WriteLine("{0} = {1}: {2}", g1, g2, Object.Equals(g1, g2));	// true
		Console.WriteLine("{0} Reference Equals {1}: {2}\n", g1, g2, Object.ReferenceEquals(g1, g2));	// true

		Console.WriteLine("{0} = {1}: {2}", m1, m2, Object.Equals(m1, m2));	// false
		Console.WriteLine("{0} Reference Equals {1}: {2}\n", m1, m2, Object.ReferenceEquals(m1, m2));	// false

		Console.WriteLine("{0} = {1}: {2}", m1, d1, Object.Equals(m1, d1));	// false
		Console.WriteLine("{0} Reference Equals {1}: {2}", m1, d1, Object.ReferenceEquals(m1, d1)) ;	// false
	}
}

public class Dog
{
	// public field
	public string Breed;

	// class constructor
	public Dog(string dogBreed)
	{
		this.Breed = dogBreed;
	}

	public override bool Equals(Object obj)
	{
		if (obj == null || !(obj is Dog)) 
			return false;
		else
			return this.Breed == ((Dog) obj).Breed;
	}

	public override int GetHashCode()
	{
		return this.Breed.GetHashCode();
	}

	public override string ToString()
	{
		return this.Breed;
	}
}

// 示例输出如下:
// null = null: True
// null Reference Equals null: True

// Great Pyrenees = Great Pyrenees: True
// Great Pyrenees = Great Pyrenees: True

// Alaskan Malamute = Alaskan Malamute: True
// Alaskan Malamute Reference Equals Alaskan Malamute: False

// Alaskan Malamute = Dalmation: False
// Alaskan Malamute Reference Equals Dalmation: False

注意
上面源码中 Console打印语句后的注释是我没有看到 Dog类的定义及其Equals重写之前,我给的答案。
源码后,示例输出是看了Dog类定义及Equals重写后的正确答案。
这也警示我们,用Equals之前千万要知晓其比较规则,不要盲猜。

3.1.4.2 备注

static Equals(Object, Object) 方法指明两个对象是否相等(objAobjB )。它也使你能够检测值为 null 的对象是否相等。具体如下:

  • 它确定两个对象是否表示相同的对象引用。如果是,则返回 true 。此检测相当于调用 ReferenceEquals 方法。此外,若 objAobjB 都为 null ,该方法也返回 true
  • 它确定 objAobjB 是否为 null 。若其中一个为 null ,则返回 false
  • 若两个对象不是同一个对象引用且都不为 null ,则调用 objA.Equals(objB) 并返回结果。这意味着,如果 objA 重写 Object.Equals(Object) 方法,该重写会被调用。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值