ごっそログ

勉強したことなどを書いていきます

[C#] 型引数を変えながらジェネリクスメソッドをループ処理で実行する

メモ。

経緯

引数が指定した型に変換可能であるかを判定して結果を返すジェネリックメソッド CanParse<T>を作った。
シグネチャは以下の通り。

public static bool CanParse<T>(string value) where T : IConvertible, new()
{
    // 判定処理(省略)
}

このメソッドの動きを確認するため、
適当な値が入った以下のようなリストを用意した。

var values = new List<string>()
{
    "-129", "-128", "-1", "0", "127", "128", "255", "256",
    "65535", "65536", "-32769", "-32768", "32767", "32768",
    "-2147483649", "-2147483648", "2147483647", "2147483648"
};

このリスト内の全ての値を、全てのプリミティブな数値型(byte, decimal, double, float, int, uint, long...)に対して総当たりで実行し、メソッドの動きを確認したかったのだがループ処理の実装で詰まってしまった。

// 型リスト
var types = new List<Type>()
{
    typeof(byte),
    typeof(decimal),
    typeof(double)
    // 以下略
};
// 総当たり
foreach (var type in types)
{
    foreach (var value in values)
    {
        // ここでコンパイルエラー
        Console.WriteLine(string.Format("{0}<{1}>: {2}\t({3})", nameof(CanParse), type, CanParse<type>(value), value ?? "null"));
    }
}

CanParse<type>(value)が問題。
型引数は変数として扱うことができないため、上記の書き方だとエラーとなってしまう。

対処法

デリゲートで実装することでループ処理できるようになった。

using System;
using System.Collections.Generic;
using System.Reflection;

// delegate bool CanParseDelegate(string value);
// 上のスコープで↑を宣言済

// 値リスト
var values = new List<string>()
{
    "-129", "-128", "-1", "0", "127", "128", "255", "256",
    "65535", "65536", "-32769", "-32768", "32767", "32768",
    "-2147483649", "-2147483648", "2147483647", "2147483648"
};

// デリゲートリスト
var delegates = new List<CanParseDelegate>
{
    new CanParseDelegate(CanParse<bool>),
    new CanParseDelegate(CanParse<byte>),
    new CanParseDelegate(CanParse<sbyte>),
    new CanParseDelegate(CanParse<char>),
    new CanParseDelegate(CanParse<decimal>),
    new CanParseDelegate(CanParse<double>),
    new CanParseDelegate(CanParse<float>),
    new CanParseDelegate(CanParse<int>),
    new CanParseDelegate(CanParse<uint>),
    new CanParseDelegate(CanParse<long>),
    new CanParseDelegate(CanParse<ulong>),
    new CanParseDelegate(CanParse<short>),
    new CanParseDelegate(CanParse<ushort>)
};

foreach (var dlg in delegates)
{
    foreach (var value in values)
    {
        // 型引数取得
        var arg = dlg.Method.GetGenericArguments();
        Console.WriteLine(string.Format("{0}<{1}>: {2}\t({3})", nameof(CanParse), arg[0], dlg(value), value ?? "null"));
    }
}

まとめ

  • 型引数は変数として扱うことができないため、ループ処理する際にはデリゲートを用意する等の工夫が必要。
  • リフレクションでごり押しする方法もあるらしい。(試していない)
  • GetGenericArguments()で型引数が取得できる。
    • Typeの配列として返されるため、参照する際は添字が必要な点に注意。