2010-08-20 4 views
227

Я хочу преобразовать строку в значение свойства объекта, имя которой у меня есть как строка. Я пытаюсь сделать это так:Convert.ChangeType() fail on Nullable Types

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

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

+0

@dtb ChangeType – iboeno

+1

Я использую ExecuteScalar с PetaPoco 4.0.3 и он не по той же причине: возвращение (T) Convert.ChangeType (вал, TypeOf (T)) в строке 554 – Larry

ответ

315

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

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

Мне просто нужен этот кусок кода. Спасибо за Nullable.GetUnderlyingType! Помог мне много, когда я построил модельный блог бедняка для проекта, который в этом нуждался. Я должен тебе пиво! –

+3

Возможно, вместо '(значение == null)? null' use '(значение == null)? по умолчанию (т) '? – threadster

55

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

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

//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 (Прокрутите вниз до Addendum, @JohnMacintyre на самом деле заметила ошибку в моем исходном коде, которая привела меня по тому же пути, на котором вы сейчас находитесь). У меня есть несколько небольших модификаций с этой должности, которая включает в себя преобразование типов перечислений, поэтому, если ваше свойство Enum, вы все равно можете использовать один и тот же вызов метода. Просто добавьте строку для проверки типов перечислений и вы до гонок, используя что-то вроде:

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

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

+0

Спасибо - Я все еще отсутствует шаг или что-то не понимает. Я пытаюсь установить значение свойства, почему я получаю объект, который он находится в базовом типе? Я также не уверен, как добраться от моего кода до метода расширения, такого как ваш. Я не буду знать, какой тип будет для того, чтобы сделать что-то вроде value.Helper (). – iboeno

+0

@iboeno - Извините, не было на встрече, поэтому я не мог помочь вам соединить точки. Рад, что у вас есть решение. – BenAlabaster

8

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

У меня есть метод 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); 
} 

И методы расширения для решения проблемы собственности.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; 
} 
0

Благодаря @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; 
} 
6

У меня была подобная необходимость, и ответ от 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); 

Я сделал это таким образом, вместо того, чтобы использовать, потому что вы не можете использовать с свойствами. Как есть, он может работать со свойствами и переменными. Вы также можете создать перегрузку, чтобы передать тип, если хотите.