Page 1 of 5
Casting – the escape from strong typing
Casting is an odd business that has found its way into many modern languages via C and C++. Both Java and C# make use of it to allow programmers to escape the confines of strong typing without being able to create anything too unsafe. The big problem is that it confuses two aspects of dealing with variables – type and representation.
Type is simply a system of classification that is used strongly typed language to determine what sort of data a variable can store or reference. Type conversion can just mean changing the classification of an object or variable or it can mean changing the actual representation of the data for example integer to floating point.
In an ideal world we wouldn’t need to convert or even mix types – an integer would always be combined with other integers and why would you ever want to convert it to a floating point number? Of course if you want to write useful programs you have to allow a certain interplay between data types and this is where casting comes in.
Casting is all about type conversion and this is how it is usually introduced but in fact there is more to it as there is are some very confused semantics behind a simple piece of syntax – just write the type name in brackets in front of the variable you want to convert – but does this mean passive type conversion or active representation conversion?
What is worse is that most books on C# don’t venture very deeply into the idea beyond presenting the basic idea that casting is just type conversion. It’s a little more tricky so let’s start simple with casting value types and then look at the more interesting reference type casting.
The simplest use of a cast is to convert from one numeric type to another – and this is where the active representation conversion first enters the picture. When the cast is safe from the point of view of representation then the compiler will do the job implicitly for you.
MyLong = MyInt;
is fine as the compiler can always store a 32-bit integer in a 64-bit integer. This is an example of a “widening” cast in the sense that the conversion is from a smaller or narrower type to a bigger or wider type.
In this case the compiler assumes that you intended to write:
MyLong = (long)MyInt;
However, if the implied cast could be unsafe then the compiler insists that you write out your intentions explicitly.
MyInt = MyLong;
will, quite rightly generate a compile time error because you can’t always convert a 64-bit integer into a 32-bit integer. This is an example of a “narrowing” cast in the sense that the conversion is from a bigger or wider type to a smaller or narrower type.
The fact that you can’t always do it doesn’t mean that it is always wrong. If you really mean it then you can write:
MyInt = (int)MyLong;
and the compiler will perform the type conversion by generating the necessary IL to convert a 32-bit integer into a 64-bit integer. Notice that there is no natural way to do this conversion and in practice C# simply moves the low 32 bits of the long value into the int.
Notice that casting only works in this way for numeric data. A beginner might suspect that if:
MyInt = (int)MyLong;
works then so should:
but of course it doesn’t. Why it doesn’t is something we could argue about for a long time. If you are going to use casting to perform a change of representation for numeric values why not for strings and other data based objects? Instead for most active type conversions you have to use a method:
There are, of course a number of versions of the ToString() method that allow you to control the format of the conversion and this makes it more flexible than the cast syntax.
Convert not cast
So the next question is – are there other conversion methods?
The answer is yes but you have to use the Convert class which, being part of the Framework, is a language neutral way of doing CLS type conversions.
So while you can’t write:
MyInt = MyLong.ToInt32();
You can write:
MyInt = Convert.ToInt32(MyLong);
Unlike the simple cast the method throws an exception if the long is too big to fit into the int.
If you want to convert a single or a double to an int then things are even more confusing as the two ways give different answers:
double MyDouble = 2.6;
MyInt = (int)MyDouble;
MyInt = Convert.ToInt32(MyDouble);
The cast truncates to give 2 but the ToInt32 rounds to give 3.
Of course if you want to take control of type conversion you can always use the Math class as in:
and there are overloaded methods that will give you control of how the rounding is to be performed. Notice however that neither method actually performs a type conversion so you will still need a cast as in:
MyInt = (int) Math.Round(MyDouble);
MyInt = (int) Math.Truncate(MyDouble);