Type Conversion


Earlier in this book you saw that all data, regardless of type, is simply a sequence of bits, that is, a sequence of zeros and ones. The meaning of the variable comes through the way in which this data is interpreted. The simplest example of this is the char type. This type represents a character in the Unicode character set using a number. In fact, this number is stored in exactly the same way as a ushort — both of them store a number between 0 and 65535.

However, in general, you will find that the different types of variables use varying schemes to represent data. This implies that even if it is possible to place the sequence of bits from one variable into a variable of a different type (perhaps they use the same amount of storage, or perhaps the target type has enough storage space to include all the source bits), the results might not be what you expect!

Instead of this one-to-one mapping of bits from one variable into another, you need to use type conversion on the data.

Type conversion takes two forms:

  • Implicit conversion: Where conversion from type A to type B is possible in all circumstances, and the rules for performing the conversion are simple enough for you to trust in the compiler.

  • Explicit conversion: Where conversion from type A to type B is only possible in certain circumstances or where the rules for conversion are complicated enough to merit additional processing of some kind.

Implicit Conversions

Implicit conversion requires no work on your part and no additional code. Consider the code shown here:

 var1 = var2; 

This assignment may involve an implicit conversion if the type of var2 can be implicitly converted into the type of var1, but it could just as easily involve two variables with the same type, and no implicit conversion is necessary.

Here's an example:

The values of ushort and char are effectively interchangeable, because both store a number between 0 and 65535. You can convert values between these types implicitly, as illustrated by the following code:

 ushort destinationVar; char sourceVar = 'a'; destinationVar = sourceVar; Console.WriteLine("sourceVar val: {0}", sourceVar); Console.WriteLine("destinationVar val: {0}", destinationVar); 

Here, the value stored in sourceVar is placed in destinationVar. When you output the variables with the two Console.WriteLine() commands, you get the following output:

sourceVar val: a destinationVar val: 97

Even though the two variables store the same information, they are interpreted in different ways using their type.

There are many implicit conversions of simple types; bool and string have no implicit conversions, but the numeric types have a few. For reference, the following table shows the numeric conversions that the compiler can perform implicitly (remember that chars are stored as numbers, so char counts as a numeric type):

Type

Can Safely Be Converted To

Byte

short, ushort, int, uint, long, ulong, float, double, decimal

Sbyte

short, int, long, float, double, decimal

Short

int, long, float, double, decimal

Ushort

int, uint, long, ulong, float, double, decimal

Int

long, float, double, decimal

Uint

long, ulong, float, double, decimal

Long

float, double, decimal

Ulong

float, double, decimal

Float

double

Char

ushort, int, uint, long, ulong, float, double, decimal

Don't worry — you don't need to learn this table by heart, because it's actually quite easy to work out which conversions the compiler can do implicitly. Back in Chapter 3, you saw a table showing the range of possible values for every simple numeric type. The implicit conversion rule for these types is this: any type A whose range of possible values completely fits inside the range of possible values of type B can be implicitly converted into that type.

The reasoning for this is simple. If you try to fit a value into a variable, but that value is outside the range of values the variable can take, there will be a problem. For example, a short type variable is capable of storing values up to 32767, and the maximum value allowed into a byte is 255, so there could be problems if you try to convert a short value into a byte value. If the short holds a value between 256 and 32767, it simply won't fit into a byte.

However, if you know that the value in your short type variable is less than 255, then surely you should be able to convert the value, right?

The simple answer is that, of course, you can. The slightly more complex answer is that, of course, you can, but you must use an explicit conversion. Performing an explicit conversion is a bit like saying "OK, I know you've warned me about doing this, but I'll take responsibility for what happens."

Explicit Conversions

As their name suggests, explicit conversions occur when you explicitly ask the compiler to convert a value from one data type to another. Because of this, they require extra code, and the format of this code may vary, depending on the exact conversion method. Before you look at any of this explicit conversion code, you should look at what happens if you don't add any.

For example, the following modification to the code from the last section attempts to convert a short value into a byte:

 byte destinationVar; short sourceVar = 7; destinationVar = sourceVar; Console.WriteLine("sourceVar val: {0}", sourceVar); Console.WriteLine("destinationVar val: {0}", destinationVar);

If you attempt to compile this code, you will receive the following error:

Cannot implicitly convert type 'short' to 'byte'. An explicit conversion exists (are you missing a cast?)

Luckily for you, the C# compiler can detect missing explicit conversions!

To get this code to compile, you need to add the code to perform an explicit conversion. The easiest way to do this in this context is to cast the short variable into a byte (as suggested by the error string shown above). Casting basically means forcing data from one type into another and involves the following simple syntax:

(destinationType)sourceVar 

This will convert the value in sourceVar into destinationType.

Note

Note that this is only possible in some situations. Types that bear little or no relation to each other are likely not to have casting conversions defined.

You can, therefore, modify your example using this syntax to force the conversion from a short to a byte

byte destinationVar; short sourceVar = 7; destinationVar = (byte)sourceVar; Console.WriteLine("sourceVar val: {0}", sourceVar); Console.WriteLine("destinationVar val: {0}", destinationVar);

resulting in the following output:

sourceVar val: 7 destinationVar val: 7

So, what happens when you try to force a value into a variable into which it won't fit? Modifying your code as follows illustrates this:

byte destinationVar; short sourceVar = 281; destinationVar = (byte)sourceVar; Console.WriteLine("sourceVar val: {0}", sourceVar); Console.WriteLine("destinationVar val: {0}", destinationVar); 

This results in:

sourceVar val: 281 destinationVar val: 25

What happened? Well, if you look at the binary representations of these two numbers, along with the maximum value that can be stored in a byte, which is 255

281 = 100011001  25 = 000011001 255 = 011111111

you can see that the leftmost bit of the source data has been lost. This immediately raises a question: how can you tell when this happens? Obviously, there will be times when you will need to explicitly cast one type into another, and it would be nice to know if any data has been lost along the way. If you didn't detect this, it could cause serious errors, for example in an accounting application or an application determining the trajectory of a rocket to the moon.

One way of doing this is simply to check the value of the source variable and compare it with the known limits of the destination variable. You also have another technique available, which is to force the system to pay special attention to the conversion at runtime. Attempting to fit a value into a variable when that value is too big for the type of that variable results in an overflow, and this is the situation you want to check for.

Two keywords exist for setting what is called the overflow checking context for an expression: checked and unchecked. You use these in the following way:

 checked(expression) unchecked(expression) 

You can force overflow checking in the last example:

byte destinationVar; short sourceVar = 281; destinationVar = checked((byte)sourceVar); Console.WriteLine("sourceVar val: {0}", sourceVar); Console.WriteLine("destinationVar val: {0}", destinationVar);

When this code is executed, it will crash with the error message shown in Figure 5-1 (I've compiled this in a project called OverflowCheck).

image from book
Figure 5-1

However, if you replace checked with unchecked in this code, you will get the result you saw earlier, and no error will occur. This is identical to the default behavior you saw earlier.

As well as these two keywords, you can configure your application to behave as if every expression of this type includes the checked keyword, unless that expression explicitly uses the unchecked keyword (in other words, you can change the default setting for overflow checking). To do this, you modify the properties for your project in VS by right-clicking on the project in the Solution Explorer window and selecting the Properties option. Click on Build on the left side of the window, and this will bring up the Build settings, as shown in Figure 5-2.

image from book
Figure 5-2

The property you want to change is one of the Advanced settings, so you click the Advanced button. In the dialog that appears, check the Check for arithmetic overflow/underflow option, as shown in Figure 5-3. By default, this setting is disabled, but enabling it gives the checked behavior detailed previously.

image from book
Figure 5-3

Explicit Conversions Using the Convert Commands

The type of explicit conversion you have been using in many of the Try It Out examples in this book is a bit different from those you have seen so far in this chapter. You have been converting string values into numbers using commands such as Convert.ToDouble(), which is obviously something that won't work for every possible string.

If, for example, you try to convert a string like Number into a double value using Convert.ToDouble(), you will see the dialog shown in Figure 5-4 when you execute the code.

image from book
Figure 5-4

As you can see, the operation fails. For this type of conversion to work, the string supplied must be a valid representation of a number, and that number must be one that won't cause an overflow. A valid representation of a number is one that contains an optional sign (that is, plus or minus), zero or more digits, an optional period followed by one or more digits, and an optional "e" or "E" followed by an optional sign and one or more digits and nothing else except spaces (before or after this sequence). Using all of these optional extras, you can recognize strings as complex as -1.2451e-24 as being a number.

There are many such explicit conversions that you can specify in this way, as the following table shows:

Command

Result

Convert.ToBoolean(val)

val converted to bool.

Convert.ToByte(val)

val converted to byte.

Convert.ToChar(val)

val converted to char.

Convert.ToDecimal(val)

val converted to decimal.

Convert.ToDouble(val)

val converted to double.

Convert.ToInt16(val)

val converted to short.

Convert.ToInt32(val)

val converted to int.

Convert.ToInt64(val)

val converted to long.

Convert.ToSByte(val)

val converted to sbyte.

Convert.ToSingle(val)

val converted to float.

Convert.ToString(val)

val converted to string.

Convert.ToUInt16(val)

val converted to ushort.

Convert.ToUInt32(val)

val converted to uint.

Convert.ToUInt64(val)

val converted to ulong.

Here val can be most types of variable (if it's a type that can't be handled by these commands, the compiler will tell you).

Unfortunately, as the table shows, the names of these conversions are slightly different from the C# type names; for example, to convert to an int you use Convert.ToInt32(). This is so because these commands come from the .NET Framework System namespace, rather than being native C#. This allows them to be used from other .NET-compatible languages besides C#.

The important thing to note about these conversions is that they are always overflow-checked, and the checked and unchecked keywords and project property settings have no effect.

The next Try It Out is an example that covers many of the conversion types from this section. It declares and initializes a number of variables of different types, then converts between them implicitly and explicitly.

Try It Out – Type Conversions in Practice

image from book
  1. Create a new console application called Ch05Ex01 in the directory C:\BegVCSharp\Chapter5.

  2. Add the following code to Program.cs:

    static void Main(string[] args) { short  shortResult, shortVal = 4; int    integerVal = 67; long   longResult; float  floatVal = 10.5F; double doubleResult, doubleVal = 99.999; string stringResult, stringVal = "17"; bool   boolVal = true; Console.WriteLine("Variable Conversion Examples\n"); doubleResult = floatVal * shortVal; Console.WriteLine("Implicit, -> double: {0} * {1} -> {2}", floatVal, shortVal, doubleResult); shortResult = (short)floatVal; Console.WriteLine("Explicit, -> short:  {0} -> {1}", floatVal, shortResult); stringResult = Convert.ToString(boolVal) +  Convert.ToString(doubleVal); Console.WriteLine("Explicit, -> string: \"{0}\" + \"{1}\" -> {2}", boolVal, doubleVal, stringResult); longResult = integerVal + Convert.ToInt64(stringVal); Console.WriteLine("Mixed,    -> long:   {0} + {1} -> {2}",  integerVal, stringVal, longResult); Console.ReadKey(); }

  3. Execute the code. The result is shown in Figure 5-5.

    image from book
    Figure 5-5

How It Works

This example contains all of the conversion types you've seen so far, both in simple assignments as in the short code examples in the preceding discussion and in expressions. You need to consider both cases, because the processing of every nonunary operator may result in type conversions, not just assignment operators. For example:

 shortVal * floatVal 

Here, you are multiplying a short value by a float value. In situations such as this, where no explicit conversion is specified, implicit conversion will be used if possible. In this example, the only implicit conversion that makes sense is to convert the short into a float (as converting a float into a short requires explicit conversion), so this is the one that will be used.

However, you can override this behavior should you wish, using:

 shortVal * (short)floatVal 

Note

This doesn't mean that a short will be returned from this operation. Since the result of multiplying two short values is quite likely to exceed 32767 (the maximum value a short can hold), this operation actually returns an int.

Explicit conversions performed using this casting syntax take the same operator precedence as other unary operators (such as ++ used as a prefix), that is, the highest level of precedence.

When you have statements involving mixed types, conversions occur as each operator is processed, according to the operator precedence. This means that "intermediate" conversions may occur, for example:

 doubleResult = floatVal + (shortVal * floatVal); 

The first operator to be processed here is *, which, as discussed previously, will result in shortVal being converted to a float. Next, you process the + operator, which won't require any conversion, because it acts on two float values (floatVal and the float type result of shortVal * floatVal). Finally, the float result of this calculation is converted into a double when the = operator is processed.

This conversion process can seem complex at first glance, but as long as you break expressions down into parts by taking the operator precedence order into account, you should be able to work things out.

image from book




Beginning Visual C# 2005
Beginning Visual C#supAND#174;/sup 2005
ISBN: B000N7ETVG
EAN: N/A
Year: 2005
Pages: 278

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net