在日常開發時,經常需要將對象從一種類型轉換爲另一種類型。CLR允許將對象轉換爲它的(實際)類型或者它的任何基類型。
C#不要求任何特殊語法即可將對象轉換爲它的任何基類型,因爲向基類型的轉換被認爲是一種安全的隱式轉換。然而,將對象轉換爲它的某個派生類型時,C#要求開發人員只能進行顯式轉換,因爲這種轉換可能在運行時失敗。可以理解爲:父類強制轉換成子類,子類隱式轉換成父類
internal class Employee
{
}
class Program
{
static void Main(string[] args)
{
// 子類隱式轉換成父類,不需要轉換
// 因爲new返回一個Employee對象,而Object是Employee的基類
Object o = new Employee();
// 子類可以自動轉父類 可以這麼理解把子類的實例em的地址賦值給了o1, o1的地址就是em的地址
// 這時就可以調用Employee 類的方法,點出Employee 類的屬性
Employee em = new Employee();
Object o1 = em;
// 父類強制轉換成子類, 需要轉換
// 因爲Employee派生自Object
Employee e = (Employee)o;
}
}
在運行時,CLR會檢查轉型操作,確定總是轉換爲對象得實際類型或者它的任意類型。下面的代碼雖然能通過編譯,但會在運行時拋出InvalidCastException異常:
internal class People
{
}
internal class Employee : People
{
}
internal class Manager : Employee
{
}
class Program
{
static void Main(string[] args)
{
// 創建實例的時候沒有將父類引用到子類對象,是無法轉換的
// PromoteEmployee不能運行成功
People p = new People();
PromoteEmployee(p);
// 創建實例的時候將父類引用到子類對象,是可以轉換的
// PromoteEmployee能運行成功
People p1 = new Employee();
PromoteEmployee(p1);
// Manager"屬於"(IS-A)Employee對象
// PromoteEmployee能運行成功
Manager m = new Manager();
PromoteEmployee(m);
// DateTime不是從Employee派生的
// PromoteEmployee不能運行成功
DateTime newYears = new DateTime(2011, 10, 1);
PromoteEmployee(newYears);
}
static void PromoteEmployee(Object o)
{
// 編譯器在編譯時無法準確地獲知對象0引用的是什麼類型,因此允許代碼通過編譯
// 但在運行時,CLR知道了o引用的是什麼類型(在每次執行轉型的時候)
// 所以它會覈實對象的類型是不是Employee或者從Employee派生的任何類型
Employee e = (Employee)o;
}
}
使用C#的is和as操作符來轉型
在C#語言中進行轉換的另一種方式是使用is操作符。is檢查對象是否兼容於指定類型,返回Boolean值true或false。注意,is操作符永遠不拋出異常,例如以下代碼:
static void Main(string[] args)
{
Object o = new Object();
Boolean b1 = (o is Object); //True
Boolean b2 = (o is Employee); //False
Boolean b3 = (o is Nullable); //False
}
is操作符通常像下面這樣使用:
static void Main(string[] args)
{
Object o = new Employee();
if( o is Employee )
{
Employee e = (Employee)o;
}
}
在上訴代碼中,CLR實際檢查兩次對象類型。is操作符首先覈實o是否兼容於Employee類型。如果是,在if語句內部轉型時,CLR再次覈實o是否引用一個Employee。CLR的類型檢查增強了安全性,但無疑會對性能造成一定的影響。這是因爲CLR首先必須判斷變量(o)引用的對象的實際類型。然後CLR必須遍歷繼承層次結構,用每個基類型去核對指定的類型(Employee)。由於這是一個相當常用的編程模式,所以C#專門提供了as操作符,目的就是簡化這種代碼的寫法,同時提升其性能。
static void Main(string[] args)
{
Object o = new Employee();
Employee e = o as Employee;
if(e != null)
{
}
}
在這段代碼中,CLR覈實o是否兼容於Employee類型;如果是,as放回對同一個對象的非null引用。如果o不兼容於Employee類型,as返回null。as操作符永遠不拋出異常。注意,as操作符造成CLR只校驗一次對象類型。if語句只檢查e是否爲NULL;這個檢查的速度不校驗對象的類型快得多。
測試代碼:
namespace ConsoleApplicationTest
{
internal class B{}
internal class D : B{}
class Program
{
static void Main(string[] args)
{
Object o1 = new Object(); //OK
Object o2 = new B(); //OK
Object o3 = new D(); //OK
Object o4 = o3; //OK
B b1 = new B(); //OK
B b2 = new D(); //OK
D d1 = new D(); //OK
B b3 = new Object(); //編譯時錯誤,正確:Object b3 = new B();
D d2 = new Object(); //編譯時錯誤,正確:Object b3 = new D();
B b4 = d1; //OK,子類可以自動轉父類
D d3 = b2; //編譯時錯誤,正確:D d3 = (D)b2;
D d4 = (D)d1; //OK
D d5 = (D)b2; //OK
D d6 = (D)b1; //運行時錯誤,創建實例的時候沒有將父類引用到子類對象
B b5 = (B)o1; //運行時錯誤,創建實例的時候沒有將父類引用到子類對象
B b6 = (D)b2; //OK,創建實例的時候將父類引用到子類對象
}
}
}