C sharper - casting without doubt

This article was originally published in VSJ, which is now part of Developer Fusion.
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. 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. What is worse is that most books on C# don’t venture very deeply into the idea beyond presenting the basic concept that casting is just type conversion. It’s a little trickier than that, so let’s start simply with casting value types, and then look at the more interesting reference type casting.

Type conversion

The simplest use of a cast is to convert from one numeric type to another. When the cast is safe then the compiler will do the job implicitly for you. For example:
int MyInt;
long MyLong;
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. For example:
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:
MyString=(string)MyInt;
…but of course it doesn’t. Instead you have to use a method:
string MyString=MyInt.ToString();
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.

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:
Math.Round(MyDouble);
Math.Truncate(MyDouble);
…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);

Upcasting - moving up the inheritance hierarchy is always safe
Upcasting – moving up the inheritance hierarchy is always safe

Upcasting

In many ways casting is a construct that is intended to cope with some problems inherent in the mixing of strong typing with object-oriented programming. You can view its involvement with data format conversion of value types as an accident due to the need for some consistency. Indeed C# modelled its casting on C++ just at the point where C++ was attempting to put its house in order by adding some constructs that removed the need to use casts.

When you define a new class it always has a relationship to the classes it inherits from. At its simplest this could be just Object which is the root of the inheritance hierarchy. In many cases a class will also have a relationship with classes that have been derived from it. As a class that inherits from another class generally adds properties and methods, you can think of the new class as being in some sense “bigger” than the base class. That is if we define two classes:

class ClassA
{
}
class ClassB : ClassA
{
}
…then ClassB has all of the methods and properties of ClassA plus some of its own. Thus ClassB is “bigger” than ClassA. What this means is that you can treat a ClassB object as if it was in fact a ClassA object. In this case you would be ignoring the new bits added by defining ClassB and simply restricting your access to ClassA methods and properties – this “downsizing” of ClassB is generally called an upcast because it moves you up the object hierarchy towards the root object.

For example, ClassB inherits methodA from ClassA:

class ClassA
{
	public void methodA() { }
}
class ClassB : ClassA
{
	public void methodB() { }
}
Now we can create a ClassA variable and make it reference a ClassB object:
ClassA MyA;
MyA = new ClassB();
This works without the need for an explicit cast because it is safe, in the sense that you can call any ClassA members and expect them to be present in ClassB. That is you can use:
MyA.methodA();
…but not:
MyA.methodB();
There is one other slightly subtle point – if ClassB redefines MethodA, which class’s method is used in an upcast? The answer depends on exactly how the redefinition is performed either using new or override. This difference goes straight to the heart of why there two ways of redefining methods. If you redefine a method using the new modifier then the upcast:
((ClassA) MyB).methodA();
…will always call the MethodA defined in ClassA.

Of course this return to the original is one of the main reasons for upcasting. However, this doesn’t always work because if the method is redefined using the override modifier then the upcast will still call the version defined in ClassB. You can think of the difference as being related to where the modification to the method occurs in the class hierarchy. The new modifier results in the method being redefined down the hierarchy the override modifier redefined the method both up and down the hierarchy.

You might think that upcasting is a bit esoteric for you to use, but it is implicitly used in other C# constructs that rely on “polymorphism”. For example, if you create a collection of classes derived from a base class, then iterating through the collection will return a reference to objects of different types according to what is actually stored in the collection. In this case each object will be implicitly upcast to the base type of the collection. This means that for a method that is redefined in the derived classes, which method you actually call will depend on which of new or override was used when redefining the method.

Notice that you don’t have to use upcasting to call base class methods within the definition of a class because you can use the base keyword instead.

Downcasting - moving down the inheritance hierarchy isn't safe
Downcasting – moving down the inheritance hierarchy isn’t safe

Downcasting

If there is an upcast you can guess that there is going to be a downcast. In fact, downcasting is more commonly used than upcasting because it allows you to reference all of the derived classes, their methods and properties using the base class type. In many ways this is a more mysterious process (and more difficult to get used to), because downcasting moves down the class hierarchy to objects that are “bigger” than the base class. This is also potentially unsafe because you might try to use a method or property that the “bigger” class doesn’t actually support. For this reason downcasting is always explicit.

Consider the following example. First we create a MyA that references a ClassB object:

ClassA MyA;
MyA = new ClassB();
Notice that this is an example of upcasting, and to a certain extent this upcasting only to downcast again is artificial, but it isn’t difficult to find a more realistic example once the idea has been explained. Next we downcast the MyA reference to a MyB object:
ClassB MyB;
MyB = (ClassB)MyA;
In this case the cast has to be explicit and following this you can now call ClassB methods as in:
MyB.methodB();
This example is easy to follow but it doesn’t really give you any hint of why you might want to downcast – after all you could have used the ClassB object more sensibly by referencing it by using a ClassB type in the first place. What downcasting allows you to do is to use a reference to a class without really knowing what it is. The most extreme example of downcasting is to use an object reference for every class in your program. For example:
object MyAnything;
MyAnything = new ClassB();
((ClassB)MyAnything).methodB();
Why might this be useful? The answer is that you can now write code that will work with any type using nothing but object references. Of course the catch is that you need to know how to downcast the reference correctly so that you can use the members that are specific to the class and there is always the very real chance that you will get it all wrong. For example, if we change MyAnything to a ClassA object and try to downcast it to a ClassB object:
MyAnything = new ClassA();
((ClassB)MyAnything).methodB();
…then you don’t get a compile time error but you do get a run time exception.

If you abandon strong typing by using casts then this is the sort of problem you have to live with. There are two operators that can help avoid runtime exceptions. The is operator can be used to test the type of the reference before applying a cast. For example:

if (MyAnything is ClassB)
{
	ClassB MyB = (ClassB)MyAnything;
};
The as operator is an alternative way of writing a cast, but with the difference that if it fails the reference is to null. For example:
ClassB MyB = MyAnything as ClassB;
…never throws an exception, but you have to make sure that you don’t end up using a null reference because it didn’t actually work.

Downcasting can be dangerous if the object isn't the type you think it is
Downcasting can be dangerous if the object isn’t the type you think it is

A cast of my own

You can write methods that will be called when a cast is performed to your class. However, you can’t define a custom cast to or from the base class. In fact apart from making conversions to and from value types it is difficult to see what sort of custom casts you could possibly create. For example, if you want to allow ClassB to be custom cast to an int you might add something like:
public int Value=123;
public static explicit operator
	int(ClassB MyB)
{
	return MyB.Value;
}
This simply returns the value stored in a class variable, but in practice you could return something more complicated. All cast operators have be delcared static, but you can choose between explicit and implicit for the style of the cast. Following this definition you can write code like:
ClassB MyB=new ClassB();
int v = (int)MyB;
There is the argument that if you want to do a custom type conversion like this then it would be clearer to define a ToInt method rather than a custom cast – but you can decide which suits your approach.

The cast system

Now that we have looked in detail at casting you can begin to see what it is really all about. Casting is really to do with introducing a little organised freedom into the type system so that object oriented programming is more flexible. Without it, there are lots of things that would be much more difficult to do. It is arguable that it really shouldn’t be involved in an active type conversion because it confuses the issue and the inability to pass parameters makes it restrictive. If you want to be “pure” it might be better to implement all active type conversion as a method. To demonstrate how confusing this can be consider the following example where an object reference is downcast to an int (this is also an example of explicit boxing):
object MyAnything;
int MyIntA = 3;
MyAnything = MyIntA;
To convert back to an int (or to unbox the int) all you have to use is a simple cast:
int MyIntB = (int)MyAnything;
This all works, but what about:
long MyLong = (long) MyAnything;
This looks as if it is the same sort of cast but it doesn’t work because it’s attempting two sorts of type conversion in one go. You can’t downcast MyAnything to an int and perform an active widening conversion needed to go from an int to a long in one go. The correct way to do it is the rather ugly looking:
long MyLong = (long) (int) MyAnything;
First the explicit downcast and then the explicit widening cast are needed to make it all work.

There are various topics that we haven’t touched on that are relevant to casting. In particular, boxing and unboxing of value types is essentially just the use of upcasting and downcasting to and from objects. It is also worth knowing that generics (new in C# 2.0) provide a way of writing non-type specific code that can be instansated to work with a specific type without the need for casts. If your real purpose is to provide active type conversion for your classes, then see theTypeConverter class and the IConvertible interface in the C# documentation.


Dr. Mike James’ programming career has spanned many languages, starting with Fortran. He is the author of Foundations of Programming, and he has always been interested in the latest developments and the synergy between different languages.

You might also like...

Comments

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“Any fool can write code that a computer can understand. Good programmers write code that humans can understand.” - Martin Fowler