Problem
I’m trying to transform a string into an object property value whose name is also a string. This is how I’m attempting to do it:
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);
}
The issue is that when the property type is a nullable type, this fails and throws an Invalid Cast Exception. It’s not that the values can’t be converted; they will work if I do it manually (e.g. DateTime? d = Convert.ToDateTime(value);). I’ve seen several similar questions, but I’m still stumped.
Asked by iboeno
Solution #1
Untested, but something along these lines might work:
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);
}
Answered by LukeH
Solution #2
To do so, you must first obtain the underlying type…
Try this, it’s worked for me with generics:
//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);
I use it in a variety of places in my work; one example is an utility method I use to transform database variables typesafely:
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);
}
Called using:
string field1 = dr.GetValue<string>("field1");
int? field2 = dr.GetValue<int?>("field2");
DateTime field3 = dr.GetValue<DateTime>("field3");
At http://www.endswithsaurus.com/2010 07 01 archive.html, I created a series of blog postings, including this one. (Scroll down to the Addendum to see how @JohnMacintyre found the flaw in my initial code that took me down the same path you are going.) Since that post, I’ve made a few minor changes, including the conversion of enum types, so you may still use the same method call if your property is an Enum. Simply include a line to check for enum types, and you’re ready to go, using something like:
if (t.IsEnum)
return (T)Enum.Parse(t, value);
Normally, you’d check for errors or use TryParse instead of Parse, but you get the idea.
Answered by BenAlabaster
Solution #3
This is a bit of a long example, but it’s a really solid approach that splits the casting task from unknown value to unknown type.
I have a TryCast function that performs something similar while accounting for nullable types.
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;
}
}
Because TryCast is a Method with a Type Parameter, you must construct the MethodInfo yourself to call it dynamically:
var constructedMethod = typeof (ObjectExtensions)
.GetMethod("TryCast")
.MakeGenericMethod(property.PropertyType);
Then, to determine the actual property value, follow these steps:
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);
}
In addition, there are extension ways for dealing with 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;
}
Answered by bopapa_1979
Solution #4
I had a similar need, and LukeH’s response directed me in the right route. To make things easier, I created this generic function.
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); }
}
The following is an example of how to use it:
NotNullableDateProperty = CopyValue(NullableDateProperty, NotNullableDateProperty);
It’s worth noting that the second parameter is only a prototype for showing the function how to cast the return value; it’s not required to be the destination property. That is to say, you can do something like this:
DateTime? source = new DateTime(2015, 1, 1);
var dest = CopyValue(source, (string)null);
Because you can’t use out with properties, I did it this way instead of using an out. It can work with attributes and variables in its current state. If you wished, you could even write an overload to pass the type instead.
Answered by Steve In CO
Solution #5
Even for Nullable types, this works perfectly:
TypeConverter conv = TypeDescriptor.GetConverter(type);
return conv.ConvertFrom(value);
Before calling ConvertFrom, you should call conv.CanConvertFrom(type) for type safety (). If it returns false, you can use ChangeType or something else as a fallback.
Answered by Major
Post is based on https://stackoverflow.com/questions/3531318/convert-changetype-fails-on-nullable-types