Learning C# and OOP, Polymorphism, Type Conversion, Casting, etc.Baldwin teaches you about assignment compatibility, type conversion, and casting for both primitive and reference types. He also teaches you about the relationships among reference types, method invocations, and the location in the class hierarchy where a method is defined. Published: November 4, 2002
C# Programming Notes # 116 PrefaceThis lesson is one of a series of lessons designed to teach you about the essence of Object-Oriented Programming (OOP) using C#. The first lesson in the group was entitled Learning C# and OOP, Getting Started, Objects and Encapsulation. That lesson, and each of the lessons following that one, has provided explanations of certain aspects of the essence of Object-Oriented Programming using C#. The previous lesson was entitled Learning C# and OOP, Polymorphism Based on Overloaded Methods. Necessary and significant aspects This miniseries will describe and discuss the necessary and significant aspects of OOP using C#. If you have a general understanding of computer programming, you should be able to read and understand the lessons in this miniseries, even if you don't have a strong background in the C# programming language. Viewing tip You may find it useful to open another copy of this lesson in a separate browser window. That will make it easier for you to scroll back and forth among the different listings while you are reading about them. Supplementary material I recommend that you also study the other lessons in my extensive collection of online programming tutorials. You will find a consolidated index at www.DickBaldwin.com. PreviewType conversion This lesson discusses type conversion for both primitive and reference types. (Some people refer to primitive types as value types.) Assignment compatibility A value of a particular type may be assignment-compatible with variables of other types. If so, the value can be assigned directly to the variable. If not, it may be possible to perform a cast on the value to change its type and assign it to the variable as the new type. Successful cast depends on class hierarchy With regard to reference types, whether or not a cast can be successfully performed depends on the relationship of the classes involved in the class hierarchy. The generic type Object A reference to any object can be assigned to a reference variable of the type Object, because the Object class is a superclass of every other class. (In other words, it is assignment-compatible with the type Object.) What is a downcast? When we cast a reference along the class hierarchy in a direction away from the root class Object toward the leaves, we often refer to it as a downcast. Invoking a method on an object Whether or not a method can be invoked on a reference to an object depends on the current type of the reference and the location in the class hierarchy where the method is defined. In order to use a reference of a class type to invoke a method, the method must be defined at or above that class in the class hierarchy. A sample program is provided that illustrates much of the detail involved in type conversion, method invocation, and casting with respect to reference types. Discussion and Sample CodeWhat is polymorphism? The meaning of the word polymorphism is something like one name, many forms. How does C# implement polymorphism? Polymorphism manifests itself in C# in the form of multiple methods having the same name. In some cases, multiple methods have the same name, but different formal argument lists.
In other cases, multiple methods have the same name, same return type, and same formal argument list.
Three distinct forms of polymorphism From a practical programming viewpoint, polymorphism manifests itself in three distinct forms in C#:
I covered method overloading as one form of polymorphism in a previous lesson. In this lesson, I will backtrack a bit and discuss the conversion of references from one type to another. I will begin the discussion of polymorphism through method overriding and inheritance in the next lesson. I will cover interfaces in a subsequent lesson. Assignment compatibility and type conversion As a background for polymorphism, you need to understand something about assignment compatibility and type conversion. A value of a given type is assignment-compatible with another type if a value of the first type can be successfully assigned to a variable of the second type. Type conversion and the cast operator In some cases, type conversion happens automatically. In other cases, type conversion must be forced through the use of a cast operator. A cast operator is a unary operator, which has a single right operand. The physical representation of the cast operator is the name of a type enclosed by a pair of matched parentheses, as in:
Applying a cast operator Applying a cast operator to the name of a variable doesn't actually change the type of the variable. However, it does cause the contents of the variable to be treated as a different type for the evaluation of the expression in which the cast operator is contained. Primitive values and type conversion Assignment compatibility issues come into play for both primitive types and reference types. To begin with, values of type bool can only be assigned to variables of type bool (you cannot change the type of a bool). Thus, a value of type bool is not assignment-compatible with a variable of any other type. In general, numeric primitive values can be assigned to (are assignment-compatible with) a variable of a type whose numeric range is as wide as or wider than the range of the type of the value. In that case, the type of the value is automatically converted to the type of the variable.
Conversion to narrower range On the other hand, a primitive numeric value of a given type cannot be assigned to (is not assignment-compatible with) a variable of a type with a narrower range than the type of the value. However, it is possible to use a cast operator to force a type conversion for numeric primitive values.
Assignment compatibility for references Assignment compatibility for references doesn't involve range issues, as is the case with primitives. Rather, the reference to an object instantiated from a given class can be assigned to (is assignment-compatible with):
Such an assignment does not require the use of a cast operator. Type Object is completely generic A reference to any object can be assigned to a reference variable of the type Object, because the Object class is a superclass of every other class. Converting reference types with a cast Assignments of references, other than those listed above, require the use of a cast operator to purposely change the type of the reference. Doesn't work in all cases However, it is not possible to perform a successful cast to convert the type of a reference to another type in all cases. Generally, a cast can only be performed among reference types that fall on the same ancestral line of the class hierarchy, or on an ancestral line of an interface hierarchy. For example, a reference cannot be successfully cast to the type of a sibling or a cousin in the class hierarchy. Downcasting When we cast a reference along the class hierarchy in a direction away from the root class Object toward the leaves, we often refer to it as a downcast. While it is also possible to cast in the direction from the leaves to the root, this conversion happens automatically, and the use of a cast operator is not required. A sample program The program named Poly02, shown in Listing 11 near the end of the lesson, illustrates the use of the cast operator with references. When you examine that program, you will see that two classes named A and C each extend the class named Object. Hence, we might say that they are siblings in the class hierarchy. Another class named B extends the class named A. Thus, we might say that A is a child of Object, and B is a child of A. The class named A The definition of the class named A is shown in Listing 1.
This class implicitly extends the class named Object by default.
The class named A is empty. It was included in this example for the sole purpose of adding a layer of inheritance to the class hierarchy. The class named B Listing 2 shows the definition of the class named B. This
class extends the class named A.
The method named m() The class named B defines a method named m(). The behavior of the method is simply to display a message each time it is invoked. The class named C Listing 3 contains the definition of the class named C, which
also extends Object.
The class named C is also empty. It was included in this example as a sibling class for the class named A. Stated differently, it was included as a class that is not in the ancestral line of the class named B. The driver class Listing 4 shows the beginning of the driver class named Poly02.
An object of the class named B This code instantiates an object of the class B and assigns the object's reference to a reference variable of type Object.
This assignment is allowable because Object is a superclass of B. In other words, the reference to the object of the class B is assignment-compatible with a reference variable of the type Object. Automatic type conversion In this case, the reference of type B is automatically converted to type Object and assigned to the reference variable of type Object.
Only part of the story However, assignment compatibility is only part of the story. The simple fact that a reference is assignment-compatible with a reference variable of a given type says nothing about what can be done with the reference after it is assigned to the reference variable. An illegal operation For example, in this case, the reference variable that was automatically
converted to type Object cannot be used directly to invoke the method
named m() on the object of type B. This is indicated
in Listing 5.
An attempt to invoke the method named m() on the reference variable of type Object in Listing 5 resulted in the following compiler error: error CS0117:
An important rule In order to use a reference of a class type to invoke a method, the method must be defined at or above that class in the class hierarchy. Stated differently, the method must either be defined in, or inherited into that class. This case violates the rule In this case, the method named m() is defined in the class named B, which is two levels down from the class named Object.
When the reference to the object of the class B was assigned to the reference variable of type Object, the type of the reference was automatically converted to type Object. Therefore, because the reference is of type Object, it cannot be used directly to invoke the method named m(). The solution is a downcast In this case, the solution to the problem is a downcast. The code in Listing 6 shows an attempt to solve the problem by casting
the reference down the hierarchy to type A.
Still doesn't solve the problem However, this still doesn't solve the problem, and the result is another compiler error.
Again, it was necessary to convert the statement into a comment in order to get the program to compile. What is the problem here? The problem is that the downcast simply didn't go far enough down the inheritance hierarchy. The class named A does not contain a definition of the method named m(). Neither does it inherit the method named m(). The method named m() is defined in class B, which is a subclass of A. Therefore, a reference of type A is no more useful than a reference of type Object insofar as invoking the method named m() is concerned. The real solution The solution to the problem is shown in Listing 7.
The code in Listing 7 casts (converts) the reference value contained in the Object variable named var down to type B. The method named m() is defined in the class named B. Therefore, a reference of type B can be used to invoke the method. The code in Listing 7 compiles and executes successfully. This causes the method named m() to execute, producing the following output on the computer screen. m in class B A few odds and ends Before leaving this topic, let's look at a few more issues. The code in Listing 8 declares and populates a new variable of type
B.
The code in Listing 8 uses a cast to:
A legal operation This is a legal operation. In this class hierarchy, the reference to the object of the class B can be assigned to a reference variable of the types B, A, or Object.
Cannot be assigned to type C However, the reference to the object of the class B cannot be
assigned to a reference variable of any other type, including the type
C.
An attempt to do so is shown in Listing 9.
The code in Listing 9 attempts to cast the reference to type C and assign it to a reference variable of type C. A runtime exception Although the program will compile, it won't execute. An attempt to execute the statement in Listing 9 results in an exception at runtime. As a result, it was necessary to convert the statement into a comment in order to execute the program. Another failed attempt Similarly, an attempt to cast the reference to type B and assign
it to a reference variable of type C, as shown in Listing 10, won't
compile.
The problem here is that the class C is not a superclass of the class named B. Therefore, a reference of type B is not assignment-compatible with a reference variable of type C. Again, it was necessary to convert the statement to a comment in order to compile the program. SummaryThis lesson discusses type conversion for primitive and reference types. A value of a particular type may be assignment-compatible with variables of other types. If the type of a value is not assignment-compatible with a variable of a given type, it may be possible to perform a cast on the value to change its type and assign it to the variable as the new type. For primitive types, this will often result in the loss of information. In general, numeric values of primitive types can be assigned to any variable whose type represents a numeric range that is as wide as or wider than the range of the value's type. (Values of type bool can only be assigned to variables of type bool.) With respect to reference types, the reference to an object instantiated from a given class can be assigned to any of the following without the use of a cast:
Assignments of references, other than those listed above, require the use of a cast to change the type of the reference. It is not always possible to perform a successful cast to convert the type of a reference. Whether or not a cast can be successfully performed depends on the relationship of the classes involved in the class hierarchy. A reference to any object can be assigned to a reference variable of the type Object, because the Object class is a superclass of every other class. When we cast a reference along the class hierarchy in a direction away from the root class Object toward the leaves, we often refer to it as a downcast. Whether or not a method can be invoked on a reference to an object depends on the current type of the reference and the location in the class hierarchy where the method is defined. In order to use a reference of a class type to invoke a method, the method must be defined in or inherited into that class. A sample program is provided that illustrates much of the detail involved in type conversion, method invocation, and casting with respect to reference types. What's Next?I will begin the discussion of runtime polymorphism through method overriding and inheritance in the next lesson. I will demonstrate that for runtime polymorphism, the selection of a method for execution is based on the actual type of object whose reference is stored in a reference variable, and not on the type of the reference variable on which the method is invoked. Complete Program ListingA complete listing of the program is shown in Listing 11 below.
Copyright 2002, Richard G. Baldwin. Reproduction in whole or in part in any form or medium without express written permission from Richard Baldwin is prohibited. About the authorRichard Baldwin is a college professor (at Austin Community College in Austin, Texas) and private consultant whose primary focus is a combination of Java, C#, and XML. In addition to the many platform and/or language independent benefits of Java and C# applications, he believes that a combination of Java, C#, and XML will become the primary driving force in the delivery of structured information on the Web. Richard has participated in numerous consulting projects and he frequently provides onsite training at the high-tech companies located in and around Austin, Texas. He is the author of Baldwin's Programming Tutorials, which has gained a worldwide following among experienced and aspiring programmers. He has also published articles in JavaPro magazine. Richard holds an MSEE degree from Southern Methodist University and has many years of experience in the application of computer technology to real-world problems. -end- |
© 1996, 1997, 1998, 1999, 2000, 2001, 2002 Richard G. Baldwin |