欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C#值类型、引用类型中的Equals和==的区别浅析

程序员文章站 2023-12-13 22:41:10
引言 最近一个朋友正在找工作,他说在笔试题中遇到equals和==有什么区别的题,当时跟他说如果是值类型的,它们没有区别,如果是引用类型的有区别,但string类型除外。...

引言

最近一个朋友正在找工作,他说在笔试题中遇到equals和==有什么区别的题,当时跟他说如果是值类型的,它们没有区别,如果是引用类型的有区别,但string类型除外。为了证实自己的说法,也研究了一下,以免误导别人,这里将研究结果总结一下,如果我有什么地方说的不对的地方,望指出。

相等性

在定义类或结构时,您将决定为类型创建值相等性(或等效性)的自定义定义是否有意义。 通常,当类型的对象预期要添加到某类集合时,或者当这些对象主要用于存储一组字段或属性时,您将实现值相等性。 您可以基于类型中所有字段和属性的比较来定义值相等性,也可以基于子集进行定义。 但在任何一种情况下,类和结构中的实现均应遵循五个等效性保证条件:

1.x.equals(x) 返回 true. 。这称为自反属性。
2.x.equals(y) 返回与 equals(x) 相同的值。 这称为对称属性。
3.如果 (x.equals(y) && y.equals(z)) 返回 true,则 x.equals(z) 返回 true。 这称为可传递属性。
4.只要不修改 x 和 y 所引用的对象,x.equals(y) 的后续调用就返回相同的值。
5.x.equals(null) 返回 false。 但是,null.equals(null) 会引发异常;它不遵循上面的第二条规则。

您定义的任何结构已经具有它从 object.equals(object) 方法的 system.valuetype 重写中继承的默认值相等性实现。 此实现使用反射来检查类型中的所有公共和非公共字段以及属性。 尽管此实现可生成正确的结果,但与您专门为类型编写的自定义实现相比,它的速度相对较慢。

类和结构的值相等性的实现详细信息不同。 但是,类和结构都需要相同的基础步骤来实现相等性:

重写 object.equals(object)虚方法。 大多数情况下,您的 bool equals( object obj ) 实现应只调入作为 system.iequatable<t> 接口的实现的类型特定 equals 方法。 (请参见步骤 2。)

通过提供类型特定的 equals 方法实现 system.iequatable<t> 接口。 实际的等效性比较将在此接口中执行。 例如,您可能决定通过仅比较类型中的一两个字段来定义相等性。 不要从 equals 中引发异常。 仅适用于类:此方法应仅检查类中声明的字段。 它应调用 base.equals 来检查基类中的字段。 (如果类型直接从 object 中继承,则不要这样做,因为 object.equals(object) 的 object 实现会执行引用相等性检查。)

可选,但建议这样做:重载 == 和 != 运算符。

重写 object.gethashcode,使具有值相等性的两个对象生成相同的哈希代码。

可选:若要支持“大于”或“小于”定义,请为类型实现 icomparable<t> 接口,并同时重载 <= 和 >= 运算符。

                         ——msdn()这里将msdn的说法贴在此处,方便查看。

值类型

这里就以int类型的为代表进行分析,在分析之前先复习一下什么是重载?重载:简单的说就是一个类中的方法与另一个方法同名,但是参数列表个数或者类型或者返回值类型不同,则这两个方法构成重载。那么重载方法的调用规则是什么?那么先看下面的一段测试代码:

复制代码 代码如下:

namespace wolfy.equalsdemo
{
    class program
    {
        static void main(string[] args)
        {
            int a =1, b = 1;
            console.writeline(add(a,b));
            console.read();
        }
        static int add(object a, object b)
        {
            console.writeline("调用了object类型参数列表的方法:");
            return (int)a + (int)b;
        }
        static int add(int a, float b)
        {
            console.writeline("调用了int,float类型参数列表的方法:");
            return a + (int)b;
        }
        static int add(int a, int b)
        {
            console.writeline("调用了int类型参数列表的方法:");
            return a + b;
        }
    }
}

测试结果:

C#值类型、引用类型中的Equals和==的区别浅析

说明根据传入实参的类型,优先匹配最相近的形参列表的方法。

那么我们将add(int a ,int b)这个方法注释掉,那么会调用哪个方法?

C#值类型、引用类型中的Equals和==的区别浅析

为什么花费那么多口舌说明上面的问题,那么现在看一下int32反编译的代码:

C#值类型、引用类型中的Equals和==的区别浅析

int32有一个自己的equals方法,有一个重写的equals方法,如果两个int类型的值进行比较,equals和==是一样的,因为它优先调用了下面的equals方法,如果是下面的代码,则会选择重写的equals方法。

复制代码 代码如下:

static void main(string[] args)
        {
            int a = 1;
            object b = 1;
            console.writeline(a.equals(b));
            console.read();
        }

可见,对于值类型的equals和==是一样的。

引用类型

在类(引用类型)上,两种 object.equals(object) 方法的默认实现均执行引用相等性比较,而不是值相等性检查。 当实施者重写虚方法时,目的是为了为其指定值相等性语义。
即使类不重载 == 和 != 运算符,也可以将这些运算符与类一起使用。 但是,默认行为是执行引用相等性检查。 在类中,如果您重载 equals 方法,则应重载 == 和 != 运算符,但这并不是必需的。

                                                                ——msdn

测试代码:

复制代码 代码如下:

static void main(string[] args)
        {

            person p1 = new person() { name = "wolfy" };
            person p2 = new person() { name = "wolfy" };
            person p3 = p2;
            bool r1 = p1 == p2;
            bool r2 = p1.equals(p2);
            bool r3 = p2 == p3;
            bool r4 = p2.equals(p3);
            bool r5 = object.referenceequals(p1, p2);
            bool r6 = object.equals(p1,p2);
            bool r7 = object.referenceequals(p2, p3);
            console.writeline("==\t"+r1);
            console.writeline("equals\t"+r2);
            console.writeline("p3=p2\t"+r3);
            console.writeline("p2.equals(p3)\t"+r4);
            console.writeline("object.referenceequals\t" + r5);
            console.writeline("object.equals(p1,p2)\t" + r6);
            console.writeline("object.referenceequals(p2, p3)\t" + r7);
            console.read();
        }

结果:

C#值类型、引用类型中的Equals和==的区别浅析

顺便反编译一下equals和referenceequals方法,看看他们的实现如何?

复制代码 代码如下:

 // object
 [__dynamicallyinvokable, targetedpatchingoptout("performance critical to inline across ngen image boundaries")]
 public static bool equals(object obja, object objb)
 {
     return obja == objb || (obja != null && objb != null && obja.equals(objb));
 }

// object
[__dynamicallyinvokable, reliabilitycontract(consistency.willnotcorruptstate, cer.success), targetedpatchingoptout("performance critical to inline across ngen image boundaries")]
public static bool referenceequals(object obja, object objb)
{
    return obja == objb;
}

通过上面的代码,我们可以得出这样的结论,引用类型中equals和referenceequals的行为是相同的,==与referenceequals的行为也相同,但string除外。

对特殊应用类型string的相等性,遵循值类型的相等性。string类型的反编译后的equals方法和==代码如下:

复制代码 代码如下:

public override bool equals(object obj)
        {
            if (this == null)
            {
                throw new nullreferenceexception();
            }
            string text = obj as string;
            return text != null && (object.referenceequals(this, obj) || (this.length == text.length && string.equalshelper(this, text)));
        }
        [__dynamicallyinvokable, reliabilitycontract(consistency.willnotcorruptstate, cer.mayfail), targetedpatchingoptout("performance critical to inline across ngen image boundaries")]
        public bool equals(string value)
        {
            if (this == null)
            {
                throw new nullreferenceexception();
            }
            return value != null && (object.referenceequals(this, value) || (this.length == value.length && string.equalshelper(this, value)));
        }
        [__dynamicallyinvokable, securitysafecritical]
        public bool equals(string value, stringcomparison comparisontype)
        {
            if (comparisontype < stringcomparison.currentculture || comparisontype > stringcomparison.ordinalignorecase)
            {
                throw new argumentexception(environment.getresourcestring("notsupported_stringcomparison"), "comparisontype");
            }
            if (this == value)
            {
                return true;
            }
            if (value == null)
            {
                return false;
            }
            switch (comparisontype)
            {
            case stringcomparison.currentculture:
                return cultureinfo.currentculture.compareinfo.compare(this, value, compareoptions.none) == 0;
            case stringcomparison.currentcultureignorecase:
                return cultureinfo.currentculture.compareinfo.compare(this, value, compareoptions.ignorecase) == 0;
            case stringcomparison.invariantculture:
                return cultureinfo.invariantculture.compareinfo.compare(this, value, compareoptions.none) == 0;
            case stringcomparison.invariantcultureignorecase:
                return cultureinfo.invariantculture.compareinfo.compare(this, value, compareoptions.ignorecase) == 0;
            case stringcomparison.ordinal:
                return this.length == value.length && string.equalshelper(this, value);
            case stringcomparison.ordinalignorecase:
                if (this.length != value.length)
                {
                    return false;
                }
                if (this.isascii() && value.isascii())
                {
                    return string.compareordinalignorecasehelper(this, value) == 0;
                }
                return textinfo.compareordinalignorecase(this, value) == 0;
            default:
                throw new argumentexception(environment.getresourcestring("notsupported_stringcomparison"), "comparisontype");
            }
        }
        [__dynamicallyinvokable, targetedpatchingoptout("performance critical to inline across ngen image boundaries")]
        public static bool equals(string a, string b)
        {
            return a == b || (a != null && b != null && a.length == b.length && string.equalshelper(a, b));
        }
        [__dynamicallyinvokable, securitysafecritical]
        public static bool equals(string a, string b, stringcomparison comparisontype)
        {
            if (comparisontype < stringcomparison.currentculture || comparisontype > stringcomparison.ordinalignorecase)
            {
                throw new argumentexception(environment.getresourcestring("notsupported_stringcomparison"), "comparisontype");
            }
            if (a == b)
            {
                return true;
            }
            if (a == null || b == null)
            {
                return false;
            }
            switch (comparisontype)
            {
            case stringcomparison.currentculture:
                return cultureinfo.currentculture.compareinfo.compare(a, b, compareoptions.none) == 0;
            case stringcomparison.currentcultureignorecase:
                return cultureinfo.currentculture.compareinfo.compare(a, b, compareoptions.ignorecase) == 0;
            case stringcomparison.invariantculture:
                return cultureinfo.invariantculture.compareinfo.compare(a, b, compareoptions.none) == 0;
            case stringcomparison.invariantcultureignorecase:
                return cultureinfo.invariantculture.compareinfo.compare(a, b, compareoptions.ignorecase) == 0;
            case stringcomparison.ordinal:
                return a.length == b.length && string.equalshelper(a, b);
            case stringcomparison.ordinalignorecase:
                if (a.length != b.length)
                {
                    return false;
                }
                if (a.isascii() && b.isascii())
                {
                    return string.compareordinalignorecasehelper(a, b) == 0;
                }
                return textinfo.compareordinalignorecase(a, b) == 0;
            default:
                throw new argumentexception(environment.getresourcestring("notsupported_stringcomparison"), "comparisontype");
            }
        }
        [__dynamicallyinvokable, targetedpatchingoptout("performance critical to inline across ngen image boundaries")]
        public static bool operator ==(string a, string b)
        {
            return string.equals(a, b);
        }
        [__dynamicallyinvokable, targetedpatchingoptout("performance critical to inline across ngen image boundaries")]
        public static bool operator !=(string a, string b)
        {
            return !string.equals(a, b);
        }

从上面的代码可以看出string类型的equals和==是一样的。

复制代码 代码如下:

public static bool operator ==(string a, string b)
        {
            return string.equals(a, b);
        }
[__dynamicallyinvokable, targetedpatchingoptout("performance critical to inline across ngen image boundaries")]
        public static bool operator !=(string a, string b)
        {
            return !string.equals(a, b);
        }

总结

值类型具有它从 object.equals(object) 方法的 system.valuetype 重写中继承的默认值相等性实现。特殊的引用类型string类型,因为重写了equals和==方法,所以string类型的equals和==与值类型的相等性一样。对于其他的引用类型此时的equals和==与引用相等referenceequals的行为相同。

以上是由一个同事的问题引起,中间也查了很多资料,发现这篇文章在草稿箱中躺了很久了,今天突然看到就拿出来晒晒。中间修修改改,总尝试着用哪种方式来说明这个老生常谈的问题更好些。以上有些观点,纯属个人见解,如果你有更好的理解方式,不妨分享一下。如果对你有所帮助,不妨点一下推荐,让更多的人看到,说说自己对equals和==的理解。

上一篇:

下一篇: