数値型間で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
}