[C#] Nullable 형식에서 Convert.ChangeType ()이 실패합니다.

문자열을 이름이 문자열 인 객체 속성 값으로 변환하고 싶습니다. 나는 이것을 이렇게하려고 노력하고있다 :

string modelProperty = "Some Property Name";
string value = "SomeValue";
var property = entity.GetType().GetProperty(modelProperty);
if (property != null) {
    property.SetValue(entity, 
        Convert.ChangeType(value, property.PropertyType), null);
}

문제는 속성 유형이 nullable 유형 인 경우 실패하고 잘못된 캐스트 예외가 발생한다는 것입니다. 이것은 값을 변환 할 수없는 경우가 아닙니다. 수동으로 수동으로 수행하면 작동합니다 (예 : DateTime? d = Convert.ToDateTime(value);비슷한 질문이 있지만 여전히 작동하지 않습니다.



답변

테스트되지 않았지만 다음과 같이 작동합니다.

string modelProperty = "Some Property Name";
string value = "Some Value";

var property = entity.GetType().GetProperty(modelProperty);
if (property != null)
{
    Type t = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType;

    object safeValue = (value == null) ? null : Convert.ChangeType(value, t);

    property.SetValue(entity, safeValue, null);
}


답변

그렇게하려면 기본 유형을 가져와야합니다 …

이것을 시도해보십시오. 제네릭과 함께 성공적으로 사용했습니다.

//Coalesce to get actual property type...
Type t = property.PropertyType();
t = Nullable.GetUnderlyingType(t) ?? t;

//Coalesce to set the safe value using default(t) or the safe type.
safeValue = value == null ? default(t) : Convert.ChangeType(value, t);

코드의 여러 곳에서 사용합니다. 하나의 예는 typesafe 방식으로 데이터베이스 값을 변환하는 데 사용하는 도우미 메서드입니다.

public static T GetValue<T>(this IDataReader dr, string fieldName)
{
    object value = dr[fieldName];

    Type t = typeof(T);
    t = Nullable.GetUnderlyingType(t) ?? t;

    return (value == null || DBNull.Value.Equals(value)) ? 
        default(T) : (T)Convert.ChangeType(value, t);
}

다음을 사용하여 호출 :

string field1 = dr.GetValue<string>("field1");
int? field2 = dr.GetValue<int?>("field2");
DateTime field3 = dr.GetValue<DateTime>("field3");

나는에서이 포함 블로그 게시물의 시리즈를 쓴 http://www.endswithsaurus.com/2010_07_01_archive.html을 부칙에, 아래로 스크롤 ( @JohnMacintyre은 실제로 당신이있어 동일한 경로 아래로 나를 이끌어 내 원래의 코드에서 버그를 발견 지금). enum 유형의 변환을 포함하는 게시물 이후 몇 가지 작은 수정 사항이 있으므로 속성이 Enum 인 경우 여전히 동일한 메소드 호출을 사용할 수 있습니다. 열거 형 유형을 확인하기 위해 줄을 추가하면 다음과 같은 방식으로 경주를 떠납니다.

if (t.IsEnum)
    return (T)Enum.Parse(t, value);

일반적으로 오류 검사가 있거나 Parse 대신 TryParse를 사용하지만 그림이 나타납니다.


답변

이것은 예를 들어 조금 길지만 비교적 견고한 접근 방식이며 알 수없는 값에서 알 수없는 유형으로 캐스팅하는 작업을 분리합니다.

비슷한 것을 수행하고 nullable 형식을 고려하는 TryCast 메서드가 있습니다.

public static bool TryCast<T>(this object value, out T result)
{
    var type = typeof (T);

    // If the type is nullable and the result should be null, set a null value.
    if (type.IsNullable() && (value == null || value == DBNull.Value))
    {
        result = default(T);
        return true;
    }

    // Convert.ChangeType fails on Nullable<T> types.  We want to try to cast to the underlying type anyway.
    var underlyingType = Nullable.GetUnderlyingType(type) ?? type;

    try
    {
        // Just one edge case you might want to handle.
        if (underlyingType == typeof(Guid))
        {
            if (value is string)
            {
                value = new Guid(value as string);
            }
            if (value is byte[])
            {
                value = new Guid(value as byte[]);
            }

            result = (T)Convert.ChangeType(value, underlyingType);
            return true;
        }

        result = (T)Convert.ChangeType(value, underlyingType);
        return true;
    }
    catch (Exception ex)
    {
        result = default(T);
        return false;
    }
}

물론 TryCast는 유형 매개 변수가있는 메서드이므로 동적으로 호출하려면 MethodInfo를 직접 구성해야합니다.

var constructedMethod = typeof (ObjectExtensions)
    .GetMethod("TryCast")
    .MakeGenericMethod(property.PropertyType);

그런 다음 실제 속성 값을 설정하십시오.

public static void SetCastedValue<T>(this PropertyInfo property, T instance, object value)
{
    if (property.DeclaringType != typeof(T))
    {
        throw new ArgumentException("property's declaring type must be equal to typeof(T).");
    }

    var constructedMethod = typeof (ObjectExtensions)
        .GetMethod("TryCast")
        .MakeGenericMethod(property.PropertyType);

    object valueToSet = null;
    var parameters = new[] {value, null};
    var tryCastSucceeded = Convert.ToBoolean(constructedMethod.Invoke(null, parameters));
    if (tryCastSucceeded)
    {
        valueToSet = parameters[1];
    }

    if (!property.CanAssignValue(valueToSet))
    {
        return;
    }
    property.SetValue(instance, valueToSet, null);
}

그리고 property.CanAssignValue를 다루는 확장 메소드 …

public static bool CanAssignValue(this PropertyInfo p, object value)
{
    return value == null ? p.IsNullable() : p.PropertyType.IsInstanceOfType(value);
}

public static bool IsNullable(this PropertyInfo p)
{
    return p.PropertyType.IsNullable();
}

public static bool IsNullable(this Type t)
{
    return !t.IsValueType || Nullable.GetUnderlyingType(t) != null;
}


답변

나는 비슷한 필요를 가지고 있었고, LukeH의 대답이 나를 지시했다. 이 일반 기능을 쉽게 만들었습니다.

    public static Tout CopyValue<Tin, Tout>(Tin from, Tout toPrototype)
    {
        Type underlyingT = Nullable.GetUnderlyingType(typeof(Tout));
        if (underlyingT == null)
        { return (Tout)Convert.ChangeType(from, typeof(Tout)); }
        else
        { return (Tout)Convert.ChangeType(from, underlyingT); }
    }

사용법은 다음과 같습니다.

        NotNullableDateProperty = CopyValue(NullableDateProperty, NotNullableDateProperty);

두 번째 매개 변수는 함수가 리턴 값을 캐스트하는 방법을 표시하기 위해 프로토 타입으로 사용되므로 실제로 대상 특성 일 필요는 없습니다. 다음과 같이 할 수도 있습니다.

        DateTime? source = new DateTime(2015, 1, 1);
        var dest = CopyValue(source, (string)null);

속성과 함께 사용할 수 없기 때문에 out을 사용하는 대신이 방법을 사용했습니다. 있는 그대로 속성 및 변수를 사용할 수 있습니다. 원하는 경우 유형을 전달하기 위해 과부하를 만들 수도 있습니다.


답변

감사합니다 @LukeH
조금 변경했습니다.

public static object convertToPropType(PropertyInfo property, object value)
{
    object cstVal = null;
    if (property != null)
    {
        Type propType = Nullable.GetUnderlyingType(property.PropertyType);
        bool isNullable = (propType != null);
        if (!isNullable) { propType = property.PropertyType; }
        bool canAttrib = (value != null || isNullable);
        if (!canAttrib) { throw new Exception("Cant attrib null on non nullable. "); }
        cstVal = (value == null || Convert.IsDBNull(value)) ? null : Convert.ChangeType(value, propType);
    }
    return cstVal;
}


답변

이런 식으로 했어

public static List<T> Convert<T>(this ExcelWorksheet worksheet) where T : new()
    {
        var result = new List<T>();
        int colCount = worksheet.Dimension.End.Column;  //get Column Count
        int rowCount = worksheet.Dimension.End.Row;

        for (int row = 2; row <= rowCount; row++)
        {
            var obj = new T();
            for (int col = 1; col <= colCount; col++)
            {

                var value = worksheet.Cells[row, col].Value?.ToString();
                PropertyInfo propertyInfo = obj.GetType().GetProperty(worksheet.Cells[1, col].Text);
                propertyInfo.SetValue(obj, Convert.ChangeType(value, Nullable.GetUnderlyingType(propertyInfo.PropertyType) ?? propertyInfo.PropertyType), null);

            }
            result.Add(obj);
        }

        return result;
    }


답변