【C#】え!!C#でif式を?できらぁ!

タイトルは正確ではありません。すいません。if式っぽく書ける条件判定関数的なものです。if式っぽく書きたかったので作ってみました。

C#のifは文であって式ではありません。式ではないので

var name = "ほげほげ君";

// こうは書けない
var exName1 = if (name.EndsWith("君")) {
                return name;
              }
              else {
                  return name + "君";
              }

// こう書く
var exName2 = "";
if (name.EndsWith("君"))
    exName = name;
else
    exName2 = name + "君";

みたいに書かなければいけません。人によるかと思いますが、これ読みにくくないですか?exName2への代入が2箇所あるので読んでて鬱陶しいです。何度もこういうコードが出てくると嫌です。

このくらいのコードであれば三項演算子で解決するという方法もあるでしょう。

var name = "ほげほげ君";
var newName = name.EndsWith("君") ? name : name + "君";

これであれば代入は1箇所ですし分かりやすいですね。しかし、次のようなケースではどうでしょう。

// 三項演算子
var newName = name.EndsWith("さん") ? Regex.Replace(name, "さん$", "君") : (name.EndsWith("君") ? name : name + "君");

// if文
var newName = "";
if (name.EndsWith("さん"))
    newName = Regex.Replace(name, "さん$", "君");
else if (name.EndsWith("君"))
    newName = name;
else
    newName = name + "君";

最後が”さん”であれば”君”に変換するという場合は、三項演算子を使えば分かりにくくなり、if else if elseでは悪戯に長くなりますし結局代入箇所が分散していますね。もっとこう、スパっと書けないものでしょうか。ということでif式っぽいものを作りました。

// ex.1
var newName1 = Ext.If(name.EndsWith("さん"), Regex.Replace(name, "さん$", "君"))
                  .ElseIf(!name.EndsWith("君"), name + "君")
                  .Else(name);

あるいは

// ex.2
var newName2 = name.EndsWith("さん").Then(Regex.Replace(name, "さん$", "君"))
                                    .ElseIf(!name.EndsWith("君"), name + "君")
                                    .Else(name);

上記のように書くためのメソッドです。条件に対して欲しい結果をメソッドチェーンで書いていけるのでなんとなく読みやすいと思います。

まずはex.1にあるIf ElseIf Elseというメソッドがこちら。

public static Tuple<bool, T> If<T>(bool term, T value)
{
    if (term)
    {
        // 条件が成立する場合はvalue
        return new Tuple<bool, T>(true, value);
    }
    else
    {
        // 条件が成立しない場合はT型のdefault
        return new Tuple<bool, T>(false, default);
    }
}

public static Tuple<bool, T> ElseIf<T>(this Tuple<bool, T> prev, bool term, T value)
{
    if (prev.Item1)
    {
        // 前の式が成立している場合はそれをそのまま返す
        return prev;
    }
    else if (term)
    {
        // 条件が成立する場合はvalue
        return new Tuple<bool, T>(true, value);
    }
    else
    {
        // 条件が成立しない場合はT型のdefault
        return new Tuple<bool, T>(false, default);
    }
}

public static T Else<T> Else(this Tuple<bool, T> prev, T value)
{
    if (prev.Item1)
    {
        // 前の式が成立する場合は、その値を取り出して返す
        return prev.Item2;
    }
    else
    {
        // 前の式が成立しない場合は規定値としてvalueを返す
        return value;
    }
}

if式を実現するにあたり、前の式の計算結果と値を受け取って引き継ぎつつ、最終的には値のみを返すという処理をする必要があります。そのため、Tuple<bool, T>でそれらの情報を受け渡し、最終的にElseT型の値部分のみを返しています。

ElseIfElseではTuple<bool, T>の拡張メソッドを使用しています。なので、このメソッドはstaticなクラスで宣言する必要があります。

続いてex.2にあるThenについて。ex.2ElseIfElseex.1のものと同様です。

public static Tuple<bool, T> Then<T>(this bool term, T value)
{
    if (term)
    {
        // 条件が成立する場合はvalue
        return new Tuple<bool, T>(true, value);
    }
    else
    {
        // 条件が成立しない場合はT型のdefault
        return new Tuple<bool, T>(false, default);
    }
}

これも単純にex.1Ifの引数termを、引数ではなくメソッドチェーン的に取れるようにthisキーワードで拡張したメソッドになります。thisキーワードにしている引数は明示的に指定することも可能なので、なんならex.1IfメソッドをこのThenメソッドで代用することも可能です。(条件を指定するならIf的な名前がいいと思って別に定義しました)

ElseIfは必要に応じて省略することも、複数記述することもできます。

var point = GetPoint();
var score = Ext.If(point == 100, "S")
               .ElseIf(point >= 80, "A")
               .ElseIf(point >= 60, "B")
               .ElseIf(point >= 40, "C")
               .ElseIf(point >= 30, "D")
               .Else("E");

このように書けます。if文でちまちま書いたり三項演算子でネストして書いていくよりはるかにわかりやすいと思います。

FuncActionを受け取るよう拡張すれば、ラムダ式などを使ってより柔軟な表現が出来るようになると思います。たとえばActionだと、

public static bool Then(this bool term, Action action)
{
    if (!term)
        return false;

    action();
    return true;
}

public static bool ElseIf(this bool previous, bool term, Action action)
{
    if (previous)
        return true;
    else if (!term)
        return false;

    action();
    return true;
}

public static void Else(this bool previous, Action action)
{
    if (previous)
        return;

    action();
}

// ***

var point = GetPoint();
(point == 100).Then(() => Console.WriteLine("S"))
              .ElseIf((point >= 80), () => Console.WriteLine("A"))
              .ElseIf((point >= 60), () => Console.WriteLine("B"))
              .ElseIf((point >= 40), () => Console.WriteLine("C"))
              .ElseIf((point >= 30), () => Console.WriteLine("D"))
              .Else(() => Console.WriteLine("E"));

こんな感じで書けます。

ジェネリクスを使用していますが制約などはつけていませんし、より最適なコードもあるかもしれません。ある程度拡張性はあると思いますが、もっといいやり方あるよ!などがあれば、是非教えてください。

次の記事

偏食の話New!!