在某些場合我們需要手動對 object 類型進行轉型的操作,目前常用的轉型方式有以下幾種:
- 轉換運算式(Cast expression)
- as 運算子
- pattern matching
首先建立兩個類別 Car
和 Bus
,我們可以設定乘客數量,並且添加使用者自訂的轉換型別方法讓我們可以直接進行隱含轉換。
public class Car
{
private int _passenger;
public int Passenger
{
get { return _passenger; }
set
{
if (value > 3)
throw new ArgumentException("Too Many People");
_passenger = value;
}
}
public Car(int passenger)
{
Passenger = passenger;
}
public static implicit operator Bus(Car car)
{
return new Bus(car.Passenger);
}
}
public class Bus
{
private int _passenger;
public int Passenger
{
get { return _passenger; }
set { _passenger = value; }
}
public Bus(int passenger)
{
Passenger = passenger;
}
public static implicit operator Car(Bus bus)
{
return new Car(bus.Passenger);
}
}
型別轉換
為了方便測試,我們建立一個 Factory 類別,並回傳 object 類型。
public class Factory
{
public static object GetObject(int passenger, Type t)
{
return Activator.CreateInstance(t, passenger);
}
}
最傳統的的轉換方式需要搭配 try/catch
來攔截例外錯誤,還需要另外檢查 null 類型,因為 null 可以直接轉換成任何參考型別。
// 1.轉換運算式(Cast expression)
object o = Factory.GetObject(3, typeof(Car));
try
{
if (o != null)
{
Car t = (Car)o;
Console.WriteLine($"Passenger Count: {t.Passenger}");
}
}
catch (Exception ex)
{
Console.WriteLine("Error");
}
使用 as 運算子如果轉換錯誤並不會拋出例外,因此只要檢查 null 類型就好。
// 2. as 運算子
object o = Factory.GetObject(3, typeof(Car));
Car t = o as Car;
if (t != null)
Console.WriteLine($"Passenger Count: {t.Passenger}");
else
Console.WriteLine("Error");
使用 is 運算子搭配 pattern matching 寫法可以更精簡。
// 3. pattern matching
object o = Factory.GetObject(3, typeof(Car));
if (o is Car t)
Console.WriteLine($"Passenger Count: {t.Passenger}");
else
Console.WriteLine("Error");
使用者自訂的轉換型別方法
接下來測試一下之前寫的自訂的轉換型別方法,根據程式碼可以得知 Car
和 Bus
可以來回轉換,唯一的限制是 Car
類型 Passenger 不能超過三人否則會報錯。
從三名乘客的 Car
隱含轉換成 Bus
類別可以轉換成功。
var c = new Car(3);
Bus b = c;
Console.WriteLine($"Passenger Count: {b.Passenger}");
從四名乘客的 Bus
隱含轉換成 Car
類別因為超過三人所以會報錯。
var b = new Bus(4);
Car c = b;
Console.WriteLine($"Passenger Count: {b.Passenger}");
另外我們也可以用明確轉型,但是要注意自訂的轉換型別方法只能用 Cast expression
處理轉型操作,沒辦法使用 as
和 pattern matching
。
//無法編譯
Car c = new Car(3);
Bus b = c as Bus;
Console.WriteLine($"Passenger Count: {b.Passenger}");
Car c = new Car(3);
Bus b = (Bus)c;
Console.WriteLine($"Passenger Count: {b.Passenger}");
那麼使用上一節的 Factory.GetObject
方法建立出來的隱藏型別的 object 是否也能進行隱含轉換呢?
object o = Factory.GetObject(3, typeof(Car));
if (o != null)
{
Bus c = (Bus)o;
Console.WriteLine($"Passenger Count: {c.Passenger}");
}
else
Console.WriteLine("Error");
答案是不行的,因為編譯器不會知道 o 的執行期型別,編譯器認為沒有 object
至 Bus
的使用者自訂轉換型別方法。
Foreach
在這個例子中我們傳入一個非泛型的 IEnumerable,並且使用 foreach 遍歷每一個元素。
可以發現 foreach 背後將元素轉換為 Car
型別,並不是由我們使用一開始提到的三種轉換方式來轉換。
public void UseCollection(IEnumerable array)
{
foreach (Car c in array)
{
Console.WriteLine("Success");
}
}
使用反編譯軟體查看背後的程式碼,發現他內部是使用 Cast expression
來轉換,所以要注意可能會拋出 InvalidCastException
IEnumerator enumerator = array.GetEnumerator ();
try
{
while (enumerator.MoveNext ())
{
Car c = (Car)enumerator.Current;
Console.WriteLine ("Success");
}
}
finally
{
IDisposable disposable = enumerator as IDisposable;
if (disposable != null)
{
disposable.Dispose ();
}
}
Summary
最好是盡量避免轉型的操作,如果一定要的話優先使用 as
與 is
來進行,不僅可以避免拋出 InvalidCastException
,
而且只需要查結果是否為 null
就好,另外語意也更接近英文可讀性會提高,另外如果方法有提供泛型的版本那就優先使用。