CHAPTER 15 Conversions in C#

In C#, conversions are divided into implicit and explicit conversions. Implicit conversions are those that will always succeed; the conversion can always be performed without data loss. For numeric types, this means the destination type can fully represent the range of the source type. For example, a short can be converted implicitly to an int, because the short range is a subset of the int range.

1. Numeric Types

For the numeric types, there are widening implicit conversions for all the signed and unsigned numeric types. Figure 15-1 shows the conversion hierarchy. If a path of arrows can be followed from a source type to a destination type, there’s an implicit conversion from the source to the destination. For example, there are implicit conversions from sbyte to short, from byte to decimal, and from ushort to long.

Note that the path taken from a source type to a destination type in the figure doesn’t represent how the conversion happens; it merely indicates it can be done. In other words, the conversion from byte to long happens in a single operation, not by converting through ushort and uint:

class Test {

public static void Main()

{

// all implicit

sbyte v = 55;

short v2 = v;

int v3 = v2;

long v4 = v3;

// explicit to “smaller” types

v3 = (int) v4;

v2 = (short) v3;

v = (sbyte) v2;

}

}

2. Conversions and Member Lookup

When considering overloaded members, the compiler may have to choose between several functions. Consider the following:

using System; class Conv {

public static void Process(sbyte value)

{

Console.WriteLine(“sbyte {0}”, value);

}

public static void Process(short value)

{

Console.WriteLine(“short {0}”, value);

}

public static void Process(int value)

{

Console.WriteLine(“int {0}”, value);

}

}

class Test {

public static void Main() {

int valuel = 2;

sbyte value2 = 1;

Conv.Process(value1);

Conv.Process(value2);

}

}

The preceding code produces the following output:

int 2 sbyte 1

In the first call to Process(), the compiler could match the int parameter to only one of the functions—the one that took an int parameter.

In the second call, however, the compiler had three versions to choose from, taking sbyte, short, or int. To select one version, it first tries to match the type exactly. In this case, it can match sbyte, so that’s the version that gets called. If the sbyte version didn’t appear there, it’d select the short version, because a short can be converted implicitly to an int. In other words, short is “closer to” sbyte in the conversion hierarchy and is therefore preferred.

The preceding rule handles many cases, but it doesn’t handle the following one:

using System; class Conv {

public static void Process(short value)

{

Console.WriteLine(“short {0}”, value);

}

public static void Process(ushort value)

{

Console.WriteLine(“ushort {0}”, value);

}

}

class Test {

public static void Main()

{

byte value = 3;

Conv.Process(value);

}

}

Here, the earlier rule doesn’t allow the compiler to choose one function over the other, because there are no implicit conversions in either direction between ushort and short.

In this case, there’s another rule that kicks in, which says that if there’s a single-arrow implicit conversion to a signed type, it will be preferred over all conversions to unsigned types. Figure 15-1 represents this graphically with dotted arrows; the compiler will choose a single solid arrow over any number of dotted arrows.

3. Explicit Numeric Conversions

Explicit conversions—those using the cast syntax—are the conversions that operate in the opposite direction from the implicit conversions. Converting from short to long is implicit; therefore, converting from long to short is an explicit conversion.

Viewed another way, an explicit numeric conversion may result in a value that’s different from the original:

using System; class Test {

public static void Main()

{

uint value1 = 312;

byte value2 = (byte) value1;

Console.WriteLine(“Value2: {0}”, value2);

}

}

The preceding code results in the following output: 56

In the conversion to byte, the least significant (lowest valued) part of the uint is put into the byte value. In many cases, the programmer either knows that the conversion will succeed or depends on this behavior.

4. Checked Conversions

In other cases, it may be useful to check whether the conversion succeeded. You can do this by executing the conversion in a checked context:

using System; class Test {

public static void Main()

{

checked

{

uint valuel = 312;

byte value2 = (byte) valuel;

Console.WriteLine(“Value: {0}”, value2);

}

}

}

When an explicit numeric conversion takes place in a checked context, if the source value won’t fit in the destination data type, an exception will be thrown.

The checked statement creates a block in which conversions are checked for success. Whether a conversion is checked is determined at compile time, and the checked state doesn’t apply to code in functions called from within the checked block.

Checking conversions for success does have a small performance penalty and therefore may not be appropriate for released software. It can, however, be useful to check all explicit numeric conversions when developing software. The C# compiler provides a /checked compiler option that will generate checked conversions for all explicit numeric conversions. This option can be used while developing software and then can be turned off to improve performance for released software.

If the programmer is depending upon the unchecked behavior, turning on /checked could cause problems. In this case, the unchecked statement can indicate that none of the conversions in a block should ever be checked for conversions.

It’s sometimes useful to be able to specify the checked state for a single statement; in this case, you can specify the checked or unchecked operator at the beginning of an expression:

using System; class Test {

public static void Main()

{

uint value1 = 312; byte value2;

value2 = unchecked((byte) value1);

value2 = (byte) value1;

value2 = checked((byte) value1);

}

}

In this example, the first conversion will never be checked, the second conversion will be checked if the /checked statement is present, and the third conversion will always be checked.

5. Conversions of Classes (Reference Types)

Conversions involving classes are similar to those involving numeric values, except that object conversions deal with casts up and down the object inheritance hierarchy instead of conversions up and down the numeric type hierarchy.

C# also allows conversion between unrelated classes (or structs) to be overloaded. We discuss this later in this chapter.

As with numeric conversions, implicit conversions are those that will always succeed, and explicit conversions are those that may fail.

5.1. To the Base Class of an Object

A reference to an object can be converted implicitly to a reference to the base class of an object. Note that this doesn’t convert the object to the type of the base class; only the reference is to the base class type. The following example illustrates this:

using System; public class Base {

public virtual void WhoAmI()

{

Console.WriteLine(“Base”);

}

}

public class Derived: Base {

public override void WhoAmI()

{

Console.WriteLine(“Derived”);

}

}

public class Test {

public static void Main()

{

Derived d = new Derived();

Base b = d;

b.WhoAmI();

Derived d2 = (Derived) b;

object o = d;

Derived d3 = (Derived) o;

}

}

This code produces the following output:

Derived

Initially, a new instance of Derived is created, and the variable d contains a reference to that object. The reference d is then converted to a reference to the base type Base. The object referenced by both variables, however, is still a Derived; when the virtual function WhoAmI() is called, the version from Derived is called.[2] It’s also possible to convert the Base reference b back to a reference of type Derived or to convert the Derived reference to an object reference and back.

Converting to the base type is an implicit conversion because, as discussed in Chapter 1, a derived class is always an example of the base class. In other words, Derived “is-a” Base.

Explicit conversions are possible between classes when a “could-be” relationship exists. Because Derived is derived from Base, any reference to Base could really be a Base reference to a Derived object, and therefore the conversion can be attempted. At runtime, the actual type of the object referenced by the Base reference (b in the previous example) will be checked to see if it’s really a reference to Derived. If it isn’t, an exception will be thrown on the conversion.

Because object is the ultimate base type, any reference to a class can be implicitly converted to a reference to object, and a reference to object may be explicitly converted to a reference to any class type.

Figure 15-2 illustrates the previous example.

5.2. To an Interface the Object Implements

Interface implementation is somewhat like class inheritance. If a class implements an interface, an implicit conversion can be used to convert from a reference to an instance of the class to the interface. This conversion is implicit because it’s known at compile time that it works.

Once again, the conversion to an interface doesn’t change the underlying type of an object.

A reference to an interface can therefore be converted explicitly back to a reference to an object that implements the interface, since the interface reference “could-be” referencing an instance of the specified object.

In practice, converting from the interface to an object is an operation that’s rarely, if ever, used.

5.3. To an Interface the Object Might Implement

The implicit conversion from an object reference to an interface reference discussed in the previous section isn’t common. An interface is especially useful in situations where it isn’t known whether an object implements an interface. The following example implements a debug trace routine that uses an interface if it’s available:

using System;

interface IDebugDump {

string DumpObject();

}

class Simple {

public Simple(int value)

{

this.value = value;

}

public override string ToString()

{

return(value.ToString());

}

int value;

}

class Complicated: IDebugDump {

public Complicated(string name)

{

this.name = name;

}

public override string ToString()

{

return(name);

}

string IDebugDump.DumpObject()

{

return(String.Format(

“{0}\nLatency: {1}\nRequests: {2}\nFailures: {3}\n”,

new object[] {name, latency, requestCount, failedCount} ));

}

string name;

int latency = 0;

int requestCount = 0;

int failedCount = 0;

}

class Test {

public static void DoConsoleDump(params object[] arr)

{

foreach (object o in arr)

{

IDebugDump dumper = o as IDebugDump;

if (dumper != null)

Console.WriteLine(“{0}”, dumper.DumpObject());

else

Console.WriteLine(“{0}”, o);

}

}

public static void Main()

{

Simple s = new Simple(13);

Complicated c = new Complicated(“Tracking Test”);

DoConsoleDump(s, c);

}

}

This produces the following output: 13

Tracking Test

Latency: 0

Requests: 0

Failures: 0

This example has dumping functions that can list objects and their internal state. Some objects have a complicated internal state and need to pass back some rich information, while others can get by with the information returned by their ToString() functions.

This is nicely expressed by the IDebugDump interface, which is used to generate the output if an implementation of the interface is present.

This example uses the as operator, which will return the interface if the object implements it or null if it doesn’t.

5.4 From One Interface Type to Another

A reference to an interface can be converted implicitly to a reference to an interface that it’s based upon. It can be converted explicitly to a reference to any interface that it isn’t based upon. This is successful only if the interface reference is a reference to an object that implements the other interface as well.

6. Conversions of Structs (Value Types)

The only built-in conversion dealing with structs is an implicit conversion from a struct to an interface that it implements. The instance of the struct will be boxed to a reference and then converted to the appropriate interface reference. There are no implicit or explicit conversions from an interface to a struct.

Source: Gunnerson Eric, Wienholt Nick (2005), A Programmer’s Introduction to C# 2.0, Apress; 3rd edition.

Leave a Reply

Your email address will not be published. Required fields are marked *