String to Object Parsing
.NET do it for me
In the .NET world there is a lot of confusion around parsing a string to a typed object such as an int, decimal, datetime etc. Its not that the libaries don’t exist, its that there are several of them, and they all seem to stop just short of providing an adequate solution.
Int32.Parse(string s)
,DateTime.Parse(string s)
, etc. These work fine, however you have to know at the time you are writing the code what object you need back, there is no way to parse an object to type ofType
.Convert.ChangeType(object val, Type t)
This fixes the parsing to a givenType
issue but it does not work for some of the less common case (such as GUIDs). It is also limited in its ability to add custom conversions.TypeDescriptor.GetConverter(type).ConvertTo(Type t, object val)
This is the closest to what we are after, it is extensiable, handles the common cases and works with converting to a givenType
. However it does have some drawbacks: it does not work with enums; failed conversions thow an exception that you need to catch to handle uncovertable cases; and its a bit long to write out. But we can fix all those issues with a couple of handy extension methods.
A couple of handy extension methods
Here are two extension methods, one for when we know what we want at compile time Convert<T>
, and one if we have a Type
that we want ot convert it to TryConvert(Type t)
.
A great deal of time was spent on contemplating how invalid conversion should be handled, there are several ways to do it and I wanted to hit the nice balance in enforcing safe code and being simple to use. The TryConvert
method follows the TryX pattern by returning whether its successful and having the actual result as an out parameter, however the Convert<T>
does something different and will return null if failed. The reason the Convert<T>
method returns null as it allows for cleaner use in code, for example I can easily default the value of a failed conversion by using the ?? operator.
myPotentiallyInvalidIntString.Convert<int?>() ?? 100;
There is also a runtime check to make sure only using nullable structs are used. "3".Convert<int>()
will throw an argument exception, using "3".Convert<int?>()
is the expected input. This enforces the fact that conversions can fail and null is always a possible return value. Nullable types will also protect you from making accidents in your code and force you to consider the case when the string is not convertable.
The restrictions to nullable types don’t apply to the TryConvert
method, the reason we are passing a Type
object is we have no idea what type we want, so it seems unfair to throw argument exceptions. For this case the TryX method works much better and the invalid conversion handling can be done by using the returned boolean value.
Custom converters
.NET has a bunch of type converters built in and cover most of the basics
- int
- float
- double
- decimal
- long
- bool
- guid
- (possibly others)
However there will always be custom classes you want to be able to convert and even some in built classes that you wish you could convert. Luckily there is a way to add our own converters by creating a class that extends TypeConverter, then registering it agaisnt the class with an attribute. Below is a an example of a converter that will convert an XDocument to and from a string.
Although I have been discussing converting from a string, the converter framework is far more general and allows converting to and from lots of different types. The four methods that need overriding are
bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
to determine if a conversion from the given type is possible.object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
to perform the conversion from the given value and type. Culture information is also available to allow for locale specific transformations.bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
to determine if a conversion is possible to a given type.object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
to perform the conversion to the given type.
We still need to register the converter with the framework so that it knows that you custom converter exists. Normally this is done with a TypeConverterAttribute
on the actual class the converter is for, for the XDocument converter this would need to go on XDocument
Unfortunatly for classes in the .NET framework, like XDocument, we cant just put an attribute on it, we can however add attributes at run time using.
Which is exactly what our type converter register method does, but because its at runtime we have to actaully run that piece of code, this means at the start of your application you need to run XDocumentTypeConverter.Register()
for the converter to work.
Give it to me in bullets
So in summary of all the above
- .NETs handling of conversions are confusing, but TypeConverters are the best way to go.
- Presented is a useful extension method for simplyfing the complexities and shortcomings out of using TypeConverters.
- Can create custom converters by extending TypeConverter.
- Need to register custom converters, if you own the source code for the class you can add an attribute to the class you are converting to/from, if you dont own the source code you need to add the attribute at runtime using ` TypeDescriptor.AddAttributes(Type t, params Attribute[] attrs)`.