web-dev-qa-db-ja.com

反射を伴う「キャスティング」

次のサンプルコードを検討してください。

class SampleClass
{
    public long SomeProperty { get; set; }
}

public void SetValue(SampleClass instance, decimal value)
{
    // value is of type decimal, but is in reality a natural number => cast
    instance.SomeProperty = (long)value;
}

今、私はリフレクションを通して同様のことをする必要があります:

void SetValue(PropertyInfo info, object instance, object value)
{
    // throws System.ArgumentException: Decimal can not be converted to Int64
    info.SetValue(instance, value)  
}

PropertyInfoが常にlongを表すと仮定することはできません。どちらの値も常に小数ではありません。ただし、そのプロパティの正しい型に値をキャストできることは知っています。

リフレクションを介して、「値」パラメーターをPropertyInfoインスタンスで表されるタイプに変換するにはどうすればよいですか?

75
jeroenh
void SetValue(PropertyInfo info, object instance, object value)
{
    info.SetValue(instance, Convert.ChangeType(value, info.PropertyType));
}
126
Thomas Levesque

Thomasの答えは正しいのですが、Convert.ChangeTypeはnull許容型への変換を処理しないという発見を追加すると思いました。 null許容型を処理するために、次のコードを使用しました。

void SetValue(PropertyInfo info, object instance, object value)
{
    var targetType = info.PropertyType.IsNullableType() 
         ? Nullable.GetUnderlyingType(info.PropertyType) 
         : info.PropertyType; 
    var convertedValue = Convert.ChangeType(value, targetType);

    info.SetValue(instance, convertedValue, null);
}

このコードは、次の拡張メソッドを使用します。

public static class TypeExtensions
{
  public static bool IsNullableType(this Type type)
  {
    return type.IsGenericType 
    && type.GetGenericTypeDefinition().Equals(typeof(Nullable<>));
  }
39
jeroenh

トーマスアンサーは、IConvertibleインターフェイスを実装するタイプでのみ機能します。

変換を成功させるには、valueがIConvertibleインターフェイスを実装する必要があります。これは、メソッドが呼び出しを適切なIConvertibleメソッドに単純にラップするためです。このメソッドでは、値のconversionTypeへの変換がサポートされている必要があります。

このコードは、ボックス化解除(必要な場合)と変換を行うlinq式をコンパイルします。

    public static object Cast(this Type Type, object data)
    {
        var DataParam = Expression.Parameter(typeof(object), "data");
        var Body = Expression.Block(Expression.Convert(Expression.Convert(DataParam, data.GetType()), Type));

        var Run = Expression.Lambda(Body, DataParam).Compile();
        var ret = Run.DynamicInvoke(data);
        return ret;
    }

結果のラムダ式は(TOut)(TIn)Dataに等しくなります。ここで、TInは元のデータの型で、TOutは指定された型です

37
rafael

Jeroenhの答えに貢献すると、Convert.ChangeTypeがnull値でクラッシュするため、変換された値を取得するための行は次のようになります。

var convertedValue = value == null ? null : Convert.ChangeType(value, targetType);
10
Ignacio Calvo

TypeがNullable Guidである場合、上記の提案されたソリューションはいずれも機能しません。 'System.DBNull'から 'System.Guid'への無効なキャストがConvert.ChangeTypeでスローされます

その変更を修正するには:

var convertedValue = value == System.DBNull.Value ? null : Convert.ChangeType(value, targetType);
2
Loukas

これは非常に古い質問ですが、ASP.NET CoreのGoogle社員を歓迎するつもりでした。

ASP.NET Coreでは、.IsNullableType()は(他の変更の中でも)保護されているため、コードは少し異なります。 ASP.NET Coreで動作するように修正された@jeroenhの回答を次に示します。

void SetValue(PropertyInfo info, object instance, object value)
{
    Type proptype = info.PropertyType;
    if (proptype.IsGenericType && proptype.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
    {
        proptype = new NullableConverter(info.PropertyType).UnderlyingType;
    }

    var convertedValue = Convert.ChangeType(value, proptype);
    info.SetValue(instance, convertedValue);
}
0
chakeda