我已经扩展了Ani的出色答案,以显示在处理引用类型和重写的相等方法时的主要区别。
。
void Main()
{
object o1 = null;
object o2 = new object();
object o3 = null;
object o4 = new object();
object o5 = o1;
object o6 = o2;
Demo d1 = new Demo(Guid.Empty);
Demo d2 = new Demo(Guid.NewGuid());
Demo d3 = new Demo(Guid.Empty);
Debug.WriteLine("comparing null with null always yields true...");
ShowResult("ReferenceEquals(o1, o1)", () => ReferenceEquals(o1, o1));
ShowResult("ReferenceEquals(o3, o1)", () => ReferenceEquals(o3, o1));
ShowResult("ReferenceEquals(o5, o1)", () => ReferenceEquals(o5, o1));
ShowResult("o1 == o1", () => o1 == o1);
ShowResult("o3 == o1", () => o3 == o1);
ShowResult("o5 == o1", () => o5 == o1);
Debug.WriteLine("...though because the object's null, we can't call methods on the object (i.e. we'd get a null reference exception).");
ShowResult("o1.Equals(o1)", () => o1.Equals(o1));
ShowResult("o1.Equals(o2)", () => o1.Equals(o2));
ShowResult("o3.Equals(o1)", () => o3.Equals(o1));
ShowResult("o3.Equals(o2)", () => o3.Equals(o2));
ShowResult("o5.Equals(o1)", () => o5.Equals(o1));
ShowResult("o5.Equals(o2)", () => o5.Equals(o1));
Debug.WriteLine("Comparing a null object with a non null object always yeilds false");
ShowResult("ReferenceEquals(o1, o2)", () => ReferenceEquals(o1, o2));
ShowResult("ReferenceEquals(o2, o1)", () => ReferenceEquals(o2, o1));
ShowResult("ReferenceEquals(o3, o2)", () => ReferenceEquals(o3, o2));
ShowResult("ReferenceEquals(o4, o1)", () => ReferenceEquals(o4, o1));
ShowResult("ReferenceEquals(o5, o2)", () => ReferenceEquals(o3, o2));
ShowResult("ReferenceEquals(o6, o1)", () => ReferenceEquals(o4, o1));
ShowResult("o1 == o2)", () => o1 == o2);
ShowResult("o2 == o1)", () => o2 == o1);
ShowResult("o3 == o2)", () => o3 == o2);
ShowResult("o4 == o1)", () => o4 == o1);
ShowResult("o5 == o2)", () => o3 == o2);
ShowResult("o6 == o1)", () => o4 == o1);
ShowResult("o2.Equals(o1)", () => o2.Equals(o1));
ShowResult("o4.Equals(o1)", () => o4.Equals(o1));
ShowResult("o6.Equals(o1)", () => o4.Equals(o1));
Debug.WriteLine("(though again, we can't call methods on a null object:");
ShowResult("o1.Equals(o2)", () => o1.Equals(o2));
ShowResult("o1.Equals(o4)", () => o1.Equals(o4));
ShowResult("o1.Equals(o6)", () => o1.Equals(o6));
Debug.WriteLine("Comparing 2 references to the same object always yields true");
ShowResult("ReferenceEquals(o2, o2)", () => ReferenceEquals(o2, o2));
ShowResult("ReferenceEquals(o6, o2)", () => ReferenceEquals(o6, o2));
ShowResult("o2 == o2", () => o2 == o2);
ShowResult("o6 == o2", () => o6 == o2);
ShowResult("o2.Equals(o2)", () => o2.Equals(o2));
ShowResult("o6.Equals(o2)", () => o6.Equals(o2));
Debug.WriteLine("However, comparing 2 objects may yield false even if those objects have the same values, if those objects reside in different address spaces (i.e. they're references to different objects, even if the values are similar)");
Debug.WriteLine("NB: This is an important difference between Reference Types and Value Types.");
ShowResult("ReferenceEquals(o4, o2)", () => ReferenceEquals(o4, o2));
ShowResult("o4 == o2", () => o4 == o2);
ShowResult("o4.Equals(o2)", () => o4.Equals(o2));
Debug.WriteLine("We can override the object's equality operator though, in which case we define what's considered equal");
Debug.WriteLine("e.g. these objects have different ids, so we treat as not equal");
ShowResult("ReferenceEquals(d1,d2)",()=>ReferenceEquals(d1,d2));
ShowResult("ReferenceEquals(d2,d1)",()=>ReferenceEquals(d2,d1));
ShowResult("d1 == d2",()=>d1 == d2);
ShowResult("d2 == d1",()=>d2 == d1);
ShowResult("d1.Equals(d2)",()=>d1.Equals(d2));
ShowResult("d2.Equals(d1)",()=>d2.Equals(d1));
Debug.WriteLine("...whilst these are different objects with the same id; so we treat as equal when using the overridden Equals method...");
ShowResult("d1.Equals(d3)",()=>d1.Equals(d3));
ShowResult("d3.Equals(d1)",()=>d3.Equals(d1));
Debug.WriteLine("...but as different when using the other equality tests.");
ShowResult("ReferenceEquals(d1,d3)",()=>ReferenceEquals(d1,d3));
ShowResult("ReferenceEquals(d3,d1)",()=>ReferenceEquals(d3,d1));
ShowResult("d1 == d3",()=>d1 == d3);
ShowResult("d3 == d1",()=>d3 == d1);
Debug.WriteLine("For completeness, here's an example of overriding the == operator (wihtout overriding the Equals method; though in reality if overriding == you'd probably want to override Equals too).");
Demo2 d2a = new Demo2(Guid.Empty);
Demo2 d2b = new Demo2(Guid.NewGuid());
Demo2 d2c = new Demo2(Guid.Empty);
ShowResult("d2a == d2a", () => d2a == d2a);
ShowResult("d2b == d2a", () => d2b == d2a);
ShowResult("d2c == d2a", () => d2c == d2a);
ShowResult("d2a != d2a", () => d2a != d2a);
ShowResult("d2b != d2a", () => d2b != d2a);
ShowResult("d2c != d2a", () => d2c != d2a);
ShowResult("ReferenceEquals(d2a,d2a)", () => ReferenceEquals(d2a, d2a));
ShowResult("ReferenceEquals(d2b,d2a)", () => ReferenceEquals(d2b, d2a));
ShowResult("ReferenceEquals(d2c,d2a)", () => ReferenceEquals(d2c, d2a));
ShowResult("d2a.Equals(d2a)", () => d2a.Equals(d2a));
ShowResult("d2b.Equals(d2a)", () => d2b.Equals(d2a));
ShowResult("d2c.Equals(d2a)", () => d2c.Equals(d2a));
}
public delegate bool Statement();
void ShowResult(string statementText, Statement statement)
{
try
{
Debug.WriteLine("\t{0} => {1}",statementText, statement());
}
catch(Exception e)
{
Debug.WriteLine("\t{0} => throws {1}",statementText, e.GetType());
}
}
class Demo
{
Guid id;
public Demo(Guid id) { this.id = id; }
public override bool Equals(object obj)
{
return Equals(obj as Demo);
}
public bool Equals(Demo obj)
{
if (obj == null)
{
return false;
}
else
{
return id.Equals(obj.id);
}
}
public override int GetHashCode()
{
return id.GetHashCode();
}
}
class Demo2
{
Guid id;
public Demo2(Guid id)
{
this.id = id;
}
public static bool operator ==(Demo2 obj1, Demo2 obj2)
{
if (ReferenceEquals(null, obj1))
{
return ReferenceEquals(null, obj2);
}
else
{
if(ReferenceEquals(null, obj2))
{
return false;
}
else
{
return obj1.id == obj2.id;
}
}
}
public static bool operator !=(Demo2 obj1, Demo2 obj2)
{
return !(obj1 == obj2);
}
}