對象的比較與排序(四):對象的相等判斷Equals和IEquatable(轉)

本節覆蓋了以下知識點:
一、Object.Equals()方法;
二、“==”,“!=”運算符重載;
三、IEquatable<T> 接口。
 
一、重寫Equals方法。
衆所周知,Object類型有一個名爲Equals的實例方法可以用來確定兩個對象是否相等。Object的Equals的默認實現比較的是兩個對象的引用等同性。而Object的派生類ValueTpye重寫了Equals方法,它比較的是兩個對象的邏輯等同性。也就是說,在C#裏,引用類型的默認Equals版本關注的是引用等同性,而值類型關注的是邏輯等同性。當然,這並不總能滿足我們的要求。所以每當我們更在意引用類型的邏輯等同性的時候,我們就應該重寫Equals方法。
上一個日記通過實現IComparable<T>接口的對象可以相互比較,但比較結果是0,1和-1,顯得不直觀,在許多情況下,可能只需要知道兩個對象是否相等,而不需要知道這兩個對象
“誰大誰小”,這時,可以重寫object類的 Equals方法。
    public override bool Equal(object obj)
    {
        if(this.CompareTo(obj)==0)
            return true;
        else
            return false;
    }
    
可以看到,Equals方法的內部調用CompareTo方法進行對象比較。
 
總結:
C#中有兩種不同的相等:引用相等和值相等。值相等,即兩個對象包含相同的值。例如,兩個值爲2的整數數具有值相等性。引用相等意味着要比較的兩個對象不是兩個對象,而是兩個“對象引用”,這兩個“對象引用”引用的是同一個對象。
1、對於值類型
  對於值類型,如果對象的值相等,則相等運算符 (==) 返回 true,否則返回 false。
2、對於引用類型
  對於string 以外的引用類型,如果兩個對象引用同一個對象,則 == 返回 true。對於 string 類型,== 比較字符串的值。 
  ==操作比較的是兩個變量的值是否相等。 
  equals()方法比較的是兩個對象的內容是否一致.equals也就是比較引用類型是否是對同一個對象的引用。
  首先我們看一段程序:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    namespace CSharpDemo
    {
        class Person
        {
            private string name;
            public string Name
            {
                get { return name; }
                set { name = value; }
            }
            public Person(string name)
            {
                this.name = name;
            }
        }
        class Program
        {
            static void Main(string[] args)
            {
                int i = 1;
                int j = 1;
                
                Console.WriteLine(i == j); //True
                Console.WriteLine(i.Equals(j)); //True
                string a = new string(new char[] { 'h', 'e', 'l', 'l', 'o' });
                string b = new string(new char[] { 'h', 'e', 'l', 'l', 'o' });
                Console.WriteLine(a == b); //True
                Console.WriteLine(a.Equals(b)); //True
                object g = a;
                object h = b;
                Console.WriteLine(g == h); //False
                Console.WriteLine(g.Equals(h)); //True
                Person p1 = new Person("person");
                Person p2 = new Person("person");
                Console.WriteLine(p1 == p2); //False
                Console.WriteLine(p1.Equals(p2)); //False
                Person p3 = new Person("person");
                Person p4 = p3;
                Console.WriteLine(p3 == p4); //True
                Console.WriteLine(p3.Equals(p4)); //True
                Console.ReadKey();
            }
        }
    }
Equals方法的調用輸出情況
輸出結果在對應的行後面標出。
  爲什麼會出現這個答案呢?因爲值類型是存儲在內存中的堆棧(以後簡稱棧),而引用類型的變量在棧中僅僅是存儲引用類型變量的地址,而其本身則存儲在堆中。 
==操作比較的是兩個變量的值是否相等,對於引用型變量表示的是兩個變量在堆中存儲的地址是否相同,即棧中的內容是否相同。 
equals操作表示的兩個變量是否是對同一個對象的引用,即堆中的內容是否相同。 
而字符串是一個特殊的引用型類型,在C#語言中,重載了string 對象的很多方法方法(包括equals()方法),使string對象用起來就像是值類型一樣。 
因此在上面的例子中,字符串a和字符串b的兩個比較是相等的。 
對於object g 和object h 時內存中兩個不同的對象,所以在棧中的內容是不相同的,故不相等。而g.equals(h)用的是string的equals()方法故相等(多態)。如果將字符串a和b作這樣的修改: 
string a="aa"; 
string b="aa"; 
則,g和h的兩個比較都是相等的。這是因爲系統並沒有給字符串b分配內存,只是將"aa"指向了b。所以a和b指向的是同一個字符串(字符串在這種賦值的情況下做了內存的優化)。 
  對於p1和p2,也是內存中兩個不同的對象,所以在內存中的地址肯定不相同,故p1==p2會返回false,又因爲p1和p2又是對不同對象的引用,所以p1.equals(p2)將返回false。 
  對於p3和p4,p4=p3,p3將對對象的引用賦給了p4,p3和p4是對同一個對象的引用,所以兩個比較都返回true。
 
二、運算符重載
PS:若一個對象重載了"=="運算符,則也必須重載"!="運算符,這是一個基本原則。
關於運算符重載,請參見下一個日記。
 
 總結ReferenceEquals,Equals, == 的區別:
  ReferenceEquals:靜態方法,不能重寫,只能比較引用,如果有一個參數爲null會返回false,不會拋出異常,如果比較值類型,則始終返回false。
  Equals:實例方法,默認可以比較引用也可以比較值,可以重寫。可以按值比較對象。
  靜態Equals:靜態方法,不能重寫。如果沒有重寫Equals,比較引用,或者比較值。如果重載了Equals方法。比較引用,或者比較值,或者按重寫的Equals比較,如果其中一個參數爲null,拋出異常
  ==運算符:可以按引用比較,也可以按值比較。可以重寫。是操作運算符。
  最後需要的是,如果重載了Equals,則最好是重載GetHashCode,必須重載==運算符。
 
三、IEquatable<T> 接口
在某些場合可能不需要判斷對象的大小,而僅需要給一個“兩個對象是否相等”的結論,爲些.NET又定義了一個專用於“比較對象是否相等”的接口
    public interface IEquatable<T>
    {
        bool Equals(T other);
    }
    
關於這個接口和前面幾個日記提到的接口,具體可以參見:
http://www.cnblogs.com/symphony2010/archive/2011/08/19/2145890.html
 
四、綜合示例
總結前面的幾個日記,現給出一個綜合示例
 
定義一個Circle類
    using System;
    using System.Collections.Generic;
    using System.Text;
    namespace ObjectCompare
    {
        /// <summary>
        /// 圓心
        /// </summary>
        public struct CircleCenter
        {
            public double x;
            public double y;
        }
        class Circle:IComparable,IComparable<Circle>,IEquatable<Circle>
        {
            public double Radius = 0;   //半徑
            public CircleCenter center ; //圓心
            //實現IComparable接口定義的方法
            public int CompareTo(object obj)
            {
                if (!(obj is Circle))
                    throw new ArgumentException("只能比對Cirlce對象");
                return CompareTo(obj as Circle);
               
            }
            //實現IComparable<T>接口定義的方法
            public int CompareTo(Circle other)
            {
                double ret = Math.Abs(other.Radius - this.Radius);
                if (ret < 1e-3)
                    return 0;
                if (other.Radius < this.Radius)
                    return 1;
                return -1;
            }
            //覆蓋Object類的GetHashCode方法
            public override int GetHashCode()
            {
                //整數部分與小數點後3位相同的對象生成相同的哈希值
                return (int)(Radius*1000);
            }
            //重寫Object類的Equals方法
            public override bool Equals(object obj)
            {
                if (this.CompareTo(obj) == 0)
                    return true;
                return false;
            }
            //實現IEquatable<Circle>接口定義的方法
            public bool Equals(Circle other)
            {
                return this.CompareTo(other) == 0;
            }
            //----------------------------------------
            //重載相關的運算符
            //----------------------------------------
            public static bool operator ==(Circle obj1, Circle obj2)
            {
                return obj1.Equals(obj2);
            }
            public static bool operator !=(Circle obj1, Circle obj2)
            {
                return !(obj1.Equals(obj2));
            }
            public static bool operator >(Circle obj1, Circle obj2)
            {
                if (obj1.CompareTo(obj2) > 0)
                    return true;
                return false;
            }
            public static bool operator <(Circle obj1, Circle obj2)
            {
                if (obj1.CompareTo(obj2) < 0)
                    return true;
                return false;
            }
            public static bool operator <=(Circle obj1, Circle obj2)
            {
                if ((obj1.CompareTo(obj2) < 0) || (obj1.CompareTo(obj2) == 0))
                    return true;
                return false;
            }
            public static bool operator >=(Circle obj1, Circle obj2)
            {
                if ((obj1.CompareTo(obj2) > 0) || (obj1.CompareTo(obj2) == 0))
                    return true;
                return false;
            }
        }
    }
    
    測試代碼
    using System;
    using System.Collections.Generic;
    using System.Text;
    namespace ObjectCompare
    {
        class Program
        {
            static void Main(string[] args)
            {
                Circle obj1 = new Circle { Radius = 100.1 };
                Circle obj2 = new Circle { Radius = 100.9 };
                //測試覆蓋的方法
                Console.WriteLine(obj1.GetHashCode());//100100
                Console.WriteLine(obj2.GetHashCode());//100900
                Console.WriteLine(obj1.CompareTo(obj2)); //-1
                Console.WriteLine(obj1.Equals(obj2));//false
                //測試重載的運算符
                Console.WriteLine(obj1 == obj2);    //false
                Console.WriteLine(obj1 != obj2);    //true
                Console.WriteLine(obj1 >= obj2);    //false
                //以下測試Circle對象數組的排序功能
                Circle[] circles = new Circle[10];  //創建Circle對象數組
                Random ran = new Random();
                for (int i = 0; i < 10; i++)
                {
                    circles[i] = new Circle { Radius = ran.Next(1, 1000)/100.0 };
                }
                Console.WriteLine("原始數組:");
                Array.ForEach<Circle>(circles, (circle) => { Console.WriteLine("圓對象的哈希代碼:{0},半徑:{1}", circle.GetHashCode(), circle.Radius); });
                Console.WriteLine("\n排序之後:");
                Array.Sort(circles);
                Array.ForEach<Circle>(circles, (circle) => { Console.WriteLine("圓對象的哈希代碼:{0},半徑:{1}", circle.GetHashCode(), circle.Radius); });
                Console.ReadKey();
            }
        }
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章