Chapter 1 - Item 3 : Prefer the is or as Operator to Casts

Effective C# (Covers C# 6.0), (includes Content Update Program): 50 Specific Ways to Improve Your C#, 3rd Edition By Bill Wagner 讀後心得

在 C# 這種強型別的語言中,有時需要轉型操作(e.g. object 轉型成其他型別)。作者在本章節建議使用 as 或 is 用以轉型,以下針對 as operator 和 Cast 做一些比較。

1. 使用 as operator 比 Cast 效能更好且語意更簡潔。
    
Version one :

object o = Factory.GetObject( );

MyType t = o as MyType;

if ( t != null )
{
    // work with t, it's MyType.
}

else
{
    // report the failure.
}

Version two :

object o = Factory.GetObject( );

try
{
    MyType t;
    t = ( MyType ) o;

    if ( t != null )
    {
        // work with t, it's MyType.
    }

    else
    {
        // report o is null, all of null value could be casted to another 
        // reference type that will return null.
    }
}

catch ( InvalidCastException )
{
    // report the conversion failure.
}

顯然 Version one 程式碼較為簡短,利用 try catch block 控制流程除了消耗額外資源;也會造成程式碼閱讀較為困難。使用 as operator 轉型只需檢查回傳值是否為 null 值(強制轉型即便轉型成功也需要做相同檢查,因為所有的 object null 值皆可轉型成其他參考型別並回傳 null)。
    
2. Cast 可自定義轉型,而 as operator 不行。

public class SecondType
{
    private MyType _value;

    // Other detail elided.

    // Conversion operator.
    // This converts a SecondType to a MyType.
    public static implicit operator MyType( SecondType t )
    {
        return t._value;
    }
}

Version one :

object o = Factory.GetObject( );

// o is SecondType.
MyType t = o as MyType; // Fails, o is not MyType.

if ( t != null )
{
    // work with t, it's MyType.
}

else
{
    // report the failure.
}

Version two : 

object o = Factory.GetObject( );

try
{
    MyType t;
    t = ( MyType ) o; // Fails, o is not MyType.

    if ( t != null )
    {
        // work with t, it's MyType.
    }

    else
    {
        // report t is null, all of object type null value could be casted to 
        // another reference type that will return null.
    }
}

catch ( InvalidCastException )
{
    // report the conversion failure.
}

兩種情況皆會轉型失敗。原因在於 as operator 只關心在執行階段 o 是否為 MyType;而上述情況很顯然 o 不是 MyType,所以回傳 null。而 Version two 雖然有自定義的轉型方法,但只在編譯階段有效;也就是說 o 在編譯階段並非 SecondType,故執行後 CLR 會依照預設方式執行(檢查 o 在執行階段是否為 MyType),最後跳出例外。
    
而要在 Version two 成功執行自定義轉型方法,需要將程式改寫。
    
Version three :

object o = Factory.GetObject( );
SecondType st = o as SecondType; // cast o to SecondType by as operator.

try
{
    MyType t;
    t = ( MyType ) st; // pass, st could be casted to MyType by definition.

    if ( t != null )
    {
        // work with t, it's MyType.
    }

    else
    {
        // report t is null, null value of object type could be casted to
        // another reference type that will return null.
    }
}

catch ( InvalidCastException )
{
    // report the conversion failure.
}

3. 使用 as operator 可得到較為一致的結果。

t = ( MyType )st; // 宣告不同 st 型別會有不同結果。
    
t = st as MyType; // as operator 只關注執行階段 st 是否能轉型成功。

4. as operator 只能使用在轉型為參考型別(可為 null 型別),而 Cast 無此限制。

object o = Factory.GetValue( );
    
int i = o as int // Does not compile.

由於 as operator 在轉型失敗時會回傳 null;而 int 無法為 null,故編譯失敗。
    
這樣的問題可以使用 Nullable<T> 解決。

object o = Factory.GetValue( );

var i = o as int?; // it's work whenever o is int or int?.

if ( i != null )
    Console.WriteLine( i.Value );

5. foreach loop 使用 Cast 而非 as operator 讓轉型更加彈性(不限制轉為參考型別)。

public void UseCollectionV1( IEnumerable theCollection )
{
    foreach ( MyType t in theCollection )
        t.DoStuff( );
}

等同於

public void UseCollectionV2( IEnumerable theCollection )
{
    IEnumerator it = theCollection.GetEnumerator( );

    while ( it.MoveNext( ) )
    {
        MyType t = ( MyType ) it.Current;
        t.DoStuff( );
    }
}
結論:
1. 在一般情況下,使用 as operator 比 Cast 更能得到一致的結果、更好的可讀性與效能。

2. 在繼承體系中,無論 as operator 或 Cast 皆能從子類別轉型為父類別;若要進一步精確的比較型別,可使用 object.GetType( )。