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(); // 呼叫的就是裝箱後的值