C#でぬるぽを回避するどうでもいい方法

どうもペチパーです。嘘です逃げないで。まあ、どうでもいいPHPの例をまずは出しませう。あ、逃げないで、PHPの話はすぐやめるんで。

// ネストしてる配列
$hoge["huga"]["hage"]["tako"] = "なのなの";

// なのなの
$v = isset($hoge["huga"]["hage"]["tako"])
    ? $hoge["huga"]["hage"]["tako"]
    : "ない";

// 途中で欠けてる配列
$hoge["huga"] = "なのなの";

// ない
$v = isset($hoge["huga"]["hage"]["tako"])
    ? $hoge["huga"]["hage"]["tako"]
    : "ない";

全体的にキモいんですが、まあ無視してもらって、何が言いたいか、と言うとisset。これはネストしてる部分も一気に評価してくれるのです。フツーの関数だと常識的に考えて評価は先に内側で行うので配列の境界外で死ぬんですが、issetは関数みたいな見た目だけど実は言語構文なのだ!キモチワルイ。ともあれ、そんなわけでネストしてるものの有無を一気にチェックできるのです。

で、PHPのこと書いてるとサイト違うので、C#の話をしませう。

C#でネストネスト

PHPは何でも連想配列なのですが、C#だったらクラスのプロパティでしょうか。以下のようなシチュエーション。

// こういうドカドカした構造があるとして
class Hoge
{
    public Huga Prop1 { get; set; }
}

class Huga
{
    public Hage Prop2 { get; set; }
}

class Hage
{
    public string Prop3 { get; set; }
}

// こっちプログラム本体
var hoge = new Hoge();

// とちゅーでヌルぽが発生すると死んじゃうんの回避が醜悪!
var prop3 = (hoge != null && hoge.Prop1 != null && hoge.Prop1.Prop2 != null && hoge.Prop1.Prop2.Prop3 != null)
    ? hoge.Prop1.Prop2.Prop3
    : null;

!=nullの連鎖が面倒くさいですぅー。なんとかしてくださいぃー。ぴーHPに負けてるんじゃないですかぁー?とか言われてないですが言われてるってことにするので、しょうがないからエレガントな解決策を探してあげました、誰にも頼まれてませんが!

Love ExpressionTree

こーいう風に書ければいいんでしょ!下のhoge.GetValueOrDefaultってとこです。

// こんなHogeがあるとして
var hoge = new Hoge();

// すっきり!
var value = hoge.GetValueOrDefault(x => x.Prop1.Prop2.Prop3);
Console.WriteLine(value == null); // true

// 中身が詰まってたら
hoge = new Hoge { Prop1 = new Huga { Prop2 = new Hage { Prop3 = "ほげ!" } } };
var value2 = hoge.GetValueOrDefault(x => x.Prop1.Prop2.Prop3);
Console.WriteLine(value2); // ほげ!

すっごくスッキリしますね!イイね!

で、どーやってるかというと、ExpressionTreeでグルグルですよ。

public static class MonyaMonyaExtensions
{
    public static TR GetValueOrDefault<T, TR>(this T value, Expression<Func<T, TR>> memberSelector)
        where T : class
    {
        var expression = memberSelector.Body;

        var memberNames = new List<string>();
        while (!(expression is ParameterExpression))
        {
            if ((expression is UnaryExpression) && (expression.NodeType == ExpressionType.Convert))
            {
                expression = ((UnaryExpression)expression).Operand;
                continue;
            }

            var memberExpression = (MemberExpression)expression;
            memberNames.Add(memberExpression.Member.Name);
            expression = memberExpression.Expression;
        }

        object value2 = value;
        for (int i = memberNames.Count - 1; i >= 0; i--)
        {
            if (value2 == null) return default(TR);
            var memberName = memberNames[i];
            dynamic info = value2.GetType().GetMember(memberName)[0];
            value2 = info.GetValue(value2);
        }

        return (TR)value2;
    }
}

はい。というわけで、一つ言えるのは、これ、あんま速くないんで実用には使わないでくださいね、あくまでネタです、ネタ。

もにゃど

それもにゃど、という人はLINQでMaybeモナドでも検索しませう。既出なので私は書きません。

Profile

Yoshifumi Kawai

Cysharp, Inc
CEO/CTO

Microsoft MVP for Developer Technologies(C#)
April 2011
|
July 2024

Twitter:@neuecc GitHub:neuecc

Archive