LoginSignup
5
3

More than 3 years have passed since last update.

[C#] 数値型間でGenericに型変換したい

Last updated at Posted at 2019-09-07

数値型間でGenericに型変換したい

こういうことがやりたいんです。

static TResult Convert<T, TResult>(T number)
{
    return (TResult)number; // エラー: 型 'T' を 'TResult' に変換できません
}

static void Main(string[] args)
{
    double x1 = 137.036;
    int y1 = Convert<double, int>(x1);
    Console.WriteLine(y1);  // 137

    int x2 = 42; // 42
    long y2 = Convert<int, long>(x2);
    Console.WriteLine(y2);  // 42

    sbyte x3 = -1;
    byte y3 = Convert<sbyte, byte>(x3);
    Console.WriteLine(y3);  // 255
}

これがアップ/ダウンキャストのみならば次のようにできます。

static TResult ConvertViaObject<T, TResult>(T number)
{
    return (TResult)(object)number; // object型を経由する
}

static void Main(string[] args)
{
    int[] array = new int[] { 1, 1, 2, 3, 5 };
    IReadOnlyCollection<int> collection = ConvertViaObject<int[], IReadOnlyCollection<int>>(array);
    IList<int> list = ConvertViaObject<IReadOnlyCollection<int>, IList<int>>(array);
}

同じことをすればビルドは通ります。しかし実行時に落ちます。

double x1 = 137.036;
int y1 = ConvertViaObject<double, int>(x1);  // System.InvalidCastException

解決法1: System.Convert.ChangeType

System.Convert にはType型を引数に取るChangeTypeという関数があります。
ただし、これはcheckedで変換されるようです。それでもかまわない、その方が良いという場合はこれが使えます。
また、型変換をユーザー定義した場合は使えません。

static TResult ConvertViaChangeType<T, TResult>(T number)
{
    unchecked  // 無意味
    {
        return (TResult)Convert.ChangeType(number, typeof(TResult));
    }
}

static void Main(string[] args)
{
    double x1 = 137.036;
    int y1 = ConvertViaChangeType<double, int>(x1);
    Console.WriteLine(y1);  // 137

    int x2 = 42;
    long y2 = ConvertViaChangeType<int, long>(x2);
    Console.WriteLine(y2);  // 42

    sbyte x3 = -1;
    byte y3 = ConvertViaChangeType<sbyte, byte>(x3); // System.OverflowException
    Console.WriteLine(y3);
}

解決法2: 場合分け

ゴリ押し 呼び出し側で分けた方が良いんじゃないか
T4テンプレートを使えば、まあ全列挙も出来るでしょう。
ユーザー定義型変換は都度追加する必要があります。

static TResult ElefantConvert<T, TResult>(T number)
{
    if (typeof(T) == typeof(sbyte) && typeof(TResult) == typeof(sbyte))
    {
        return (TResult)(object)number;
    }
    if (typeof(T) == typeof(sbyte) && typeof(TResult) == typeof(byte))
    {
        return (TResult)(object)(byte)(sbyte)(object)number;
    }
    if (typeof(T) == typeof(sbyte) && typeof(TResult) == typeof(short))
    {
        return (TResult)(object)(short)(sbyte)(object)number;
    }
    /*
     * 
     */
    if (typeof(T) == typeof(byte) && typeof(TResult) == typeof(sbyte))
    {
        return (TResult)(object)(sbyte)(byte)(object)number;
    }
    /*
     * 
     */

    throw new NotSupportedException();
}

解決法3: 動的に

この記事の本題はこれです。
dynamic型を経由することで期待通りの動きになります。
ユーザー定義型変換も使えます。

static TResult ConvertViaDynamic<T, TResult>(T number)
{
    return (TResult)(dynamic)number;
}

static void Main(string[] args)
{
    double x1 = 137.036;
    int y1 = ConvertViaDynamic<double, int>(x1);
    Console.WriteLine(y1);  // 137

    int x2 = 42; // 42
    long y2 = ConvertViaDynamic<int, long>(x2);
    Console.WriteLine(y2);  // 42

    sbyte x3 = -1;
    byte y3 = ConvertViaDynamic<sbyte, byte>(x3);
    Console.WriteLine(y3);  // 255
}
5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3