本文阐述C#中相等性比较,其中主要集中在下面两个方面

==和!=运算符,什么时候它们可以用于相等性比较,什么时候它们不适用,如果不使用,那么它们的替代方式是什么?

什么时候,需要自定一个类型的相等性比较逻辑

在阐述相等性比较,以及如何自定义相等性比较逻辑之前,我们首先了解一下值类型比较和引用类型比较

值类型比较对比引用类型比较

C#中的相等性比较有两种:

  • 值类型相等,两个值在某种场景下相等
  • 引用类型相等,两个引用指向同一个对象

默认情况下,

  • 值类型使用值类型相等
  • 引用类型使用引用相等

实际上,值类型只能使用值相等(除非值类型进行了装箱操作)。看一个简单的例子(比较两个数字),运行的结果为True

int x = 5, y = 5;
Console.WriteLine(x == y);

默认地,引用类型使用引用相等。比如下面的例子:返回False

object x = 5, y = 5;
Console.WriteLine(x == y);

如果x和y指向同一个对象,那么将返回True:

object x = 5, y = x;
Console.WriteLine(x == y);

相等性的标准

下面三个标准用于实现相等性比较:

  • ==和!=运算符
  • object中的虚方法Equals
  • IEquatable<T>接口

下面我们来分别阐述

1. ==和!=运算符

使用==和!=的原因是它们是运算符,它们通过静态函数实现相等性比较。因此,当你使用==或!=时,C#在编译时就决定了所比较的类型,而且不会执行任何虚方法(Object.Equals)。这是大家所期望的相等行比较。比如在第一个数字比较的例子中,编译器在编译时就决定了执行==运算的类型是int类型,因为x和y都是int类型。

而第二个例子,编译器决定执行==运算的类型是object类型,因为object是类(引用类型),因此对象的==运算符采取引用相等去比较x和y。那么结果就返回False,这是因为x和y指向堆上不同的对象(被装箱的int)

 

2. Object.Equals虚方法

为了正确地比较第二个例子中的x和y,我们可以使用Equals虚方法。System.Object中定义了Equals虚方法,它适用于所有类型

object x = 5, y = 5;
Console.WriteLine(x.Equals(y));

Equals在程序运行时决定比较的类型--根据对象的实际类型进行比较。在上面的例子中,会调用Int32的Euqals方法,该方法使用值相等进行比较,所以上面的例子返回True。如果x和y是引用类型,那么调用引用相等进行比较;如果x和y是结构类型,那么Equals会调用结构每个成员对应类型的Equals方法进行比较。

看到这里,你可能会想,为什么C#的设计者不把==设计成virtaul,从而使其与Equals一样,以避免上诉缺陷。这是因为:

  • 如果第一个运算对象是null,Equals方法会抛出NullReferenceException异常;而静态的运算符则不会
  • 因为==运算符在编译时决定了比较类型(静态解析比较类型),那么它的执行就非常快。这也就使得编写大量运算代码去执行相等性比较时对性能不会带来太大的影响
  • 有时候,==和Equals适用于不同的场景的相等性比较。(后续的内容会涉及)

简而言之,复杂的设计反映了复杂的场景:相等的概念涉及到许多场景。

 

而Euqals方法,适用于比较两个未知类型的对象,下面的这个方法就适用于比较任何类型的两个对象:

public static bool AreEqual(object obj1, object obj2)
{
    return obj1.Equals(obj2);
}

但是,该函数不能处理第一个参数是null的情形,如果第一个函数是null,你会得到NullReferenceException异常。因此我们需要对该函数进行修改:

public static bool AreEqual(object obj1, object obj2)
{
    if (obj1 == null)
        return obj2 == null;
    return obj1.Equals(obj2);
}

 

object的静态Equals方法

object类还定义了一个静态Equals方法,它的作用与AreEquals方法一样。

public static bool Equals(Object objA, Object objB)
{
    if (objA==objB) {
        return true;
    }
    if (objA==null || objB==null) {
        return false;
    }
    return objA.Equals(objB);
}

这样就可以对编译时不知道类型的null对象进行安全地比较。

object x = 5, y = 5;
Console.WriteLine(object.Equals(x, y)); // -> True
x = null;
Console.WriteLine(object.Equals(x, y)); // -> False
y = null;
Console.WriteLine(object.Equals(x, y)); // -> True

Console.WriteLine(x.Equals(y)); // -> NullReferebceException, because x is null

请注意,当编写Generic类型时,下面的代码将不能通过编译(除非把==或!=运算符替换成Object.Equals方法的调用):

public class Test<T> : IEqualityComparer<T>
{
    T _value;

    public void SetValue(T newValue)
    {
        // Operator '!=' cannot be applied to operands of type 'T' and 'T'
        // it should be : if(!object.Equals(newValue, _value))
        if (newValue != _value)
            _value = newValue;
    }
}

object的静态ReferenceEquals方法

有时候,你需要强行比较两个引用是否相等。这个时候,你就需要使用object.ReferenceEquals:

internal class Widget 
{
    public string UID { get; set; }
    public override bool Equals(object obj)
    {
        if (obj == null)
            return this == null;

        if (!(obj is Widget))
            return false;
        Widget w = obj as Widget;
        return this.UID == w.UID;
    }

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

    public static bool operator  == (Widget w1, Widget w2)
            {
                return w1.Equals(w2);
            }

    public static bool operator !=(Widget w1, Widget w2)
            {
                return !w1.Equals(w2);
            }
}

static void Main(string[] args)
{

    Widget w1 = new Widget();
    Widget w2 = new Widget();
    Console.WriteLine(w1==w2); // -> True
    Console.WriteLine(w1.Equals(w2)); // -> True
    Console.WriteLine(object.ReferenceEquals(w1, w2)); // -> False 

    Console.ReadLine();
}
Basic ReferenceEquals

相关文章:

  • 2021-08-07
  • 2022-12-23
  • 2022-12-23
  • 2021-11-18
  • 2021-12-14
  • 2021-08-02
  • 2021-12-02
  • 2021-05-30
猜你喜欢
  • 2022-12-23
  • 2022-01-02
  • 2022-02-19
  • 2022-12-23
  • 2021-07-08
  • 2022-12-23
相关资源
相似解决方案