8
votes

I have a string and a Type, and I want to return the string value converted to that Type.

public static object StringToType(string value, Type propertyType)
{
    return Convert.ChangeType(value, propertyType, CultureInfo.InvariantCulture);
}

This returns an object that I can use in a property set value call:

public static void SetBasicPropertyValueFromString(object target,
                                                   string propName,
                                                   string value)   
{
  PropertyInfo prop = target.GetType().GetProperty(propName);
  object converted = StringToType(value, prop.PropertyType);
  prop.SetValue(target, converted, null);
}

This works for most basic types, except nullables.

[TestMethod]
public void IntTest()
{ //working
    Assert.AreEqual(1, ValueHelper.StringToType("1", typeof (int)));
    Assert.AreEqual(123, ValueHelper.StringToType("123", typeof (int)));
}

[TestMethod]
public void NullableIntTest()
{ //not working
    Assert.AreEqual(1, ValueHelper.StringToType("1", typeof (int?)));
    Assert.AreEqual(123, ValueHelper.StringToType("123", typeof (int?)));
    Assert.AreEqual(null, ValueHelper.StringToType(null, typeof (int?)));
}

NullableIntTest fails on first line with:

System.InvalidCastException: Invalid cast from 'System.String' to 'System.Nullable`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]'.

I'm having difficulty detemining if the type is nullable and changing the behaiour of the StringToType method.

Behaviour I am after:

If string is null or empty, return null, else convert as per the non-nullable type.

Result

Like Kirill's answer, only with one ChangeType call.

public static object StringToType(string value, Type propertyType)
{
    var underlyingType = Nullable.GetUnderlyingType(propertyType);
    if (underlyingType != null)
    {
        //an underlying nullable type, so the type is nullable
        //apply logic for null or empty test
        if (String.IsNullOrEmpty(value)) return null;
    }
    return Convert.ChangeType(value,
                              underlyingType ?? propertyType,
                              CultureInfo.InvariantCulture);
}
6
You can make your method generic: public static object StringToType<T>(string value) where T : struct ... - Karel Frajták
@KarelFrajtak Na, because of the way it's called, see second code fragment. prop.PropertyType is from reflection, so is not known until runtime. - weston
Your if check isn't quite right. Your special cases shouldn't be for any generic type, it should be just for a Nullable<T> because they are boxed differently. - Servy
Ok, but it can be an option too;) - Karel Frajták
@Servy Yes I have updated. Now based on Kirill's answer. - weston

6 Answers

6
votes

You cannot use Convert.ChangeType on nullable types cause it is not inherited from IConvertible. You should rewrite your method.

public static object StringToType(string value, Type propertyType)
{
   var underlyingType = Nullable.GetUnderlyingType(propertyType);
   if(underlyingType == null)
          return Convert.ChangeType(value, propertyType,  CultureInfo.InvariantCulture);
   return String.IsNullOrEmpty(value)
          ? null
          : Convert.ChangeType(value, underlyingType, CultureInfo.InvariantCulture);
}
3
votes
public static object StringToType(string value, Type propertyType)
{
   var underlyingType = Nullable.GetUnderlyingType(propertyType);
   return underlyingType == null ? Convert.ChangeType(value, propertyType, CultureInfo.InvariantCulture) : Convert.ChangeType(value, underlyingType, CultureInfo.InvariantCulture);
}
2
votes

try this:

prop.IsGenericType && Nullable.GetUnderlyingType(prop) == value.GetType()
2
votes

The problem here is that the return value of ChangeType, as well as your method, is object. When you box any nullable type into an object it doesn't box the nullable type. If the value, at runtime, is actually null it boxes a null, and if it has a value it boxes the actual underlying value (rather than the nullable version).

int? i = 5;
object o = i;
Type t = o.GetType();//will be `int`, not `Nullable<int>`

This won't happen in the general case with any other type; Nullable<T> has special compiler support to do this. You'll basically need to special case Nullable in your code as well; If your method is passed a Nullable type you'll need to first check the object for null, and if not null, use the underlying type of the Nullable instead.

1
votes

The code with help of Kirill Bestemyanov' snippet:

public static object StringToType<T>(string value)
{
   return StringToType(value, typeof(T));
}

public static object StringToType(string value, Type propertyType)
{
   var underlyingType = Nullable.GetUnderlyingType(propertyType);
   if(underlyingType == null)
     return Convert.ChangeType(value, propertyType, CultureInfo.InvariantCulture);
   return String.IsNullOrEmpty(value)
     ? null
     : Convert.ChangeType(value, underlyingType, CultureInfo.InvariantCulture);
}

And usage (WL just writes to console). You were right, the generic method can't use int? as generic parameter.

WL(StringToType("1", typeof (int?))); // -> 1
WL(StringToType<int>("1"));           // -> 1
WL(StringToType<int?>("1"));          // error, not compilable
WL(StringToType<Nullable<int>>("1")); // -> 1
0
votes

You must use an appropriate cast method for each type you want to support.

int parsedInt;
int.TryParse("1", out parsedInt);

double parsedDouble;
double.TryParse("0.0d", out parsedDouble);

It is impossible for the compiler to figure out the type depending on the contents of the string. See the following links for more information about converting from string to scalars types: http://msdn.microsoft.com/en-us/library/bb397679.aspx and http://msdn.microsoft.com/en-us/library/bb384043.aspx.