반사와 함께 '주조'


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 인스턴스가 나타내는 유형으로 어떻게 변환 할 수 있습니까?

답변:


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

1
참고 Convert.ChangeType(value, property.PropertyType);경우 실패 할 수 있습니다 value구현하지 않는 IConvertible인터페이스를. 예를 들어 if info.PropertyTypeis someIEnumerable
derekantrican

42

Thomas 대답은 IConvertible 인터페이스를 구현하는 유형에서만 작동합니다.

변환이 성공하려면 값이 IConvertible 인터페이스를 구현해야합니다. 메서드가 적절한 IConvertible 메서드에 대한 호출을 단순히 래핑하기 때문입니다. 이 메서드를 사용하려면 값을 conversionType으로 변환 할 수 있어야합니다.

이 코드는 unboxing (필요한 경우) 및 변환을 수행하는 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
이것은 실제로 내가 찾던 답입니다. 비가역 동적 주조.
jnm2

1
허-내가 OP라면.
jnm2

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이 nullable 형식으로의 변환을 처리하지 않는다는 내 결과를 추가 할 것이라고 생각했습니다. nullable 형식을 처리하기 위해 다음 코드를 사용했습니다.

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

Type이 Nullable Guid이면 위에 제안 된 솔루션 중 어느 것도 작동하지 않습니다. ' System.DBNull' 에서 ' '로의 잘못된 캐스트 System.Guid예외가 발생했습니다.Convert.ChangeType

변경 사항을 다음으로 수정하려면 :

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

2
이 문제는 Guid에만 국한된 것이 아니라 DBNull.Value단순히 nullADO.Net을 통해 데이터베이스에서 null 값을 가져올 때 얻는 것이 아니라는 사실 때문입니다 . 예를 들어 nullable int에서도 동일하게 표시됩니다.
jeroenh

0

이것은 매우 오래된 질문이지만 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);
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.