Chapter 3 - Item 18 : Always Define Constraints That Are Minimal and Sufficient

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

本節提出利用泛型限制表達式簡化程式碼,設計剛剛好符合需求的 API;過多或過少的限制都會造成客戶端使用上的問題,包含不明確的呼叫或是過多的限制。

1. 利用泛型限制表達式設計簡潔的 API。

考慮需要設計一個判斷兩個物件是否相等的 API,輸入參數型別為泛型。
    
設計方法一: 

public static bool areEqual<T>( T left, T right )
{
    if ( left == null )
        return right == null;

    var leftVal = left as IComparable<T>;

    if ( leftVal != null )
    {
        if ( right is IComparable<T> )
            return leftVal.CompareTo( right ) == 0;

        else
            throw new ArgumentException( "Type does not implement IComparable<T>.",
                nameof( right ) );
    }

    // Failure.
    else
    {
        throw new ArgumentException( "Type does not implement IComparable<T>.",
            nameof( left ) );
    }
}

這樣子的設計有幾項缺點:

a. 重覆的程式碼(擲出例外)。
b. 巢狀的 if - else 判斷式。
c. 無法在編譯時期檢查到錯誤(必須等到執行階段判斷輸入型別是否有實作 IComparable<T>)。
    
用泛型限制表達式重構。
    
設計方法二:

public static bool areEqual2<T>( T left, T right ) where T : IComparable<T>
    => left.CompareTo( right ) == 0;

程式碼明顯簡潔許多,這樣設計有幾項優點:
    
a. 沒有重複的程式碼。
b. 移除了所有 if - else 判斷式。
c. 在編譯時期就可以檢查輸入型別有無實作 IComparable<T>。

2. 利用泛型限制表達式限制剛剛好的設計,不多不少。

考慮以下程式碼:

public static bool areEqual<T>( T left, T right )
    => left.Equals( right );

這樣的寫法需要考慮到當輸入型別為值型別時,會產生裝箱操作( System.Object.Equals)。

為了避免多餘的操作,可以限制 T 需實作的介面。

public static bool areEqual<T>( T left, T right ) where T : IEquatable<T>
    => left.Equals( right );

如此一來,客戶端必須實作 IEquatable<T> 才能正常編譯程式;且不會發生裝箱操作。

Note:泛型限制表達式是兩面刃;當限制越多,使用上就會更不方便。在這個例子裡,也需考慮是否要為了值型別而額外撰寫程式碼(客戶端需實作 IEquatable<T>)。
結論:
1. 善加利用泛型限制表達式設計 API。

2. 仔細考慮限制的範圍是否符合需求。