SYSK 113: System.Convert.ChangeType Undercover

Are you curious about how ChangeType is implemented, and whether it is different from a cast?  Then read on…

 

First, what is a cast?

 

C# allows classes and structures to create operators that convert the underlying data type to another type.  Conversions can be implicit or explicit, and this determines whether an explicit cast is required.

Here is the way you would implement such conversion:

 

public static implicit operator conv-type-out (conv-type-in operand)

public static explicit operator conv-type-out (conv-type-in operand)

 

E.g. a custom class Fraction could have an implicit operator that converts the data type to double:

public static implicit operator double(Fraction f)

{

      return (double)f.num / f.den;

}

By doing so, a variable of type Fraction and be treated (i.e. used) as if it were a double.

 

For example, .NET system type Decimal implements the following conversion operators:

public static implicit operator Decimal(byte value)

public static implicit operator Decimal(sbyte value)

public static implicit operator Decimal(short value)

public static implicit operator Decimal(ushort value)

public static implicit operator Decimal(char value)

public static implicit operator Decimal(int value)

public static implicit operator Decimal(uint value)

public static implicit operator Decimal(long value)

public static implicit operator Decimal(ulong value)

public static explicit operator Decimal(float value)

public static explicit operator Decimal(double value)

public static explicit operator byte(Decimal value)

public static explicit operator sbyte(Decimal value)

public static explicit operator char(Decimal value)

public static explicit operator short(Decimal value)

public static explicit operator ushort(Decimal value)

public static explicit operator int(Decimal value)

public static explicit operator uint(Decimal value)

public static explicit operator long(Decimal value)

public static explicit operator ulong(Decimal value)

public static explicit operator float(Decimal value)

public static explicit operator double(Decimal value)

 

That’s why you don’t have to use explicit cast when assigning a long to a decimal:

decimal x1 = 5l;

but you do assigning a float:

decimal x2 = (decimal) 5f;

 

 

Now, what happens if a class/structure does not implement implicit or explicit conversion?  The answer is simple – with or without an explicit cast, you will likely get a compile time error, e.g. Cannot convert type 'string' to 'decimal':

decimal x = (decimal) "5";

 

But, say, you know that string is actual a decimal…  If the class implements IConvertable interface, you can then use System.Convert.ChangeType to change the data type:

decimal x = (decimal) System.Convert.ChangeType("5", typeof(decimal));

 

 

Think of ChangeType as a big switch statement (select case in VB)…  with lots of overloaded functions.  Something like this (disclaimer: this is intended to be a pseudo-code, not the exact .NET’s implementation):

 

public static Object ChangeType(Object value, TypeCode typeCode , IFormatProvider provider)

{

IConvertible v = value as IConvertible;

switch (typeCode) {

case TypeCode.Boolean:

    return v.ToBoolean(provider);

case TypeCode.Char:

    return v.ToChar(provider);

case TypeCode.SByte:

    return v.ToSByte(provider);

case TypeCode.Byte:

    return v.ToByte(provider);

case TypeCode.Int16:

    return v.ToInt16(provider);

case TypeCode.UInt16:

    return v.ToUInt16(provider);

. . .

}

 

A class that claims to implement IConvertible interface must implement all the conversions in the switch structure above plus GetTypeCode:

· GetTypeCode

· ToBoolean

· ToByte

· ToChar

· ToDateTime

· ToDecimal

· ToDouble

· ToInt16

· ToInt32

· ToInt64

· ToSByte

· ToSingle

· ToString

· ToType

· ToUInt16

· ToUInt32

· ToUInt64

 

System.Convert class has a number of implementations that can be, in turn, called by the class implementing IConvertible interface.

 

The way I see it, implicit/explicit operators and IConvertible interface implementation allow you to convert one data type to another with increasing degree of conversion complexity, validation, and chance of runtime exceptions. For example, implicit cast should be used when there is no chance of data loss or runtime exceptions due to the conversion, the explicit cast should be used when there is a chance of data loss (that’s why you’re making the developer state that the conversion is desirable and data loss is acceptable) but still no runtime exceptions, where as ChangeType could result in a runtime exception – e.g. if value being converted to Int32 > Int32.MaxValue you’ll get a OverflowException.