Silverlight IConvertible TypeConverter
- by codingbloke
I recently answered the following question on stackoverflow: Silverlight 3 custom control: only ‘int’ as numeric type for a property? [e.g. long or int64 seems to break] I quickly knocked up the class ConvertibleTypeConverter<T> that I posted in the question (listed later here as well). Afterward I fully expected to find that of the usual clever “bods who blog” to have covered this probably with a better solution than I. So far though I’ve not found one so I thought I’d blog it myself. The Problem Here is a classic gotcha I’ve seen asked more than once on stackoverflow :- public class MyClass { public float SomeValue { get; set; } } <local:MyClass SomeValue="45.15" /> This fails with the error “Failed to create a 'System.Single' from the text '45.15'” and results in much premature hair loss. Fortunately this is SL4, in SL3 the error message is almost meaningless. So what gives, how can it be that this fails when we can see other very similar values parsing happily all over the place? It comes down the fact that the Xaml parser only handles a few of the primitive data types namely: bool, int, string and double. Since the parser has no idea how to convert a string to a float we get the above error. The Solution The sensible solution is “use double not float” but lets not dwell on that, there has to be occasions where such an answer isn’t acceptable. In order to achieve parsing of other types we need an implementation of TypeConverter for the type of the property and then we need to use the TypeConverterAttribute to decorate the property . As an example the Silverlight SDK provides one for DateTime the DateTimeTypeConverter (yes I know DateTime isn’t really a primitive). The following class will parse in Xaml:- public class MyClass { [TypeConverter(typeof(DateTimeTypeConverter))] public DateTime SomeValue {get; set; } } So far though we would need to create a TypeConverter for each primitive type we are using, what if I had the following mad class to support in Xaml:- public class StrangePrimitives { public Boolean BooleanProp { get; set; } public Byte ByteProp { get; set; } public Char CharProp { get; set; } public DateTime DateTimeProp { get; set; } public Decimal DecimalProp { get; set; } public Double DoubleProp { get; set; } public Int16 Int16Prop { get; set; } public Int32 Int32Prop { get; set; } public Int64 Int64Prop { get; set; } public SByte SByteProp { get; set; } public Single SingleProp { get; set; } public String StringProp { get; set; } public UInt16 UInt16Prop { get; set; } public UInt32 UInt32Prop { get; set; } public UInt64 UInt64Prop { get; set; } } Then I want to fill an instance of StrangePrimitives with the following Xaml which of course fails. <local:StrangePrimitives x:Key="MyStrangePrimitives" BooleanProp="True" ByteProp="156" CharProp="A" DateTimeProp="06 Jun 2010" DecimalProp="123.56" DoubleProp="8372.937803" Int16Prop="16532" Int32Prop="73738248" Int64Prop="12345678909298" SByteProp="-123" SingleProp="39.0" StringProp="Hello, World!" UInt16Prop="40000" UInt32Prop="4294967295" UInt64Prop="18446744073709551615" /> I got to thinking, though, one thing all these primitive types have in common is that they all implement IConvertible so it should be possible to write just one converter to handle them all. Here it is:- The ConvertibleTypeConverter public class ConvertibleTypeConverter<T> : TypeConverter where T : IConvertible { public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { return sourceType.GetInterface("IConvertible", false) != null; } public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) { return destinationType.GetInterface("IConvertible", false) != null; } public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) { return ((IConvertible)value).ToType(typeof(T), culture); } public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType) { return ((IConvertible)value).ToType(destinationType, culture); } } I won’t bore you with an explanation of how it works, it simply adapts one existing interface (the IConvertible) and exposes it as another (the TypeConverter). With that in place the previous strange primitives class can be modified as:- public class StrangePrimitives { public Boolean BooleanProp { get; set; } [TypeConverter(typeof(ConvertibleTypeConverter<Byte>))] public Byte ByteProp { get; set; } [TypeConverter(typeof(ConvertibleTypeConverter<Char>))] public Char CharProp { get; set; } [TypeConverter(typeof(ConvertibleTypeConverter<DateTime>))] public DateTime DateTimeProp { get; set; } [TypeConverter(typeof(ConvertibleTypeConverter<Decimal>))] public Decimal DecimalProp { get; set; } public Double DoubleProp {get; set; } [TypeConverter(typeof(ConvertibleTypeConverter<Int16>))] public Int16 Int16Prop { get; set; } public Int32 Int32Prop { get; set; } [TypeConverter(typeof(ConvertibleTypeConverter<Int64>))] public Int64 Int64Prop { get; set; } [TypeConverter(typeof(ConvertibleTypeConverter<SByte>))] public SByte SByteProp { get; set; } [TypeConverter(typeof(ConvertibleTypeConverter<Single>))] public Single SingleProp { get; set; } public String StringProp { get; set; } [TypeConverter(typeof(ConvertibleTypeConverter<UInt16>))] public UInt16 UInt16Prop { get; set; } [TypeConverter(typeof(ConvertibleTypeConverter<UInt32>))] public UInt32 UInt32Prop { get; set; } [TypeConverter(typeof(ConvertibleTypeConverter<UInt64>))] public UInt64 UInt64Prop { get; set; } } This results in the previous Xaml parsing happily. Now it seems such an obvious thing to do that one may wonder why such a class doesn’t already existing in Silverlight or at least in the SDK. I would not be surprised if there were some very good reasons hence use the ConvertibleTypeConverter with caution. It does seem to me to be a useful little class to have lying around in the toolbox for the odd occasion where it may be needed.