Chapter 3 - Item 25 : Prefer Generic Methods Unless Type Parameters Are Instance Fields

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

何時該使用泛型類別或泛型方法?本節提出一個判斷依據。

準則:盡量使用泛型方法,除非類別擁有泛型成員或實作泛型介面。

範例一:

public static class Utils<T>
{
    public static T max( T left, T right ) =>
        Comparer<T>.Default.Compare( left, right ) < 0 ?
            right : left;

    public static T min( T left, T right ) =>
        Comparer<T>.Default.Compare( left, right ) < 0 ?
            left : right;
}

private void test1( )
{
    double d1 = 4, d2 = 5;
    double max = Utils<double>.max( d1, d2 );

    string foo = "foo", bar = "bar";
    string min = Utils<string>.min( foo, bar );
}

如此設計雖然不會出錯,但每次呼叫方法時都必須指定泛型類別(多餘的程式碼);且在這個例子裡,double 已經有內嵌的 Math.Max 及 Math.Min 方法,目前寫法並非最有效率的方式。

改良後程式碼:

public static class Utils
{
    public static T max<T>( T left, T right ) =>
        Comparer<T>.Default.Compare( left, right ) < 0 ?
            right : left;

    public static double max( double left, double right ) =>
        Math.Max( left, right );

    public static T min<T>( T left, T right ) =>
        Comparer<T>.Default.Compare( left, right ) < 0 ?
            left : right;

    public static double min( double left, double right ) =>
        Math.Min( left, right );
}

private void test2( )
{
    double d1 = 4, d2 = 5;
    double max = Utils.max( d1, d2 );

    string foo = "foo", bar = "bar";
    string min = Utils.min( foo, bar );

    double? d3 = 12, d4 = null;
    double? max2 = Utils.max( d3, d4 );
}

我們將指定輸入型別的責任從類別移到方法;如此一來,讓編譯器在編譯階段自動選擇最適合的執行方法,也省去不必要的程式碼。

範例二:

public class CommaSeparatedListBuilder
{
    private StringBuilder _storage = new StringBuilder( );

    public void add<T>( IEnumerable<T> items )
    {
        foreach ( T item in items )
        {
            if ( _storage.Length > 0 )
                _storage.Append( ", " );

            _storage.Append( @"\" );
            _storage.Append( item.ToString( ) );
            _storage.Append( @"\" );
        }
    }

    public override string ToString( ) =>
        _storage.ToString( );
}

若是將泛型定義在類別,則一個 CommaSeparatedListBuilder 物件就只能對應到一種型別;若有多型別輸入的需求,就要初始化多個物件。在這個情境下,使用泛型方法是合理的選擇。

結論:
1. 若類別含有泛型成員或實作泛型介面,則將泛型定義在類別。

2. 除了 1. 的情況,應將泛型定義在方法;增加重用性與效率。