てくメモ

trivial な notes

【C#】文字列から<T>型への汎用コンバート : IParseble<TSelf>

IParsable<TSelf>というインターフェースがある。(.NET 7~)
https://learn.microsoft.com/ja-jp/dotnet/api/system.iparsable-1?view=net-8.0

実装している型は、ParseメソッドとTryParseメソッドを持つ。 例えば以下のように使える。

public T ConvertByIParsable<T>(string str)
    where T : IParsable<T>
{
    return T.Parse(str, default);
}


このインターフェース、.NET 7 においては数値の型が備えていた。

そして、.NET 8 において、stringboolも備えるようになった。(今回は関係ないがSystem.Net.IPAddressも)

このため、数値も文字列も真偽値も一緒くたというような、例えば以下の記事のような場合に、一括でIParsable<TSelf>として投げ込めるようになった。
【C#】文字列から<T>型への汎用コンバート - てくメモ


上記記事のTypeDescriptor・型での分岐に、今回IParsable<TSelf>を並べて比較。(BenchmarkDotNet)

ベンチマークコード折りたたみ

private readonly string[] lines =
[
    "12",
    "12.3",
    "c",
    "str",
    "true"
];

public T ConvertByDescriptor<T>(string str)
{
    var obj = TypeDescriptor.GetConverter(typeof(T)).ConvertFromString(str);
    return obj is T result
        ? result
        : throw new NotSupportedException();
}

public T ConvertByTypeSwitch<T>(string str)
{
    if (typeof(T) == typeof(int))
    {
        return (T)(object)int.Parse(str);
    }
    else if (typeof(T) == typeof(double))
    {
        return (T)(object)double.Parse(str);
    }
    else if (typeof(T) == typeof(char))
    {
        return (T)(object)char.Parse(str);
    }
    else if (typeof(T) == typeof(string))
    {
        return (T)(object)str;
    }
    else if (typeof(T) == typeof(bool))
    {
        return (T)(object)bool.Parse(str);
    }

    throw new NotSupportedException();
}

public T ConvertByIParsable<T>(string str)
    where T : IParsable<T>
{
    return T.Parse(str, default);
}

[Benchmark]
public string ByTypeDescriptor()
{
    var n = ConvertByDescriptor<int>(lines[0]);
    var d = ConvertByDescriptor<double>(lines[1]);
    var c = ConvertByDescriptor<char>(lines[2]);
    var s = ConvertByDescriptor<string>(lines[3]);
    var b = ConvertByDescriptor<bool>(lines[4]);

    return $"{n}, {d}, {c}, {s}, {b}";
}

[Benchmark]
public string ByTypeSwitch()
{
    var n = ConvertByTypeSwitch<int>(lines[0]);
    var d = ConvertByTypeSwitch<double>(lines[1]);
    var c = ConvertByTypeSwitch<char>(lines[2]);
    var s = ConvertByTypeSwitch<string>(lines[3]);
    var b = ConvertByTypeSwitch<bool>(lines[4]);

    return $"{n}, {d}, {c}, {s}, {b}";
}

[Benchmark]
public string ByIParsable()
{
    var n = ConvertByIParsable<int>(lines[0]);
    var d = ConvertByIParsable<double>(lines[1]);
    var c = ConvertByIParsable<char>(lines[2]);
    var s = ConvertByIParsable<string>(lines[3]);
    var b = ConvertByIParsable<bool>(lines[4]);

    return $"{n}, {d}, {c}, {s}, {b}";
}

 Method           | Mean       | Error    | StdDev   | Gen0   | Allocated |
----------------- |-----------:|---------:|---------:|-------:|----------:|
 ByTypeDescriptor | 1,250.2 ns | 612.1 ns | 33.55 ns | 0.0534 |     168 B |
 ByTypeSwitch     |   309.9 ns | 242.1 ns | 13.27 ns | 0.0229 |      72 B |
 ByIParsable      |   292.8 ns | 197.6 ns | 10.83 ns | 0.0229 |      72 B |

IParsable<TSelf>.Parseメソッドはいわゆるインターフェースの静的仮想メンバーであり、パフォーマンス的憂いもなし。