Reputation: 42340
I have been playing around with converting a string to a value type in .NET, where the resulting value type is unknown. The problem I have encountered in my code is that I need a method which accepts a string, and uses a "best fit" approach to populate the resulting value type. Should the mechanism not find a suitable match, the string is returned.
This is what I have come up with:
public static dynamic ConvertToType(string value)
{
Type[] types = new Type[]
{
typeof(System.SByte),
typeof(System.Byte),
typeof(System.Int16),
typeof(System.UInt16),
typeof(System.Int32),
typeof(System.UInt32),
typeof(System.Int64),
typeof(System.UInt64),
typeof(System.Single),
typeof(System.Double),
typeof(System.Decimal),
typeof(System.DateTime),
typeof(System.Guid)
};
foreach (Type type in types)
{
try
{
return Convert.ChangeType(value, type);
}
catch (Exception)
{
continue;
}
}
return value;
}
I feel that this approach is probably not best practice because it can only match against the predefined types.
Usually I have found that .NET accommodates this functionality in a better way than my implementation, so my question is: are there any better approaches to this problem and/or is this functionality implemented better in .NET?
EDIT: Note that the ordering of types in the array is so that the "best fit" occurs as accurately as possible for the given types.
EDIT: as per miniBill's request, this I how the method might be used (simple example!):
JsonDictionary["myKey"] = ConvertToType("255"); // 255 is a stringified json value, which should be assigned to myKey as a byte.
Upvotes: 1
Views: 842
Reputation: 50229
Your method isn't ideal as its going to cause a series of exceptions if value
is not a SByte
.
Seeing as all of these types share a common method .TryParse(string, out T)
we can use reflection extract the method and call it for each type. I made the method an extension method on string
and also factored out the Type[]
array into its own lazy loaded property for faster use.
public static class StringExtensions
{
public static dynamic ConvertToType(this string value)
{
foreach (Type type in ConvertibleTypes)
{
var obj = Activator.CreateInstance(type);
var methodParameterTypes = new Type[] { typeof(string), type.MakeByRefType() };
var method = type.GetMethod("TryParse", methodParameterTypes);
var methodParameters = new object[] { value, obj };
bool success = (bool)method.Invoke(null, methodParameters);
if (success)
{
return methodParameters[1];
}
}
return value;
}
private static Type[] _convertibleTypes = null;
private static Type[] ConvertibleTypes
{
get
{
if (_convertibleTypes == null)
{
_convertibleTypes = new Type[]
{
typeof(System.SByte),
typeof(System.Byte),
typeof(System.Int16),
typeof(System.UInt16),
typeof(System.Int32),
typeof(System.UInt32),
typeof(System.Int64),
typeof(System.UInt64),
typeof(System.Single),
typeof(System.Double),
typeof(System.Decimal),
typeof(System.DateTime),
typeof(System.Guid)
};
}
return _convertibleTypes;
}
}
}
Usage:
string value = "2391203921";
dynamic converted = value.ConvertToType();
Upvotes: 2
Reputation: 43616
You could use Reflection
to handle all the Parse
types by calling the TryParse
method, this will be a bit faster than handling multiple exceptions using ChangeType
public Type[] PredefinedTypes = new Type[]
{
typeof(System.SByte),
typeof(System.Byte),
typeof(System.Int16),
typeof(System.UInt16),
typeof(System.Int32),
typeof(System.UInt32),
typeof(System.Int64),
typeof(System.UInt64),
typeof(System.Single),
typeof(System.Double),
typeof(System.Decimal),
typeof(System.DateTime),
typeof(System.Guid)
};
public dynamic ConvertToType(string value)
{
foreach (var predefinedType in PredefinedTypes.Where(t => t.GetMethods().Any(m => m.Name.Equals("TryParse"))))
{
var typeInstance = Activator.CreateInstance(predefinedType);
var methodParamTypes = new Type[] { typeof(string), predefinedType.MakeByRefType() };
var methodArgs = new object[] { value, typeInstance };
if ((bool)predefinedType.GetMethod("TryParse", methodParamTypes).Invoke(predefinedType, methodArgs))
{
return methodArgs[1];
}
}
return value
}
Upvotes: 0
Reputation: 8751
This is something I wrote previously that might be a help:
public static Boolean CanCovertTo(this String value, Type type)
{
var targetType = type.IsNullableType() ? Nullable.GetUnderlyingType(type) : type;
TypeConverter converter = TypeDescriptor.GetConverter(targetType);
return converter.IsValid(value);
}
The basic idea is if you pass the string and a Type
that you want to test, you can check if a conversion will be valid before attempting to covert.
The problem with this design is that TypeConverter.IsValid()
is just a wrapper (with some exception handling) for TypeConverter.CanConvertFrom()
so you really aren't eliminating the exception handling, but since it is part of the BCL, I tend to think that is going to be a better implementation.
So you can implement this like so:
private static Type[] defaultTypes = new Type[]
{
typeof(System.SByte),
typeof(System.Byte),
typeof(System.Int16),
typeof(System.UInt16),
typeof(System.Int32),
typeof(System.UInt32),
typeof(System.Int64),
typeof(System.UInt64),
typeof(System.Single),
typeof(System.Double),
typeof(System.Decimal),
typeof(System.DateTime),
typeof(System.Guid)
};
public static dynamic ConvertToType(string value)
{
return ConvertToType(value, defaultTypes);
}
public static dynamic ConvertToType(string value, Type[] types)
{
foreach (Type type in types)
{
if (!value.CanConvertTo(type))
continue;
return Convert.ChangeType(value, type);
}
return value;
}
There is not really a great way to do this without the exception handling (even the exception handling in the TypeConverter.IsValid
method), so you have to live with it if you really need such a method. But you can limit the need for the exception handling if you implement some of the suggestions in miniBill's answer in addition to some improvements in the design.
Upvotes: 0
Reputation: 1733
Your approach would work but, as you say, it's not that elegant.
I think you have a couple of ways to improve this code:
TryParse
methods for cheaper testing (no catching involved) (e.g.: Int32.TryParse
)Guid.TryParse
)
TryParse
Upvotes: 0