| [ directory ] |
|
5.5 CastingC#'s default nonvirtual methods require explicit activation of the virtual dispatching mechanism. There is a way, however, that you can force a method call on the actual object instantiated and not the reference without explicitly activating the virtual dispatching mechanism. Casting is very common in Java even though it sometimes is considered a performance bottleneck. Casting exists in C#, too, and it follows the same rules as in Java. Listing 5.18 shows an example of casting in action. Listing 5.18 A Simple Casting Example (C#)
using System;
public class SuperClass {
public void Test() {
Console.WriteLine("Super class test");
}
}
public class SubClass : SuperClass {
public void Test() {
Console.WriteLine("Sub class test");
}
static void Main(string[] args) {
SuperClass sc = new SubClass();
sc.Test();
((SubClass)sc).Test();
}
}
The casting happens on the last line of the entry point (Main method): ((SubClass)sc).Test(); Listing 5.19 revisits casting using the SuperClass, SubClass, and LeafClass example. Listing 5.19 Exploring Casting in C#
using System;
public class SuperClass {
public void Test() {
Console.WriteLine("Super class test");
}
}
public class SubClass : SuperClass {
public void Test() {
Console.WriteLine("Sub class test");
}
}
public class LeafClass : SubClass {
public void Test() {
Console.WriteLine("Leaf class test");
}
public static void Main(string[] args) {
//Scenario 1
LeafClass lc = new LeafClass();
lc.Test();
((SubClass)lc).Test();
((SuperClass) lc).Test();
//Scenario 2
SubClass sc = new LeafClass();
((LeafClass)sc).Test();
((SubClass)sc).Test();
((SuperClass)sc).Test();
//Scenario 3
SubClass tc = new SubClass();
((SubClass)tc).Test();
((SuperClass)tc).Test();
try {
((LeafClass)tc).Test();
} catch (InvalidCastException e) {
Console.WriteLine(e.StackTrace);
}
}
}
The output of scenario 1 of Listing 5.19 is as follows: Leaf class test Sub class test Super class test In this fairly obvious output, a LeafClass reference is assigned a LeafClass object. The method call is made on the LeafClass object the first time, and to get the SubClass and SuperClass implementation we simply cast the reference to the appropriate superclass. We created the most specific object and cast using the least specific classes. This is legal in both Java and C#. Scenario 2 is similar to scenario 1 except that now the reference is that of the SubClass class; had we not cast the SubClass reference to a LeafClass, the LeafClass implementation would not have been called. We have a SubClass reference, and we are casting that to a more specific LeafClass. This works because the actual object instantiated was that of LeafClass. Scenario 2's output is as follows: Leaf class test Sub class test Super class test In the last scenario we create a SubClass object and assign it to a SubClass reference. The reference is then cast to SubClass and SuperClass, and the appropriate method gets called. However, when we try to cast to the LeafClass object we get an InvalidCastException. This is because we are trying to downcast to a more specific class (LeafClass) when the object created was that of SubClass. The compiler did not catch the problem because all the objects were in the same class hierarchy. However, the runtime throws the exception. Because casting has a bad reputation in Java, it would be interesting to know how the performance of casting in C# matches up with virtual dispatching. Virtual dispatching would be slower than a simple method call on the reference, but would it be slower or faster than casting? Listing 5.20 explores the relative speed of using casting and the virtual dispatch mechanism to call the subclass method. In both cases the method is called on the object instantiated (SubClass). To put matters in perspective, we will also add a method call made only to the reference SuperClass. Listing 5.20 Comparing the Speed of Casting a Simple Reference Method Call versus Virtual Dispatching (C#)
using System;
public class SuperClass {
public virtual void Test() {
}
public void Test2() {
}
public void Test3() {
}
}
public class SubClass : SuperClass {
public override void Test() {
}
public void Test2() {
}
static void Main(string[] args) {
int COUNT = 100000000;
SuperClass sc = new SubClass();
long 11 = Environment.TickCount;
for (int i = 0; i < COUNT; ++i) {
sc.Test3();
}
long 12 = Environment.TickCount;
Console.WriteLine(12-11);
11 = Environment.TickCount;
for (int i = 0; i < COUNT; ++i) {
sc.Test();
}
12 = Environment.TickCount;
Console.WriteLine(12-11);
11 = Environment.TickCount;
for (int i = 0; i < COUNT; ++i) {
((SubClass)sc).Test2();
}
12 = Environment.TickCount;
Console.WriteLine(12-11);
}
}
A sample run of Listing 5.20 gives the following output: 2534 2664 3635 The reference-based method call is faster than virtual dispatching, which in turn is faster than casting. In deeper class hierarchies (where subclasses exist at several levels below the superclass), this difference would be accentuated. |
| [ directory ] |
|