2014年4月20日

IEnumerable.IsNullOrEmpty

string.IsNullOrEmpty の配列版。あると便利。
※ null で例外が欲しい場合は、Enumerable.Any() がある。


// generic 版
public static bool IsNullOrEmpty<T>(this IEnumerable<T> source)
{
    if( source == null ) return true;
    using( var e = source.GetEnumerator() ) return !e.MoveNext();
}

// 非 generic 版(普通は不要)
public static bool IsNullOrEmpty(this IEnumerable source)
{
    return source == null || !source.GetEnumerator().MoveNext();
}

ちなみに、string にも使用可能。(IEnumerable<char> なので)

おまけ

パフォーマンスを上げたい場合は、下記メソッドを別途定義する。
ICollection<T> を実装しているクラス(List<T> や配列)は、Count の参照だけで済むため、早くなる。

public static bool IsNullOrEmpty<T>(this ICollection<T> source)
{
    return source == null || source.Count == 0;
}

おまけのおまけ

下記パターンでパフォーマンスを測定してみた。
(A) 引数が IEnumerable<T>
(B) 引数が IEnumerable<T> と ICollection<T> (おまけのパターン)
(C) 引数が IEnumerable<T> で、内部で ICollection<T> にキャスト試行(下記実装)

public static bool IsNullOrEmpty<T>(this IEnumerable<T> source)
{
    if( source == null ) return true;
    var c = source as ICollection<T>;
    if( c != null ) return c.Count == 0;
    using( var e = source.GetEnumerator() ) return !e.MoveNext();
}

[測定コード]

static void Main(string[] args)
{
    var testdata = new[]{
        new int[]{},
        new[]{1}
    };

    var loopCount = int.Parse(args[0]);
    var result = false;
    var sw = new Stopwatch();
    sw.Start();
    for( var i=0; i<loopCount; i++ ){
        result = testdata[i % testdata.Length].IsNullOrEmpty();
    }
    sw.Stop();
    Console.WriteLine(sw.Elapsed);
    Console.WriteLine(result);
}

[結果]
loopCount (A) (B) (C)
10000 0.0012318 0.0004056 0.0056082
100000 0.0038177 0.0010864 0.0455488
※コマンドプロンプトで実施(csc /optimize)
※10回の算術平均、単位は[s]

結果は、(B) > (A) > (C) となった。(左の方が高性能)

(C) が遅い原因はキャスト試行処理なので、つまり、パフォーマンス目的でキャスト処理を追加する際は要注意。
(B) のようにキャストをオーバーロードに任せられるパターンは、そうすべき。(メソッドが増えるデメリットはあるけど)

検証環境

Windows 7 64bit/Visual Studio 2010 SP1/.NET 4.0
Intel(R) Celeron(R) CPU G530 @ 2.40GHz/DDR3 8GB(4GB x 2)

0 件のコメント:

コメントを投稿