Chapter 3 - Item 23 : Delegates to Define Method Constraints on Type Parameters

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

Item 20 中提到使用泛型限制表達式可能會有過度限制的問題(e.g. 用戶端被強制要求實作介面),本節提出在方法限制輸入、輸出型別;消除過度限制的負面影響與增加設計彈型。

範例一:

public static class Example
{
    public static T Add<T>( T left, T right, Func<T, T, T> AddFunc ) =>
        AddFunc( left, right );
}

Client

int a = 6;
int b = 7;
int sum = Example.Add( a, b, ( x, y ) => x + y );

這是一個簡單的範例,Func<T,T,T> 讓外部可以自定義程式邏輯(相當於實作介面);好處是只有在需要呼叫該方法時才需要明確定義。

範例二:

public static class Utilities
{
    public static IEnumerable<TOutput> zip<T1, T2, TOutput>
        ( IEnumerable<T1> left, IEnumerable<T2> right,
        Func<T1, T2, TOutput> generartor )
    {
        IEnumerator<T1> leftSequence = left.GetEnumerator( );
        IEnumerator<T2> rightSequence = right.GetEnumerator( );

        while ( leftSequence.MoveNext( ) && rightSequence.MoveNext( ) )
            yield return generartor( leftSequence.Current, rightSequence.Current );

        leftSequence.Dispose( );
        rightSequence.Dispose( );
    }
}

Client

double[ ] xValues = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int[ ] yValues = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };

List<Point> values = new List<Point>(
    Utilities.zip( xValues, yValues,
    ( x, y ) => new Point( x, y ) ) );
Note:此處用 T1, T2 而非僅用 T,原因在於整數型別的輸入在此情境下是合法的(int 可被隱式轉型成 double);當型別無法隱式轉型時,編譯器會跳出錯誤。

範例三:

public class Point
{
    public double X { get; }
    public double Y { get; }

    public Point( TextReader reader )
    {
        var line = reader.ReadLine( );
        var fields = line.Split( ',' );

        if ( fields.Length != 2 )
            throw new InvalidOperationException(
                "Input format incorrect." );

        if ( !double.TryParse( fields[ 0 ], out double valueX ) )
            throw new InvalidOperationException(
                "Could not parse X value." );
        else
            X = valueX;

        if ( !double.TryParse( fields[ 1 ], out double valueY ) )
            throw new InvalidOperationException(
                "Could not parse Y value." );
        else
            Y = valueY;
    }
}

public class InputCollection<T>
{
    private readonly Func<TextReader, T> _readFunc;
    private List<T> _thingsRead = new List<T>( );

    public InputCollection( Func<TextReader, T> readFunc )
    {
        _readFunc = readFunc;
    }

    public void readFromStream( TextReader reader ) =>
        _thingsRead.Add( _readFunc( reader ) );

    public IEnumerable<T> Values => _thingsRead;
}

Client

var readValues = new InputCollection<Point>(
    ( inputStream ) => new Point( inputStream ) );

當客戶端想要從檔案串流讀取並轉換成 Point 時,只需呼叫 readFromStream 並帶入 reader 參數即可;讀取不同檔案需求也可滿足,同時也支援 foreach loop (IEnumerable<Point> Values)。回傳泛型型別也讓用戶端可自定義轉換方法。

結論:
1. 對於一般化的介面來說,明確定義實作介面是必要的(e.g. IEquatable<T>, IEnumerable<T>, IComparable<T>)。

2. 在只有特定情況使用的泛型方法,可以考慮使用泛型方法限制的方式實作。