ジェネリック型を使用し、struct
制約があればこんなコードですが、
void Func<T>(T? val) where T :struct
{
Console.WriteLine(val.Value.GetHashCode());
}
long? value = long.MaxValue;
Func(value);
これを制約なしでアクセスしたいわけです。
void Func<T>(T val)
{
Console.WriteLine(val.Value.GetHashCode());
^^^^^ //アクセスできない
}
long? value = long.MaxValue;
Func(value);
実行時にNullable.GetUnderlyingType(typeof(T));
でSystem.Type
を取得してリフレクションでも取得できますが、もう少し効率の良さそうな方法を取りたいので、Expression Treeを使います。
Expression Treeを使用したサンプルコード
using System;
using static System.Linq.Expressions.Expression;
static class InnerValueHashCode
{
public static int Get<T>(T value) => Impl<T>.Delegate(value);
private static class Impl<T>
{
public static Func<T, int> GenerateFunc()
{
var nullableValue = Parameter(typeof(T), "nullableValue");
var underlyingType = Nullable.GetUnderlyingType(typeof(T));
var callExpression = Call(
Property(nullableValue, typeof(T), "Value"),
underlyingType.GetMethod("GetHashCode", Type.EmptyTypes));
var lambda = Lambda<Func<T, int>>(callExpression, nullableValue);
return lambda.Compile();
}
internal readonly static Func<T, int> Delegate = GenerateFunc();
}
}
long? value = long.MaxValue;
InnerValueHashCode.Get(value);
もちろん Nullable<T>
以外を渡すと正しく動作しないので、事前にジェネリック型
のチェックは必要です。
また、HasValue == true
の場合、Nullable.ToString()
とNullable.GetHashCode()
はValue
の値を返すので、サンプルコードそのままでは実用性は皆無です。
更にいうと、dynamic
を使えば瞬殺です。
追記
もう少し実用的なサンプルコードを追記します。
ジェネリック型を受け取るFormatableToString
メソッドが、T.ToString(string)
もしくはT.Value.ToString(string)
を呼び出すようにします。
ToString(string)
が無ければTypeInitializationException
を投げます。
class Program
{
static void Main(string[] args)
{
FormatableToString(99999U, "X");
FormatableToString((uint?)99999U, "X");
FormatableToString(IntPtr.Zero, "X");
FormatableToString(UIntPtr.Zero, "X"); // throws System.TypeInitializationException
}
static void FormatableToString<T>(T value, string format)
{
string result;
if (typeof(T).IsGenericType && typeof(T).GetGenericTypeDefinition() == typeof(Nullable<>))
{
result = NullableHelper.ToString(value, format);
}
else
{
result = CallHelper.ToString(value, format);
}
Console.WriteLine(result);
}
}
NullableHelper
とCallHelper
の実装コードは長いので折りたたみusing System;
using System.Runtime.CompilerServices;
using static System.Linq.Expressions.Expression;
static class CallHelper
{
public static string ToString<T>(T value) => Impl<T>.ToStringDelegate(value);
public static string ToString<T>(T value, string format) => Impl<T>.ToStringWithFormatDelegate(value, format);
private static class Impl<T>
{
public static Func<T, TResult> MakeCallFunc<TResult>(string name) => MakeCallFunc<TResult>(name, Type.EmptyTypes);
public static Func<T, TResult> MakeCallFunc<TResult>(string name, params Type[] argTypes)
{
var value = Parameter(typeof(T), "value");
var targetMethod = typeof(T).GetMethod(name, argTypes);
if (targetMethod == null)
throw new MissingMethodException(typeof(T).FullName, name);
var lambda = Lambda<Func<T, TResult>>(
Call(value, targetMethod),
value);
return lambda.Compile(true);
}
public static Func<T, TArg, TResult> MakeCallFunc<TArg, TResult>(string name)
{
var value = Parameter(typeof(T), "value");
var arg1 = Parameter(typeof(TArg), "arg1");
var argTypes = new[] { typeof(TArg) };
var targetMethod = typeof(T).GetMethod(name, argTypes);
if (targetMethod == null)
throw new MissingMethodException(typeof(T).FullName, $"{name}({string.Join(",", argTypes.AsEnumerable())})");
var lambda = Lambda<Func<T, TArg, TResult>>(
Call(value, targetMethod, arg1),
value, arg1);
return lambda.Compile(true);
}
internal readonly static Func<T, string> ToStringDelegate = MakeCallFunc<string>("ToString");
// T must has ToString(format) method.
internal readonly static Func<T, string, string> ToStringWithFormatDelegate = MakeCallFunc<string, string>("ToString");
}
}
static class NullableHelper
{
public static string ToString<T>(T value) => Impl<T>.ToStringDelegate(value);
public static string ToString<T>(T value, string format) => Impl<T>.ToStringWithFormatDelegate(value, format);
private static class Impl<T>
{
static Impl()
{
System.Diagnostics.Debug.Assert(typeof(T).GetGenericTypeDefinition() == typeof(Nullable<>));
}
public static Func<T, TResult> MakeCallFunc<TResult>(string name) => MakeCallFunc<TResult>(name, Type.EmptyTypes);
public static Func<T, TResult> MakeCallFunc<TResult>(string name, params Type[] argTypes)
{
var nullableValue = Parameter(typeof(T), "nullableValue");
var underlyingType = Nullable.GetUnderlyingType(typeof(T));
var targetMethod = underlyingType.GetMethod(name, argTypes);
if (targetMethod == null)
throw new MissingMethodException(underlyingType.FullName, name);
var lambda = Lambda<Func<T, TResult>>(
Call(
Property(nullableValue, typeof(T), "Value"), targetMethod),
nullableValue);
return lambda.Compile(true);
}
public static Func<T, TArg, TResult> MakeCallFunc<TArg, TResult>(string name)
{
var nullableValue = Parameter(typeof(T), "nullableValue");
var arg1 = Parameter(typeof(TArg), "arg1");
var underlyingType = Nullable.GetUnderlyingType(typeof(T));
var argTypes = new[] { typeof(TArg) };
var targetMethod = underlyingType.GetMethod(name, argTypes);
if (targetMethod == null)
throw new MissingMethodException(underlyingType.FullName, $"{name}({string.Join(",", argTypes.AsEnumerable())})");
var lambda = Lambda<Func<T, TArg, TResult>>(
Call(
Property(nullableValue, typeof(T), "Value"), targetMethod, arg1),
nullableValue, arg1);
return lambda.Compile(true);
}
internal readonly static Func<T, string> ToStringDelegate = MakeCallFunc<string>("ToString");
// T must has ToString(format) method.
internal readonly static Func<T, string, string> ToStringWithFormatDelegate = MakeCallFunc<string, string>("ToString");
}
}