華麗に null チェックを行う

何気なく Stackoverflow を見てたら面白い質問を見つけたので備忘のためメモしておきます。

Cleaner way to do a null check in C#? - Stackoverflow

タイトルは「null チェック」と要約されてしまってますが、つまりあるオブジェクトが持つプロパティの更に先にあるプロパティの null チェックで良い方法はないか?という質問です。

例えばこんな感じですね。

public class Hoge
{
    public Fuga Fuga { get; set; }
}

public class Fuga
{
    public string Piyo { get; set; }
}

var hoge = new Hoge();
if (hoge.Fuga != null && hoge.Fuga.Piyo != null) // ここがイヤ
{
    // do something
}

すげー気持ち分かる。上の例はまだ大したことないですが、Stackoverflow の例みたいにさらに null チェックが続くともうダメですねー。

で、HasPiyo とか作れば?という回答があるんですが、

public class Hoge
{
    public Fuga Fuga { get; set; }
    public bool HasPiyo
    {
        return this.Fuga != null && this.Fuga.Piyo != null;
    }
}

public class Fuga
{
    public string Piyo { get; set; }
}

var hoge = new Hoge();
if (hoge.HasPiyo) // ここがイヤ
{
    // do something
}

うん、まぁガード節がイヤなら確かにこう書くよね、という感じですが。でもでも、上の例だと自分で Hoge クラスのインスタンス作ってますけど、引数とかで受け取ったら結局 null チェック必要ですよね?そしてガード節が出てきてデッドロックCode Contracts 使えればな-。

というか Code Contracts 使えたとしても Hasなんちゃら は結局必要なわけで、それもちょっと、という感じなんですよ。例だと Hoge クラスが Fuga.Piyo に触るんですか?とか。もっとプロパティがたくさん連なってたらどうすんだ?とか。それは設計が悪い?はい、ごもっとも。

で、色々もやもやしていたんですが、冒頭で紹介した Stackoverflow のベストアンサーがまさに私の期待に答えてくれて感動したというお話です。

public class IsNullVisitor : ExpressionVisitor
{
    public bool IsNull { get; set; }
    public object Current { get; set; }

    protected override Expression VisitMember(MemberExpression node)
    {
        base.VisitMember(node);
        
        if (this.CheckNull())
        {
            return node;
        }

        var member = (PropertyInfo) node.Member;
        this.Current = member.GetValue(Current, null);

        CheckNull();

        return node;
    }

    private bool CheckNull()
    {
        return (IsNull = this.Current == null);
    }
}

public static class MyExtension
{
    public static bool IsNull<T>(this T root, Expression<Func<T, object>> getter)
    {
        var visitor = new IsNullVisitor {Current = root};
        visitor.Visit(getter);
        return visitor.IsNull;
    }
}

var hoge = new Hoge();
if (hoge.IsNull(h => h.Fuga.Piyo)) // ステキ!
{
    // do something
}

ほぼ Stackoverflow の回答通りのコードです。CheckNull() が個人的に好きでないんですが、まぁそれは置いておいて。

いいですね、これ。ExpressionVisitor 使ったことなかった、っていうか Expression 系はほとんど使わ(え)ないんですが、こういう使い方をさくさく思いつけるようになると素敵ですね。勉強しよう。