“世界上不会有两片完全相同的树叶”,这句话适用于现实世界。而在软件世界中,这句话变成了"世界上必须有两片完全相同的树叶",否则,很多事情无以为继。

当比较2个对象是否相等时,通常情况下:==操作符用来比较值类型,比较的是值;实例方法Equals和静态方法Object.ReferenceEquals比较引用类型,比较的是对象的地址。

在实际项目中,当比较2个引用类型对象时,我们的需求变为:通过依次比较2个对象的所有属性来判断是否相等。这时候,IEquatable接口就有了展示自己的机会。本篇主要包括:

使用==操作符比较值类型是否相等

    class Program
    {
        static void Main(string[] args)
        {
            ComplexNumber a = new ComplexNumber(){Real = 4.5D, Imaginary = 8.4D};
            ComplexNumber b = new ComplexNumber() { Real = 4.5D, Imaginary = 8.4D };
            Console.WriteLine("{0} 是否等于{1}:{2}",a, b, CompareTwoComplexNumbers(a, b));
            Console.ReadKey();
        }

        static bool CompareTwoComplexNumbers(ComplexNumber a, ComplexNumber b)
        {
            return ((a.Real == b.Real) && (a.Imaginary == b.Imaginary));
        }
    }

    public class ComplexNumber
    {
        public double Real { get; set; }
        public double Imaginary { get; set; }
        public override string ToString()
        {
            return String.Format("{0}{1}{2}i",
                Real,
                Imaginary >= 0 ? "+" : "-",
                Math.Abs(Imaginary));
        }
    }


以上,比较诸如int,double,DateTime,struct等值类型的时候,==操作符当然是不二之选。

实例方法Equals比较引用类型地址是否相等

    class Program
    {
        static void Main(string[] args)
        {
            Guy darren1 = new Guy("Darren", 37, 100);
            Guy darren2 = darren1;
            Console.WriteLine(Object.ReferenceEquals(darren1,darren2));
            Console.WriteLine(darren1.Equals(darren2));
            Console.WriteLine(Object.ReferenceEquals(null, null));

            darren2 = new Guy("Darren", 37, 100);
            Console.WriteLine(Object.ReferenceEquals(darren1, darren2));
            Console.WriteLine(darren1.Equals(darren2));
            Console.ReadKey();
        }
    }

    public class Guy
    {
        private readonly string name;
        public string Name{get { return name; }}

        private readonly int age;
        public int Age{get { return age; }}

        public int Cash { get; private set; }

        public Guy(string name, int age, int cash)
        {
            this.name = name;
            this.age = age;
            Cash = cash;
        }

        public override string ToString()
        {
            return String.Format("{0} 今年 {1} 岁了,身价{2}", Name, Age, Cash);
        }
    }

以上,实例方法Equals()和静态方法Object.ReferenceEquals()适用于比较引用类型,而且比较的是对象的地址。

可是,如果我们想使用Equals()方法比较引用类型对象的各个属性,怎么办呢?

实现IEquatable接口重写实例方法Equals()

写一个派生于Guy的类EquatableGuy,并且实现IEquatable<Guy>接口,重写IEquatable<Guy>接口的Equals(Guy other)方法。

   class Program
    {
        static void Main(string[] args)
        {
            Guy darren1 = new EquatableGuy("Darren", 37, 100);
            Guy darren2 = new EquatableGuy("Darren", 37, 100);
            Console.WriteLine(Object.ReferenceEquals(darren1, darren2)); //False
            Console.WriteLine(darren1.Equals(darren2)); //True
            Console.ReadKey();
        }
    }

    public class Guy
    {
        private readonly string name;
        public string Name{get { return name; }}

        private readonly int age;
        public int Age{get { return age; }}

        public int Cash { get; private set; }

        public Guy(string name, int age, int cash)
        {
            this.name = name;
            this.age = age;
            Cash = cash;
        }

        public override string ToString()
        {
            return String.Format("{0} 今年 {1} 岁了,身价{2}", Name, Age, Cash);
        }
    }

    public class EquatableGuy : Guy, IEquatable<Guy>
    {
        public EquatableGuy(string name, int age, int cash) : base(name, age, cash){}
        public bool Equals(Guy other)
        {
            if (ReferenceEquals(null, other)) return false;
            if (ReferenceEquals(this, other)) return true;
            return Equals(other.Name, Name) && other.Age == Age && other.Cash == Cash;
        }

        public override bool Equals(object obj)
        {
            if (!(obj is Guy)) return false;
            return Equals((Guy) obj);
        }

        public override int GetHashCode()
        {
            const int prime = 397;
            int result = Age;
            result = (result * prime) ^ (Name != null ? Name.GetHashCode() : 0);
            result = (result * prime) ^ Cash;
            return result;
        }
    }


以上,值得注意的是:
当实现IEquatable<Guy>接口时,一定要重写Object基类的Equals方法,然后在Object基类的Equals方法内部调用我们自定义的IEquatable<Guy>接口方法。另外还必须重写Object基类的GetHashCode方法。这时MSDN规定的,在这里

而且,使用IEquatable<Guy>泛型接口还有一个好处是避免装箱和拆箱,因为在JIT编译时才替代占位符。而如果我们通过重写Object基类方法Equals实现自定义比较的话,难免会出现装箱和拆箱,影响比较性能。

如果我们项使用==操作符比较引用对象是否相等呢?我们可以通过重写操作符来实现。

我们再写一个EquatableGuy的子类EquatableGuyWithOverload,并且重写==操作符。

   class Program
    {
        static void Main(string[] args)
        {
            var darren1 = new EquatableGuyWithOverload("Darren", 37, 100);
            var darren2 = new EquatableGuyWithOverload("Darren", 37, 100);
            Console.WriteLine(Object.ReferenceEquals(darren1, darren2)); //False
            Console.WriteLine(darren1 == darren2); //True
            Console.ReadKey();
        }
    }

    public class EquatableGuyWithOverload : EquatableGuy
    {
        public EquatableGuyWithOverload(string name, int age, int cash) : base(name, age, cash){}

        public static bool operator ==(EquatableGuyWithOverload left, EquatableGuyWithOverload right)
        {
            if (Object.ReferenceEquals(left, null)) return false;
            else return left.Equals(right);
        }

        public static bool operator !=(EquatableGuyWithOverload left, EquatableGuyWithOverload right)
        {
            return !(left == right);
        }

        public override bool Equals(object obj)
        {
            return base.Equals(obj);
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }
    }


以上,子类EquatableGuyWithOverload重写了==操作符,由于其父类EquatableGuy已经重写了基类Object的Equals方法,所以在这里可以直接调用。

总结:通常情况下,使用==操作符比较值类型对象;使用实例方法Equals或静态方法Object.ReferenceEquals比较引用类型对象地址;如果想自定义比较逻辑,可以考虑实现IEquatable<>泛型接口,避免装箱、拆箱。

参考资料:

防止装箱落实到底,只做一半也是失败
Understanding C#: Equality, IEquatable, and Equals()

如何使用==操作符,Equals方法,ReferenceEquals方法,IEquatable接口比较2个对象的更多相关文章

  1. object.Equals与object.ReferenceEquals方法

    object.Equals方法表达的是语义判等,不一定是引用判等. object.ReferenceEquals方法是肯定是引用判等. 怎么实现一个对象的值语义的 Equals方法?实验. MyCla ...

  2. java String的equals,intern方法(转载)

    JAVA中的equals和==的区别 ==比较的是2个对象的地址,而equals比较的是2个对象的内容. 显然,当equals为true时,==不一定为true: 基础知识的重要性,希望引起大家的重视 ...

  3. Equals()和GetHashCode()方法深入了解

    最近在看Jeffrey Richter的CLR Via C#,在看到GetHashCode()方法的时候,有一个地方不是特别明白,就是重写Equals()方法时为什么要把GetHashCode()方法 ...

  4. ( 转 ) 聊一聊C#的Equals()和GetHashCode()方法

    聊一聊C#的Equals()和GetHashCode()方法   博客创建一年多,还是第一次写博文,有什么不对的地方还请多多指教. 关于这次写的内容可以说是老生长谈,百度一搜一大堆.大神可自行绕路. ...

  5. 聊一聊C#的Equals()和GetHashCode()方法

    博客创建一年多,还是第一次写博文,有什么不对的地方还请多多指教. 关于这次写的内容可以说是老生长谈,百度一搜一大堆.大神可自行绕路. 最近在看Jeffrey Richter的CLR Via C#,在看 ...

  6. Java中的equals和hashCode方法

    本文转载自:Java中的equals和hashCode方法详解 Java中的equals方法和hashCode方法是Object中的,所以每个对象都是有这两个方法的,有时候我们需要实现特定需求,可能要 ...

  7. String equals()方法使用以及子串加密

    String equals()方法的实现方法: 名称 说明 String.Equals (Object) 确定此 String 实例是否与指定的对象(也必须是 String)具有相同的值. Strin ...

  8. Java提高篇——equals()与hashCode()方法详解

    java.lang.Object类中有两个非常重要的方法: 1 2 public boolean equals(Object obj) public int hashCode() Object类是类继 ...

  9. Java中==、equals、hashcode的区别与重写equals以及hashcode方法实例(转)

    Java中==.equals.hashcode的区别与重写equals以及hashcode方法实例  原文地址:http://www.cnblogs.com/luankun0214/p/4421770 ...

随机推荐

  1. 关于SQLserver的索引的一些脚本

    --判断无用的索引: SELECT TOP 30 DB_NAME() AS DatabaseName , '[' + SCHEMA_NAME(o.Schema_ID) + ']' + '.' + '[ ...

  2. JavaScript闭包与变量的经典问题

    许多人第一次接触闭包大概都是从高程里这段代码开始的: function createFunctions() { var result = new Array(); for(var i=0; i< ...

  3. android拾遗——AlarmManager的使用

    AlarmManager的作用文档中的解释是:在特定的时刻为我们广播一个指定的Intent.简单的说就是我们设定一个时间,然后在该时间到来时,AlarmManager为我们广播一个我们设定的Inten ...

  4. CF475C. Kamal-ol-molk's Painting

    C. Kamal-ol-molk's Painting time limit per test 2 seconds memory limit per test 256 megabytes input ...

  5. 【AtCoder】ARC094(C-F)题解

    C - Same Integers 题解 要么三个都达到最大的数,要么三个都到达最大的数+1,判断是前一种情况的方法是不断垫高前两大的,看之后最小的那个和最大的那个差值是不是2的倍数 否则就是第二种情 ...

  6. 【POJ】1819.Disks

    博客园的话插链接链接都是凉的= = 题解 我理解成能不能看到这个圆,除了最后几个圆特殊以外都是等价的,然而我凉了,因为我把圆当成线段来处理,但是,有可能一个圆完全被遮住了,还有一个缝隙,就WA了 计算 ...

  7. Ionic入门八:头部与底部

    1.Header(头部) Header是固定在屏幕顶部的组件,可以包如标题和左右的功能按钮. ionic 默认提供了许多种颜色样式,你可以调用不同的样式名,当然也可以自定义一个. <div cl ...

  8. java 工厂模式和内部类的完美结合

    package com.bikeqx.test; public class Main{ public static void apply(ServiceFactory sf){ Service s = ...

  9. 洛谷P2597 [ZJOI2012] 灾难 [拓扑排序,LCA]

    题目传送门 灾难 题目描述 阿米巴是小强的好朋友. 阿米巴和小强在草原上捉蚂蚱.小强突然想,如果蚂蚱被他们捉灭绝了,那么吃蚂蚱的小鸟就会饿死,而捕食小鸟的猛禽也会跟着灭绝,从而引发一系列的生态灾难. ...

  10. C# NPOCO 轻量级ORM框架(进阶)

    继续翻译NPOCO wiki. 这篇将home上 下面的几个页面翻译. wiki地址:https://github.com/schotime/NPoco/wiki 上一篇: http://www.cn ...