If you're coming to C# from a weakly typed language, such as VB6, you may find the concept of strongly typed variables somewhat daunting at first. But once you get used to strong typing, you'll begin to appreciate the logic behind it and the benefits it provides: clarity and extra accuracy, better programming discipline, and fewer errors.
In my previous column, I introduced strong typing, showed how VB.NET differs from C#.NET, described the various type conversion techniques, and explained implicit casting. Now it's time to take a closer look at how explicit conversion, or casting, works. I’ll also discuss the as operator, the conversion methods that are members of the System.Convert class, and the ToString method.
Explicit type conversion
Explicit conversion is denoted using the cast operator, which is the name of the type being converted to inside parentheses placed before the expression being converted. For example, if the variable L stored a long value, the snippet below would convert that long value to an integer value stored in the variable I:
int I = (int) L;
An int value can be implicitly converted to a long value without any additional C# syntax, but the reverse is not true: A long value cannot be implicitly converted to type int.
It is easy to see why this should be the case. Longs have a much larger range of possible values than ints, and there is no way the compiler can know that a long-to-int conversion won’t occur when the long stores a bigger value than the int can store, causing a runtime error. In other words, the conversion isn’t guaranteed safe.
Suppose you change the code in the example around and attempt to cause an implicit conversion by calling a method with a long argument that is expecting an int, as shown in Listing A. Listing A won’t compile, and you’ll get a syntax error (Figure A).
|Syntax error following an attempt to implicitly convert a long to an int|
However, sometimes programmers do, in fact, know more than compilers. It might make sense for you to convert the long value to an int. You might know perfectly well that the variable holding the long value in the program will never hold a big enough number at the point it is converted to int to cause a problem.
In those situations, you can just use an explicit conversion by placing a cast operator in front of the expression to be converted. For instance, as Listing B shows, you can use the cast in the example that gave us a syntax error. The code will compile and run fine, producing the message box shown in Figure B.
|Explicit conversion was successful.|
Of course you don’t really need a separate statement for the performance of the cast; it can be done at the same time as the method invocation. The doubleIt method, which expects an int argument, receives it in the form of a cast from long to int:
long X = 2;
Table A shows the explicit numeric conversions you can perform in C# using casting. As you might expect, it is perfectly permissible to explicitly cast a type to another even if the conversion could be handled by implicit conversion (for example, int to long).
You can also do casts between more complex reference types, although some rules do apply. For example, an object can be cast to any reference type, and a base class can be explicitly converted to a derived class.
For instance, if you have a bunch of controls wired up to fire one event, you can cast the sender parameter to the Control type:
Control ctrl = (Control) sender;
You can then use the Name property of the Control variable to display which control fired the event, as shown in Listing C. Figure C shows the message box generated by this code.
|Successful casting of sender parameter to Control type|
I knew that this cast would not fail, because each object stored in the sender parameter had to be a control derived from the Control class.
Boxing and unboxing
Boxing is storing a type as an object. The type can later be cast back to its original type with the original type information intact. This is particularly useful with arrays, since objects of any type can be stored in an array of objects (because all types are derived from the object type).
For example, the code in Listing D is legal and creates an array of objects that are assigned three different types (but all, of course, are objects once they are stored in the array).
What has actually happened is that the various types have been implicitly converted to type object. However, thanks to the boxing, the extended information relating to the derived type has been preserved.
To reverse the process and unbox the types stored as objects, you need to explicitly cast the element of the object array to the original type. For example:
int newInt = (int) stuff ;
string newString = (string) stuff ;
Button button2 = (Button) stuff ;
The as operator
The as operator works like a cast, except that if it can’t perform the indicated conversion, it returns null (a null reference that does not refer to any object) rather than throwing an exception as an unsuccessful explicit conversion would.
For instance, the code snippet in Listing E will store a null reference in the variable str (with the resulting display that “i is not a string,” shown in Figure D).
If the variable i had contained string data, such as
object i = "hello";
that string data would have been displayed in the message box after the object type i was converted to the string type str.
The shared public members of the System.Convert class can be used to convert a base data type to another base data type. (The fact that they are shared, of course, implies that you do have to instantiate a Convert object to use them.)
You can use the Object Browser to see what conversion methods are available (there are quite a few), as shown in Figure E.
In Listing F, the Convert.ToBoolean method converts the string “False” to the bool value false and displays the message box shown in Figure F.
|Message box with string false|
You’ll find a Convert class method for converting every base type to every other base type, and you’ll probably use these methods quite a bit.
You should know that the Convert methods throw an exception (and don’t do any conversion) when meaningful results cannot be obtained. For example, calling any of the methods that convert the reference type System.DateTime to or from anything other than a string always produces an exception (and no conversion takes place).
In addition, an overflow exception is thrown—and no conversion occurs—if you try to stuff too large a value into a type that can't store it.
The ToString method
The C# object type, from which all other types derive, provides a ToString method. This means that whatever your object is, it has a ToString method that delivers a string.
ToString is good for converting numbers to their string representation—a common task in displaying numbers, among other things. For example, Figure G shows the result of using the ToString method to display the first four places of the expansion of pi:
double theNumber = 3.1415;
|Message box showing the expansion of pi|
Although the ToString method of any object will always deliver a string, it may not always be the string you want. By definition, the ToString method of an object returns a string that represents the object. Unfortunately, it's up to the implementer of a class to decide what is returned by objects based on the class. Most of the time, ToString is implemented so that it returns something reasonable, but you won’t know until you try.
For instance, if you invoke a Form’s ToString method, you’ll get the fully qualified form name followed by the literal “, Text:” followed by the contents of the Form’s Text property (its caption). What you probably wanted was the unqualified name of the Form. It's true that you could parse this out of the string returned by the ToString method—but the point stands that with complex objects you need to be careful about exactly what ToString returns.
When creating your own classes, you should also bear in mind that you are responsible for implementing the ToString method in a useful way—other programmers using your class libraries will expect to use your ToString method implementation, and they'll be irritated if it doesn’t return what they expect.