1. 程式人生 > >淺析Object基類提供的Virtual Object.Equals, Static Object.Equals and Reference.Equals等三個方法

淺析Object基類提供的Virtual Object.Equals, Static Object.Equals and Reference.Equals等三個方法

override sting 簡單 了解 字段 發生 虛方法 覆蓋 出現

  當我們去查看object.cs源代碼文件的時候,會發現object基類提供了三種判斷相等性的方法。弄清楚每種方法存在的原因,也就是具體解決了什麽問題,對我們理解.net判斷對象相等性的邏輯很有幫助,下面讓我們分別來看看吧!

1、Virtual Object.Equals()方法

  實際上.net中提供了幾種比較相等性(equality)的方法,但是最基礎的方法就數object類中定義的virtual Object.Equals()了。下面讓我們以一個customer類來看看該方法的實際運作。

static void Main(string[] args)
        {
            Customer C1 = new Customer();
            C1.FirstName = "Si";
            C1.LastName = "Li";
            Customer C2 = new Customer();
            C2.FirstName = "San";
            C2.LastName = "Zhang";
            Console.WriteLine(C1.Equals(C2));
            Console.Read();
        }
        public class Customer
        {
            public string FirstName { get; set; }
            public string LastName { get; set; }
        } 

  上圖代碼的比較結果為False,這正是我們所期望的結果。因為兩個Customer實例的字段值包含不同的字符序列。細心一點的童鞋也許會發現,我們並沒有Customer類中定義Equals方法,它是怎麽進行比較的呢?這裏的Equals方法實際調用的就是Object基類的Virtual Object.Equals()方法。

  另外,有些童鞋在看到上面代碼中使用Equals方法進行比較時,也許會想為何不直接使用==運算符進行比較,這樣不是更簡潔明了麽?是的,使用==運算符確實使得代碼更加具有可讀性,但是,需要註意的是:它並不是.Net FrameWork框架的一部分。若想理解.net中是如何優雅的處理equality問題的,還是需要從equals方法開始學習。

  接下來,讓我們稍微改變下代碼,增加一個新的Customer實例C3,該實例和C1具有相同的字段值,看看會出現什麽樣的結果。

 static void Main(string[] args)
        {
            Customer C1 = new Customer();
            C1.FirstName = "Si";
            C1.LastName = "Li";
            Customer C2 = new Customer();
            C2.FirstName = "San";
            C2.LastName = "Zhang";
            Customer C3 = new Customer();
            C2.FirstName = "Si";
            C2.LastName = "Li";
            Console.WriteLine(C1.Equals(C3));
            Console.Read();
        }

  上圖代碼中C1和C3的比較結果為false。至於原因,想必大多數童鞋都已經知道了,那就是Object基類的Equals虛方法比較引用相等性,即兩個變量是否指向同一個實例對象。很明顯,C1和C3指向兩個不同的實例對象,因此,Equals比較的結果就是False。

  如果我們需要比較兩個Customer實例的值,字段值相等就說他們是相等的,那麽我們就需要自己去實現Equals方法,來覆蓋Object提供的虛Equals方法。關於實現過程中需要註意的地方本文暫不討論。

2、String的相等性判斷

  FCL庫中有幾個引用類型,因為在開發中經常被開發人員拿來做相等性的比較,所以微軟對他們提供了相等性的實現,該實現override了Object的Virtual Equals方法,可以比較值相等而不是引用相等。其中,最常用到的一個就是String類型。下面我們以一小段代碼來演示String的Equals方法。

static void Main(String[] args)  
        {  
            string s1 = "Hello World";  
            string s2 = string.Copy(s1);  
              
            Console.WriteLine(s1.Equals((object)s2));  
        }  

  上面代碼中,我們定義了兩個sting類型的引用變量,s1和s2。兩者具有相同的字符序列值,但卻是兩個不同的引用。

  另外,細心的童鞋也許會註意到,我們將Equals方法的參數強轉到object類型,在實際開發中,明顯不會這樣做。這裏之所以這樣做,就是因為String類型定義了多個Equals方法,如下圖所示。而在這裏,我們需要確保String類型的對Object的Equals方法的override實現被調用。

  比較的結果正合我們的預期,String的override Equals方法比較兩個字符串的內容是否包含相同的字符序列,若是則返回true,否則,返回false。

  微軟在FCL中定義的引用類型並不多,對於這些引用類型,一般均提供了對Object的Equals方法的override實現,用來比較值。除了Sting類型之外,還有Delegate和Tuple。

3、Value Types的相等性判斷

  這次,我們以customer類相似的例子舉例,但將使用customer struct類型。

static void Main(string[] args)
        {
            Customer C1 = new Customer();
            C1.FirstName = "Si";
            C1.LastName = "Li";
            Customer C2 = new Customer();
            C2.FirstName = "San";
            C2.LastName = "Zhang";
            Customer C3 = new Customer();
            C3.FirstName = "Si";
            C3.LastName = "Li";
            Console.WriteLine(C1.Equals(C2));
            Console.WriteLine(C1.Equals(C3));
            Console.Read();
        }
        public struct Customer
        {
            public string FirstName { get; set; }
            public string LastName { get; set; }
        }

  運行結果為:第一個Equals為False,第二個為True。我們知道,object基類的虛Equals方法比較引用而非值,但本例中struct是value type,若比較引用就毫無意義可言。僅就比較的結果來看,似乎在比較C1和C3的值。但在Customer struct類型的定義中並沒有任何代碼override了object的虛Equals方法。這是怎麽做到的呢?

  答案就是:struct 類型均繼承自System.ValueType(繼承自 System.Object),System.ValueType類型override了object的虛Equals方法,該override方法的實現會遍歷value type中的每一個字段,然後在每個字段上調用各自的Equals方法。若每個字段比較的結果均相等就返回true,否則,返回false。

3.1 Value Types相等性判斷的開銷

  使用微軟為value type提供的默認相等性判斷方法是有代價的。該override的Equals方法在內部是通過反射實現的。這是不可避免的,因為 System.ValueType是一個基類型,它不知道繼承的子類型的信息。因此,只有在運行時通過反射發現自定義類型的字段信息,這就造成了性能損失。

3.2 Value Types相等性判斷的可選方案

  為了快速的比較自定義value type,一般而言,我們需要自己override objece基類的Equals方法。實際上,微軟已經為FCL中的大多數內置值類型提供了相應的實現。

4、Object的Static Equals方法

  使用object基類的虛Equals進行相等性判斷存在一個問題,就是調用Equals方法的實例對象不能為null,否則,將拋出Null Reference Exception。這是因為不能在null上調用實例方法。

  Object的static Equals方法就是為了解決這個問題而出現的。當待比較的兩個實例對象中有一個是null時,該靜態Equals方法將返回false。

  相信不少童鞋會好奇,若兩個實例對象均為null,會發生什麽呢?答案就是返回true。在.net的世界中,null總是等於null。

  如果我們去查看Object類的static Equals方法的實現,就會發現其實它的代碼邏輯十分簡單明了,下面讓我們一起看看吧。

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

  從上面代碼中,可以看到static Equal方法首先判斷兩個參數是否指向同一個實例對象(包括兩者都為null),若是,則直接返回true。接著判斷兩者之一是否為null,若是則返回false。最後,若控制流到達最後一條語句,則調用object的虛Equals方法。這意味著,static Equals方法除了進行null檢查之外,它總是和virtual Equals方法返回相同的結果。此外,需要註意的是,若我們override了Object的virtual Equals方法,那麽,static Equals方法中對virtual Equals的調用將自動調用override的Equals方法。

5、Object的ReferenceEquals方法

  ReferenceEquals方法存在的目的是為了比較兩個引用變量是否指向同一個實例對象。不少童鞋對此持懷疑態度,virtual Equals和static Equals方法就是在比較引用相等性,有必要單獨造一個方法來比較引用相等性麽?

  不錯,上面兩個方法確實檢測引用相等性,但它們不保證一定會檢測,因為virtual Equals方法能被overridden來比較值而非引用。

  因此,對於沒有override Equals方法的類型來說,ReferenceEquals方法將和Equals方法產生相同的結果。我們可以拿前面的String類型例子來說明這一點。

static void Main(String[] args)  
        {  
            string s1 = "Hello World";  
            string s2 = string.Copy(s1);  
              
            Console.WriteLine(s1.Equals((object)s2));  
            Console.WriteLine(ReferenceEquals(s1,s2));  
        }  

  從上面的結果可以看出,第一個Equals方法返回true,而ReferenceEquals方法返回false。

  我們知道,在C#中static方法不能被override,這就保證了ReferenceEquals方法的行為會始終保持一致,這是很有意義的,因為我們總是會需要一個穩定不變的方法來判斷引用相等。

  

淺析Object基類提供的Virtual Object.Equals, Static Object.Equals and Reference.Equals等三個方法