C# 開発 blog

.NET C#での開発で気づいたことなど

FirstOrDefault実行後のnull判定を簡略化する拡張メソッド

IEnumerable<Bar>.FirstOrDefaultを実行後に、nullでなければBarの値を取得する等の場合、

var bars = new Bar[] { ... };
var bar = bars.FirstOrDefault(b => b.Name == "ABC");
// or
// var bar = bars.Where(b => b.Name == "ABC").FirstOrDefault();
var hoge = "";
if (bar != null)
    hoge = bar.Hoge;

もしくは、

var bars = new Bar[] { ... };
var hoge = bars.Where(b => b.Name == "ABC").Select(b => b.Hoge).FirstOrDefault();

のように書いていました。 その後、nullだったら何もせず、nullでないなら処理が続くような場合は、

var foos = new Foo[] { ... };
var bars = new Bar[] { ... };
var hoge = bars.Where(b => b.Name == "ABC").Select(b => b.Hoge).FirstOrDefault();
if (hoge != null) {
    // 以下hogeを使用してコーディング
    var id = foos.Where(f => f.Hoge == hoge).Select(f => f.Id).FirstOrDefault();
    if (id != null) {
        //以下idを使用してコーディング
    }
}

となります。nullでないなら処理が進み、またその処理の中でFirstOrDefaultをして、またまたnullでないなら...と書くのがめんどいです。 また、一つの処理にまとめた場合、

var foos = new Foo[] { ... };
var bars = new Bar[] { ... };
var foo = foos.Where(f => f.Hoge == bars.Where(b => b.Name == "ABC").Select(b => b.Hoge).FirstOrDefault()).Select(f => f.Id).FirstOrDefault();

barsのFirstOrDefaultがnullでも処理が続いちゃうんですよね。nullの場合、例外が発生します。 処理の流れ的にも最初にfoosから始まるのも嫌です。 なので、私は以下の拡張メソッドを使用しています。

internal static class EnumerableExtensions
{

    public static TResult FirstOrDefaultAndGet<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> func) {
        if (source == null)
            return default(TResult);
        var ret = source.FirstOrDefault();
        if (ret == null || ret.Equals(default(TSource)))
            return default(TResult);
        return func(ret);
    }

    public static TResult FirstOrDefaultAndGet<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, bool> predicate, Func<TSource, TResult> func) {
        if (source == null)
            return default(TResult);
        var ret = source.FirstOrDefault(predicate);
        if (ret == null || ret.Equals(default(TSource)))
            return default(TResult);
        return func(ret);
    }
 
}

これを使用すると上記の例を、

var foos = new Foo[] { ... };
var bars = new Bar[] { ... };
var id = bars.Where(b => b.Name == "ABC").FirstOrDefaultAndGet(b => foos.Where(f => f.Hoge == b.Hoge).FirstOrDefaultAndGet(f => f.Id));

このように1文で書けますし、どこかでnullが発生した段階で処理を止めることができます。