1. 程式人生 > 實用技巧 >C#中的object型別

C#中的object型別

OBJECT型別

object(System.Object)是所有型別的終極父類,所有型別都可以向上轉換為object。

下面我們看一個例子

public class Stack
{
    int position;
    object[] data = new object[10];
    public void Push (object obj) { data[position++] = obj; }
    public object Pop() { return data[--position]; }
}

這是一個後進先出的這麼一個棧,因為是object型別,所以你可以Push和Pop任意的型別到這個棧裡

Stack stack = new Stack();
stack.Push ("sausage");
string s = (string) stack.Pop(); // 向下轉換,顯式的轉換一下

Console.WriteLine (s); // sausage

object是引用型別,但值型別可以轉換為object,反之亦然。(型別統一)

stack.Push (3);
int three = (int) stack.Pop();

在值型別和object之間轉換的時候,CLR必須執行一些特殊的工作,以彌補值型別和引用型別之間語義上的差異,這個過程就叫做裝箱和拆箱。

裝箱(boxing)

裝箱就是把值型別的例項轉換為引用型別的例項的動作,目標引用型別可以是object,也可以是某個介面

int x = 9;
object obj = x; // 把int值裝箱

拆箱(unboxing)

拆箱正好和裝箱相反,把物件轉換為原來的值型別

int y = (int)obj; // 還原int值

拆箱需要顯式的轉換

拆箱過程中,執行時會檢查這個值型別和object物件的真實型別是否匹配,如果不匹配就丟擲InvalidCastException

object obj = 9; // 9 在這裡是int型別
long x = (long) obj; // InvalidCastException

下面的轉換就是可以的,int型別可以隱式的轉換為long型別,但是像上面的直接拆箱就不可以:

object obj = 9;
long x = (int) obj;

裝箱對於型別統一是非常重要的,但是系統設計還是不夠完美,比如陣列和泛型只支援引用轉換,不支援裝箱

object[] a1 = new string[3]; // 可以的
object[] a2 = new int[3]; // 會報錯

裝箱拆箱的複製

  • 裝箱會把值型別的例項複製到一個新的物件
  • 拆箱會把這個物件的內容再複製給一個值型別的例項

看個例子:

int i = 3;
object boxed = i;
i = 5;
Console.WriteLine (boxed); // 3

靜態和執行時型別檢查

C#的程式既會做靜態的型別檢查(編譯時),也會做執行時的型別檢查(CLR)

靜態檢查:就是不執行程式的情況下,讓編譯器保證你的程式的正確性,比如 int x = "5"; 這麼寫肯定是不行的

執行時的型別檢查由CLR執行,發生在向下的引用轉換或拆箱的時候。

object y = "5";
int z = (int) y; // 執行時報錯,向下轉換失敗

執行時檢查之所以可行是因為每個在heap上的物件內部都儲存了一個型別token。這個token可以通過呼叫object的GetType()方法來獲取。

GetType方法與typeof操作符

所有C#的型別在執行時都是以System.Type的例項來展現的

有兩種方式可以獲得System.Type物件:一是在例項上呼叫GetType()方法;第二個是在型別名上使用typeof操作符。

GetType是在執行時被算出的,typeof是在編譯時被算出的(靜態)(當涉及到泛型型別引數時,它是由JIT編譯器來解析的)

System.Type

System.Type的屬性有:型別名稱、Assembly、基類等等。直接看例子:

using System;
public class Point { public int X, Y; }
class Test
{
    static void Main()
    {
        Point p = new Point();
        Console.WriteLine (p.GetType().Name); // Point
        Console.WriteLine (typeof (Point).Name); // Point
        Console.WriteLine (p.GetType() == typeof(Point)); // True
        Console.WriteLine (p.X.GetType().Name); // Int32
        Console.WriteLine (p.Y.GetType().FullName); // System.Int32
    }
}

ToString方法

ToString()方法會返回一個型別例項的預設文字表示

所有的內建型別都重寫了該方法

int x = 1;
string s = x.ToString(); // s is "1"

我們可以在自定義的型別上重寫ToString()方法,如果你沒有重寫該方法,那麼就會返回該類的名稱,是一個包括名稱空間的全名

public class Panda
{
    public string Name;
    public override string ToString() => Name;
}
   
 ...

Panda p = new Panda { Name = "Petey" };
Console.WriteLine (p); // Petey

當你呼叫一個被重寫的object成員的時候,例如在值型別上直接呼叫ToString()方法,這時候就不會發生裝箱操作,但是如果你進行了轉換,那麼裝箱操作就會發生

int x = 1;
string s1 = x.ToString(); // 這裡就沒有發生裝箱
object box = x;
string s2 = box.ToString(); // 呼叫的就是裝箱後的值