C# の TryParse() を便利にしたい

## はじめに
`int.TryParse`や`double.TryParse`は便利ですね。
これをさらに便利に使えるようにして，かつエラーの可能性を下げたい，というのがこの記事の目的です。

## やりたいこと
やりたいことは2つあります：

### 文字列のメソッドとして使いたい
こんな風にできると便利です。
```csharp
string str = 10;
if (!str.TryParse<int>(out int val)) return;
Console.WriteLine(val);
```
ジェネリックな拡張メソッドを作ればいいのですが，`TryParse<T>`に渡す型パラメータごとに呼び出すメソッドの実態が異なるので，工夫が必要です。

### エラーに強くしたい
上の例で，条件を間違えたとします。
```csharp
string str = 10;
if (str.TryParse<int>(out int val)) return;  // 条件を逆にしてしまった
Console.WriteLine(val);
```
上記コードは普通にコンパイルが通ってしまいますが，できればエラーにしたいところです。

## 実装
(2/6追記)
@chocolamint さんに `IParsable<TSelf>`を教えていただきました。
これを使うと，拡張メソッドを以下のように書けて，色々ごにょごにょしなくて大丈夫です。
```csharp
static class StringUtil
{
    public static bool TryParse<T>(this string str, out T value)
        where T : struct, IParsable<T>
    {
        value = default;
        return T.TryParse(str, null, out value);
    }

    public static T? ParseTo<T>(this string str)
        where T : struct, IParsable<T>
    {
        T value = default;
        if (!T.TryParse(str, null, out value)) return null;

        return value;
    }
}
```

以下の内容は，`IParsable<TSelf>`のようなインターフェースが無いときにどうするか，という内容になっています。いつか同じような工夫が必要になったときのために残しておきます。
（追記終わり）

### ジェネリックなTryParse
```csharp
static class TryParseWithExpressionTree
{
    delegate bool TryParseDelegate<T>(string input, out T value);

    public static bool TryParse<T>(string str, out T value)
        where T : struct
    {
        return Cache<T>.TryParse(str, out value);
    }

    static class Cache<T>
        where T : struct
    {
        public static readonly TryParseDelegate<T> TryParse;

        static Cache()
        {
            var tryparse_method = typeof(T).GetMethod("TryParse",
                new[] { typeof(string), typeof(T).MakeByRefType() });
            if (tryparse_method is null)
            {
                TryParse = (string str, out T b) => {
                    T defaultValue = default;
                    b = defaultValue;
                    return false;
                };

                return;
            }

            var tryparse_arg_0 = Expression.Parameter(typeof(string));
            var tryparse_arg_1 = Expression.Parameter(typeof(T).MakeByRefType());
            var tryparse_args = new[] { tryparse_arg_0, tryparse_arg_1 };

            var callexp = Expression.Call(tryparse_method, tryparse_args);
            var lambdaexp = Expression.Lambda<TryParseDelegate<T>>(callexp, tryparse_args);
            TryParse = lambdaexp.Compile();
        }
    }
}
```

最初に，各値型が持つ `TryParse` と同じシグネチャのデリゲート型を宣言します（`TryParseDelegate`）。

次に，`Cache<T>.TryParse` 変数に，各値型の`TryParse`を呼び出すデリゲートを保持します。ここでは，式木を使って実装してみました。
型によっては `TryParse` がないものもあるので，その場合は常に失敗するラムダ式を保持します。
C# のジェネリクスは型パラメータが異なると別の型になるので，`Cache<int>.TryParse` は `int.TryParse`を参照し，`Cache<double>.TryParse` は `double.TryParse` を参照します。

### 拡張メソッド化とエラー対策

```csharp
static class StringUtil
{
    public static bool TryParse<T>(this string str, out T value)
        where T : struct
    {
        return TryParseWithExpressionTree.TryParse<T>(str, out value);
    }

    public static T? ParseTo<T>(this string str)
        where T : struct
    {
        T value = default;
        if (!TryParseWithExpressionTree.TryParse<T>(str, out value)) return null;

        return value;
    }
}
```

ここまでできれば，`TryParse<T>` を呼び出す拡張メソッドは簡単です（パラメータに`this`つけるだけ）。

エラー対策として，null許容値型を返す `ParseTo<T>` も作ってみました。
こんな感じで使うことができます。

```csharp
string str = "10";
if (str.ParseTo<int>() is not int x) return;
Console.WriteLine(x);
```

これだけだと `TryParse` と代り映えしないですが，パターンマッチを使っているのでエラー対策になります。
int xはパターンマッチ成功のときのみ初期化されるので，コンパイラがエラーを出してくれます。

```csharp
string str = "10";
if (str.ParseTo<int>() is int x) return;    // not が抜けた，条件を反転させた...
// 未割り当てのローカル変数 x エラーになる
// Console.WriteLine(x);
```

### その他
ジェネリックな`TryParse`は以下のようにも書けます。
力技でガリガリ書く必要がある代わりに，`typeof(T)`の比較がJIT最適化で消え，高速になるはずです。
数値型の数は限られているので，こちらの方がよかったかも...

`Unsafe.As<TFrom,TTo>`は物騒な名前ですが，ここでは安全です。
`typeof(T)`が数値型と等しいことを確認してから呼んでいるので，必ず`TFrom==TTo`になるためです。

```csharp
static class TryParseWithTypeofBranching
{
    public static bool TryParse<T>(string str, out T value)
        where T : struct
    {
        value = default;

        // if (typeof(T) == typeof(int))
        // {
        //     ref var v = ref Unsafe.As<T, int>(ref value);
        //     return int.TryParse(str, out v);
        // }
        if (typeof(T) == typeof(int)) return int.TryParse(str, out Unsafe.As<T, int>(ref value));
        if (typeof(T) == typeof(double)) return double.TryParse(str, out Unsafe.As<T, double>(ref value));

        return false;
    }
}
```