1. 程式人生 > >C#中的object類深入理解

C#中的object類深入理解

    C#中所有的類都直接或間接繼承自System.Object類,這使得C#中的類得以單根繼承。如果我們沒有明確指定繼承類,編譯器預設認為該類繼承自System.Object類。System.Object類也可用小寫的object關鍵字表示,兩者完全等同。自然C#中所有的類都繼承了System.Object類的公共介面,剖析它們對我們理解並掌握C#中類的行為非常重要。下面是僅用介面形式表示的System.Object類: 
namespace System
{
    public class Object
    {
        public static bool Equals(object objA,object objB){}
        public static bool ReferenceEquals(object objA,object objB){}
        public Object(){}
        public virtual bool Equals(object obj){}
        public virtual int GetHashCode(){}
        public Type GetType(){}
        public virtual string ToString(){}
        protected virtual void Finalize(){}
        protected object MemberwiseClone(){}
    }
    我們先看object的兩個靜態方法Equals(object objA,object objB),ReferenceEquals(object objA,object objB)和一個例項方法Equals(object obj)。在我們闡述這兩個方法之前我們首先要清楚面向物件程式設計兩個重要的相等概念:值相等和引用相等。值相等的意思是它們的資料成員按記憶體位分別相等。引用相等則是指它們指向同一個記憶體地址,或者說它們的物件控制代碼相等。引用相等必然推出值相等。對於值型別關係等號“==”判斷兩者是否值相等(結構型別和列舉型別沒有定義關係等號“==”,我們必須自己定義)。對於引用型別關係等號“==”判斷兩者是否引用相等。值型別在C#裡通常沒有引用相等的表示,只有在非託管程式設計中採用取地址符“&”來間接判斷二者的地址是否相等。 
     靜態方法Equals(object objA,object objB)首先檢查兩個物件objA和objB是否都為null,如果是則返回true,否則進行objA.Equals(objB)呼叫並返回其值。問題歸結到例項方法Equals(object obj)。該方法預設的實現其實就是{return this= =obj;}也就是判斷兩個物件是否引用相等。但我們注意到該方法是一個虛方法,C#推薦我們重寫此方法來判斷兩個物件是否值相等。實際上Microsoft.NET框架類庫內提供的許多型別都重寫了該方法,如:System.String(string),System.Int32(int)等,但也有些型別並沒有重寫該方法如:System.Array等,我們在使用時一定要注意。對於引用型別,如果沒有重寫例項方法Equals(object obj),我們對它的呼叫相當於this= =obj,即引用相等判斷。所有的值型別(隱含繼承自System.ValueType類)都重寫了例項方法Equals(object obj)來判斷是否值相等。 
     注意對於物件x,x.Equals(null)返回false,這裡x顯然不能為null(否則不能完成Equals()呼叫,系統丟擲空引用錯誤)。從這裡我們也可看出設計靜態方法Equals(object objA,object objB)的原因了--如果兩個物件objA和objB都可能為null,我們便只能用object. Equals(object objA,object objB)來判斷它們是否值相等了--當然如果我們沒有改寫例項方法Equals(object obj),我們得到的仍是引用相等的結果。我們可以實現介面IComparable來強制改寫例項方法Equals(object obj)。 
      對於值型別,例項方法Equals(object obj)應該和關係等號“==”的返回值一致,也就是說如果我們重寫了例項方法Equals(object obj),我們也應該過載或定義關係等號“==”操作符,反之亦然。雖然值型別(繼承自System.ValueType類)都重寫了例項方法Equals(object obj),但C#推薦我們重寫自己的值型別的例項方法Equals(object obj),因為系統的System.ValueType類重寫的很低效。對於引用型別我們應該重寫例項方法Equals(object obj)來表達值相等,一般不應該過載關係等號“==”操作符,因為它的預設語義是判斷引用相等。 
      靜態方法ReferenceEquals(object objA,object objB)判斷兩個物件是否引用相等。如果兩個物件為引用型別,那麼它的語義和沒有過載的關係等號“==”操作符相同。如果兩個物件為值型別,那麼它的返回值一定是false。 
      例項方法GetHashCode()為相應的型別提供雜湊(hash)碼值,應用於雜湊演算法或雜湊表中。需要注意的是如果我們重寫了某型別的例項方法Equals(object obj),我們也應該重寫例項方法GetHashCode()--這理所應當,兩個物件的值相等,它們的雜湊碼也應該相等。下面的程式碼是對前面幾個方法的一個很好的示例: 
using System;
struct A
{
    public int count;
}
class B
{
    public int number;
}
class C
{
    public int integer=0;
    public override bool Equals(object obj)
    {
        C c=obj as C;
        if (c!=null)
            return this.integer==c.integer;
        else
            return false;
    }
    public override int GetHashCode()
    {
        return 2^integer;
    }
}
class Test
{
    public static void Main()
    {
        A a1,a2;
        a1.count=10;
        a2=a1;
        //Console.Write(a1==a2);沒有定義“==”操作符
        Console.Write(a1.Equals(a2));//True
Console.WriteLine(object.ReferenceEquals(a1,a2));//False
        B b1=new B();
        B b2=new B();
        b1.number=10;
        b2.number=10;
        Console.Write(b1==b2);//False
        Console.Write(b1.Equals(b2));//False
Console.WriteLine(object.ReferenceEquals(b1,b2));//False
        b2=b1;
        Console.Write(b1==b2);//True
        Console.Write(b1.Equals(b2));//True
        Console.WriteLine(object.ReferenceEquals(b1,b2));//True
        C c1=new C();
        C c2=new C();
        c1.integer=10;
        c2.integer=10;
        Console.Write(c1==c2);//False
        Console.Write(c1.Equals(c2));//True
Console.WriteLine(object.ReferenceEquals(c1,c2));//False
        c2=c1;
        Console.Write(c1==c2);//True
        Console.Write(c1.Equals(c2));//True
        Console.WriteLine(object.ReferenceEquals(c1,c2));//True
    }
}
如我們所期望,編譯程式並執行我們會得到以下輸出: 
     True   False 
     False  False  False 
     True   True   True 
     False  True   False 
     True   True   True  
      例項方法GetType()與typeof的語義相同,它們都通過查詢物件的元資料來確定物件的執行時型別,我們在“第十講 特徵與對映”對此作詳細的闡述。 
     例項方法ToString()返回物件的字串表達形式。如果我們沒有重寫該方法,系統一般將型別名作為字串返回。 
      受保護的Finalize()方法在C#中有特殊的語義,我們將在“第五講 構造器與析構器”裡詳細闡述。 
      受保護的MemberwiseClone()方法返回目前物件的一個“影子拷貝”,該方法不能被子類重寫。“影子拷貝”僅僅是物件的一份按位拷貝,其含義是對物件內的值型別變數進行賦值拷貝,對其內的引用型別變數進行控制代碼拷貝,也就是拷貝後的引用變數將持有對同一塊記憶體的引用。相對於“影子拷貝”的是深度拷貝,它對引用型別的變數進行的是值複製,而非控制代碼複製。例如X是一個含有物件A,B引用的物件,而物件A又含有物件M的引用。Y是X的一個“影子拷貝”。那麼Y將擁有同樣的A,B的引用。但對於X的一個“深度拷貝”Z來說,它將擁有物件C和D的引用,以及一個間接的物件N的引用,其中C是A的一份拷貝,D是B的一份拷貝,N是M的一份拷貝。深度拷貝在C#裡通過實現ICloneable介面(提供Clone()方法)來完成。