weston
weston

Reputation: 54811

Convert from string to any basic type

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

Upvotes: 8

Views: 4102

Answers (6)

Kirill Bestemyanov
Kirill Bestemyanov

Reputation: 11964

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

Upvotes: 6

Karel Frajták
Karel Frajták

Reputation: 4489

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

Upvotes: 1

Servy
Servy

Reputation: 203812

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.

Upvotes: 2

qstebom
qstebom

Reputation: 739

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.

Upvotes: 0

Joan Caron
Joan Caron

Reputation: 1970

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

Upvotes: 3

hunter
hunter

Reputation: 63562

try this:

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

Upvotes: 2

Related Questions