ParseOrDefault

match があれば TryParse いらないんじゃないか - 予定は未定Blog版という記事を見て、outとかrefは確かにしょっぱい。そういえばF#はTryParseはTuple返しで、おまけに let success, value = int.TryParse "123" という自然な多値返しが出来てCOOLなんだよねー。などと思ってC#でTuple返しにしたものを書いたりしたのですが、どうもしっくりこなくて延々と弄っているうちに明後日の方向へ。

さて、で、「数値に変換できるなら変換して返し、できないなら -1 を返す」というシチュエーションは大変多くて、それにoutを使って組み上げるのは、とてもかったるい、大変避けたい。かといってStringへの拡張メソッドを大量に生やすのも、IntelliSense的な観点からして抑えたい(それにしてもT4 Template使って生成ってのは面白いですね)。なので、汎用的に使えることは捨てて、上述のシチュエーション、変換出来るなら変換して、出来ないなら指定したデフォルト値を返すことにのみ絞って、拡張メソッドにしました。Stringに生やすのは抵抗感があるって場合はUtil.ParseOrDefaultとかにしてもいいと思います。

using System;

static class Program
{
    static void Main(string[] args)
    {
        // 例えばURLのクエリストリングのパースをしたい時とかありますよね!
        // num=100にint.Parse(num)はnum=hugaが来たら死ぬのでデフォでは0にしたいとか

        var r1 = "100".ParseOrDefault(int.TryParse, -1); // 100
        var r2 = "huga".ParseOrDefault(int.TryParse, -1); // -1

        // 勿論、int.Parse以外でも何でもいけます
        var r3 = "2000/12/12".ParseOrDefault(DateTime.TryParse, DateTime.Now);
        // デフォルト値を省く時は型の明記が必要
        var r4 = "2000/12/12".ParseOrDefault<DateTime>(DateTime.TryParse);
    }
}

public static class StringExtensions
{
    public delegate bool TryParse<T>(string input, out T value);

    public static T ParseOrDefault<T>(this string input, TryParse<T> tryParse)
    {
        return input.ParseOrDefault(tryParse, default(T));
    }

    public static T ParseOrDefault<T>(this string input, TryParse<T> tryParse, T defaultValue)
    {
        T value;
        return tryParse(input, out value) ? value : defaultValue;
    }
}

out付きの汎用Funcはデフォルトでは定義されていません。なので、自前で定義する必要があります。この辺の話は@takeshikさんのスライドわんくま東京#38 LT 「Func<> と ref / out 小咄」が詳しいのでどうぞ。こんなデリゲートは単体では使うことは滅多にないでしょうから、static classの中に閉じ込めておくことで名前空間を汚しません(笑)

引数にT defaultValueを要求することで、型推論が働いてTryParseを渡す際に明示的な型付けが不要になります。C#の型推論(と言っていいのかな?)がScalaやF#に比べて弱いのは事実なので、ここは、C#でも自然な形で扱えるような誘導、設計が大事です。型を書いたら負けだと思っている、みたいな。まあ、どんなに頑張っても負けるときは負けるので、その時は潔く書くことにします。

ちなみに、ParseOrDefaultは最初はこんな形でした。酷過ぎる。私は骨の髄まで関数型言語脳ならぬLinq脳なので、とにかくまず無理矢理にでもLinqで処理する方法を考えて、出来上がってふと冷静に眺めると、普通に書けよ馬鹿、ということに気付いて普通に書き直すというサイクルを取ってます。馬鹿すぎる。DbExecutorで紹介したyield returnで一個のみの列挙を返すというのを、やたら使って見ちゃったりするところが麻疹。Repeat(value,1) と Repeat(value,int.MaxValue)があればLinqは何でも記述出来るんです病。

Profile

Yoshifumi Kawai

Cysharp, Inc
CEO/CTO

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

Twitter:@neuecc GitHub:neuecc

Archive