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(); // 調用的就是裝箱后的值
