リフレクション付きの「キャスティング」


81

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

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を表すとは限らず、その値が常に10進数であるとは限らないことに注意してください。ただし、値をそのプロパティの正しい型にキャストできることは知っています。

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

回答:


134
void SetValue(PropertyInfo info, object instance, object value)
{
    info.SetValue(instance, Convert.ChangeType(value, info.PropertyType));
}

1
インターフェイスを実装しないConvert.ChangeType(value, property.PropertyType);場合でも失敗する可能性があることに注意してください。例えば、場合には、いくつかあるvalueIConvertibleinfo.PropertyTypeIEnumerable
derekantrican

42

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

メソッドは適切なIConvertibleメソッドへの呼び出しをラップするだけなので、変換を成功させるには、valueが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は指定されたタイプです。


2
これは実際に私が探しに来た答えです。非IConvertibleダイナミックキャスティング。
jnm2 2015年

1
ええ、私がOPだったら。
jnm2 2015年

1
IEnumerable<object>(これらのオブジェクトが文字列である)にキャストしようとしたときに、これが私を救うことを望んでいましたIEnumerable<string>。残念ながら、私のようなエラーを取得していますUnable to cast object of type 'System.Collections.Generic.IEnumerable'1[System.Object]' to type 'System.Collections.Generic.IEnumerable'1[System.String]'.
derekantrican

41

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<>));
  }

10

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

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

2

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

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

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

2
この問題はGuidに固有のものではなく、ADO.Netを介してデータベースからnull値をフェッチするときに取得するのではDBNull.Valueなく取得するという事実が原因ですnull。たとえば、null許容整数でも同じことがわかります。
jeroenh 2013

0

これは非常に古い質問ですが、ASP.NET CoreGoogle社員にチャイムを鳴らしたいと思いました。

ASP.NET Coreでは、.IsNullableType()(他の変更の中でも)保護されているため、コードは少し異なります。これは、ASP.NETCoreで機能するように変更された@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);
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.