Converting one type to another
All of the primitive types, such as Int32, Boolean, String etc. implement the IConvertible interface. This means we can easily change one type to another by using
float f = (float)Convert.ChangeType("100", typeof(float));
The thing to note regarding the IConvertible type is that it’s one way, i.e. from your type which implements the IConvertible to another type, but not back (this is where the class TypeConverter, which we’ll discuss next, comes into play).
So let’s look at a simple example which converts a Point to a string, and yes before I show the code for implementing IConvertible, we could have simply overridden the ToString method (which I shall also show in the sample code).
First off let’s create a couple of tests to prove our code works. The first takes a Point and using IConvertible, will generate a string representation of the type. As it uses ToString there’s no surprise that the second test which uses the ToString method will produce the same output.
[Fact] public void ChangeTypePointToString() { Point p = new Point { X = 100, Y = 200 }; string s = (string)Convert.ChangeType(p, typeof(string)); Assert.Equal("(100,200)", s); } [Fact] public void PointToString() { Point p = new Point { X = 100, Y = 200 }; Assert.Equal("(100,200)", p.ToString()); }
Now let’s look at our Point type, with an overridden ToString method
public struct Point : IConvertible { public int X { get; set; } public int Y { get; set; } public override string ToString() { return String.Format("({0},{1})", X, Y); } // ... IConvertible methods }
and now let’s look at a possible implementation of the IConvertible
TypeCode IConvertible.GetTypeCode() { throw new InvalidCastException("The method or operation is not implemented."); } bool IConvertible.ToBoolean(IFormatProvider provider) { throw new InvalidCastException("The method or operation is not implemented."); } byte IConvertible.ToByte(IFormatProvider provider) { throw new InvalidCastException("The method or operation is not implemented."); } char IConvertible.ToChar(IFormatProvider provider) { throw new InvalidCastException("The method or operation is not implemented."); } DateTime IConvertible.ToDateTime(IFormatProvider provider) { throw new InvalidCastException("The method or operation is not implemented."); } decimal IConvertible.ToDecimal(IFormatProvider provider) { throw new InvalidCastException("The method or operation is not implemented."); } double IConvertible.ToDouble(IFormatProvider provider) { throw new InvalidCastException("The method or operation is not implemented."); } short IConvertible.ToInt16(IFormatProvider provider) { throw new InvalidCastException("The method or operation is not implemented."); } int IConvertible.ToInt32(IFormatProvider provider) { throw new InvalidCastException("The method or operation is not implemented."); } long IConvertible.ToInt64(IFormatProvider provider) { throw new InvalidCastException("The method or operation is not implemented."); } sbyte IConvertible.ToSByte(IFormatProvider provider) { throw new InvalidCastException("The method or operation is not implemented."); } float IConvertible.ToSingle(IFormatProvider provider) { throw new InvalidCastException("The method or operation is not implemented."); } string IConvertible.ToString(IFormatProvider provider) { return ToString(); } object IConvertible.ToType(Type conversionType, IFormatProvider provider) { if(conversionType == typeof(string)) return ToString(); throw new InvalidCastException("The method or operation is not implemented."); } ushort IConvertible.ToUInt16(IFormatProvider provider) { throw new InvalidCastException("The method or operation is not implemented."); } uint IConvertible.ToUInt32(IFormatProvider provider) { throw new InvalidCastException("The method or operation is not implemented."); } ulong IConvertible.ToUInt64(IFormatProvider provider) { throw new InvalidCastException("The method or operation is not implemented."); }
TypeConverters
As mentioned previously, the IConvertible allows us to convert a type to one of the primitive types, but what if we want more complex capabilities, converting to and from various types. This is where the TypeConverter class comes in.
Here we develop our type as normal and then we adorn it with the TypeConverterAttribute at the struct/class level. The attribute takes a type derived from the TypeConverter class. This TypeConverter derived class does the actual type conversion to and from our adorned type.
Let’s again create a Point struct to demonstrate this on
[TypeConverter(typeof(PointTypeConverter))] public struct Point { public int X { get; set; } public int Y { get; set; } }
Note: We can also declare the TypeConverter type using a string in the standard Type, Assembly format, i.e. [TypeConverter(“MyTypeConverters.PointTypeConverter, MyTypeConverters)]
if we wanted to reference the type in an external assembly.
Before we create the TypeConverter code, let’s take a look at some tests which hopefully demonstrate how we use the TypeConverter and what we expect from our conversion code.
[Fact] public void CanConvertPointToString() { TypeConverter tc = TypeDescriptor.GetConverter(typeof(Point)); Assert.True(tc.CanConvertTo(typeof(string))); } [Fact] public void ConvertPointToString() { Point p = new Point { X = 100, Y = 200 }; TypeConverter tc = TypeDescriptor.GetConverter(typeof(Point)); Assert.Equal("(100,200)", tc.ConvertTo(p, typeof(string))); } [Fact] public void CanConvertStringToPoint() { TypeConverter tc = TypeDescriptor.GetConverter(typeof(Point)); Assert.True(tc.CanConvertFrom(typeof(string))); } [Fact] public void ConvertStringToPoint() { TypeConverter tc = TypeDescriptor.GetConverter(typeof(Point)); Point p = (Point)tc.ConvertFrom("(100,200)"); Assert.Equal(100, p.X); Assert.Equal(200, p.Y); }
So as you can see, to get the TypeConverter for our class we call the static method GetConverter on the TypeDescriptor class. This returns an instance of our TypeConverter (in this case our PointTypeConverter). From this we can check whether the type converter can convert to on from a type and then using the ConvertTo or ConvertFrom methods on the TypeConverter we can convert the type.
The tests above show that we expect to be able to convert a Point to a string where the string takes the format “(X,Y)”. So let’s look at an implementation for this
Note: note, this is an example of how we might implement this code and does not have full error handling, but hopefully gives a basic idea of what you might implement.
public class PointTypeConverter : TypeConverter { public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) { return (destinationType == typeof(string)) || base.CanConvertTo(context, destinationType); } public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) { if (destinationType == typeof(string)) { Point pt = (Point)value; return String.Format("({0},{1})", pt.X, pt.Y); } return base.ConvertTo(context, culture, value, destinationType); } public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { return (sourceType == typeof(string)) || base.CanConvertFrom(context, sourceType); } public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { string s = value as string; if (s != null) { s = s.Trim(); if(s.StartsWith("(") && s.EndsWith(")")) { s = s.Substring(1, s.Length - 2); string[] parts = s.Split(','); if (parts != null && parts.Length == 2) { Point pt = new Point(); pt.X = Convert.ToInt32(parts[0]); pt.Y = Convert.ToInt32(parts[1]); return pt; } } } return base.ConvertFrom(context, culture, value); } }