Chapter 3 - Item 24 : Do Not Create Generic Specialization on Base Classes or Interfaces

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

雖然泛型方法限制在設計上很彈性,但仍有一些需要注意的地方。如果有繼承關係或介面的輸入型別;應避免對其定義特化泛型方法。

範例:

1. 首先定義繼承關係與介面方法。

public class MyBase
{
}

public interface IMessageWritter
{
    void writeMessage( );
}

public class MyDerived : MyBase, IMessageWritter
{
    void IMessageWritter.writeMessage( ) =>
        Debug.WriteLine( "Inside MyDerived.writeMessage." );
}

public class AnotherType : IMessageWritter
{
    public void writeMessage( ) =>
        Debug.WriteLine( "Inside AnotherType.writeMessage." );
}

2. 定義輸入型別為基類、泛型、介面等 writeMessage 方法多載。

public static void writeMessage( MyBase b ) =>
    Debug.WriteLine( "Inside writreMessage( MyBase )." );

public static void writeMessage<T>( T obj )
{
    Debug.Write( "Inside writeMessage<T>( T ) : " );
    Debug.WriteLine( obj.ToString( ) );
}

public static void writeMessage( IMessageWritter obj )
{
    Debug.Write( "Inside writeMessage( IMessageWritter ) :  " );
    obj.writeMessage( );
}

3. 撰寫測試方法。

var d = new MyDerived( );
Debug.WriteLine( "Calling Item24.writeMessage" );
writeMessage( d );
Debug.WriteLine( string.Empty );

Debug.WriteLine( "Calling through IMessageWritter interface." );
writeMessage( ( IMessageWritter ) d );
Debug.WriteLine( string.Empty );

Debug.WriteLine( "Cast to base object" );
writeMessage( ( MyBase ) d );
Debug.WriteLine( string.Empty );

Debug.WriteLine( "Another Type test :" );
var anobj = new AnotherType( );
writeMessage( anobj );
Debug.WriteLine( string.Empty );

Debug.WriteLine( "Cast to IMessageWritter." );
writeMessage( ( IMessageWritter ) anobj );

輸出:

    // 呼叫泛型輸入方法。
    Calling Item24.writeMessage
    Inside writeMessage<T>( T ) : Item24.MyDerived
    
    // 呼叫介面輸入方法。
    Calling through IMessageWritter interface.
    Inside writeMessage( IMessageWritter ) :  Inside MyDerived.writeMessage.
    
    // 呼叫基類輸入方法。
    Cast to base object
    Inside writreMessage( MyBase ).
    
    // 呼叫泛型輸入方法。
    Another Type test :
    Inside writeMessage<T>( T ) : Item24.AnotherType
    
    // 呼叫介面輸入方法。
    Cast to IMessageWritter.
    Inside writeMessage( IMessageWritter ) :  Inside AnotherType.writeMessage.
    
泛型方法將會自動在編譯期選擇最適當的方法。隨著顯式的轉換輸入型別,其輸出結果有會有所不同。而在編譯時期就決定呼叫方法,省去了執行階段的額外判斷。
    
4. 避免針對基類或介面定義特化的泛型方法。

public static void writeMessage2<T>( T obj )
{
    if ( ( object ) obj is MyBase myBase ) // It's a C# 7.0 problem.
        writeMessage( myBase );

    else if ( obj is IMessageWritter iMessageWritter )
        writeMessage( iMessageWritter );

    else
    {
        Write( "Inside writeMessage<T>( T ) : " );
        WriteLine( obj.ToString( ) );
    }
}

應避免這樣的寫法,在執行階段需要額外檢查輸入型別;且無法在早期編譯階段就決定呼叫方法,增加後續除錯風險。然而並非完全不可在泛型方法中判斷輸入型別,Item 19 中在執行階段判斷輸入型別,以達到高效率的初始化方式是可行的。

Note:第一個 if 判斷式需顯式轉型為 object,否則會無法通過編譯;目前還不知道原因為何,看後續 VS2017 會不會針對此問題修正。
結論:
1. 若要特化基類或介面的泛型方法,需在方法中判斷所有子類別或實作類別。

2. 避免 1. 的設計方式,減少執行階段額外操作。