Convert.ChangeType () завершается ошибкой для типов Nullable - PullRequest
267 голосов
/ 20 августа 2010

Я хочу преобразовать строку в значение свойства объекта, имя которого у меня есть в виде строки. Я пытаюсь сделать это так:

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

Проблема заключается в том, что происходит сбой и генерируется исключение недопустимого приведения, когда тип свойства имеет тип NULL. Это не тот случай, когда значения невозможно преобразовать - они будут работать, если я сделаю это вручную (например, DateTime? d = Convert.ToDateTime(value);). Я видел несколько похожих вопросов, но все равно не могу заставить их работать.

Ответы [ 6 ]

371 голосов
/ 20 августа 2010

Не проверено, но, может быть, что-то подобное будет работать:

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);
}
70 голосов
/ 20 августа 2010

Вы должны получить базовый тип, чтобы сделать это ...

Попробуйте, я успешно использовал с дженериками:

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

Я использую его в ряде мест в своем коде, один из примеров - вспомогательный метод, который я использую для преобразования значений базы данных в безопасном виде:

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

Обычно у вас есть какая-то проверка ошибок или использование TryParse вместо Parse, но вы получите картинку.

8 голосов
/ 31 мая 2012

Это немного длинно для примера, но это относительно надежный подход, и отделяет задачу приведения от неизвестного значения к неизвестному типу

У меня есть метод 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;
}
6 голосов
/ 06 января 2015

У меня была похожая потребность, и ответ от ЛукиH указал мне направление.Я придумал эту универсальную функцию, чтобы упростить ее.

    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, потому что вы не можете использовать свойства.Как есть, он может работать со свойствами и переменными.Вы также можете создать перегрузку для передачи типа, если хотите.

0 голосов
/ 21 ноября 2018

Я сделал это таким образом

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;
    }
0 голосов
/ 21 июня 2013

Спасибо @ 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;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...