Deep C# - Casting the Escape from Strong Typing |
Written by Mike James | |||||
Monday, 04 December 2023 | |||||
Page 4 of 4
Casting and OverridingYou need to be aware that considerations of virtual versus non-virtual inheritance also come into play with both up- and downcasting. If a method is defined to be virtual then it is late bound. If a method isn’t virtual, or if the override uses the new modifier, then it is early bound. For upcasting the way that this works has already been described in Chapter 5. If MyClassB inherits MyMethod from MyClassA and overrides it then: ClassA myA = new MyClassB(); myA.MyMethod(); will call the method defined on MyClassB if it was declared virtual and the method defined on MyClassA if it wasn’t declared virtual or if new was used in the MyClassB override. The same considerations apply to downcasting, but things are a little bit more complicated because, as already mentioned, a downcast usually involves an earlier upcast. The downcast is always early bound, but any upcast is late or early bound depending on the way the methods are declared. An example should make this clear. Suppose we have three classes which form an inheritance chain and each of the methods is non-virtual and hence early bound: class MyClassA { public void MyMethod() { Console.WriteLine("A"); } } class MyClassB : MyClassA { public new void MyMethod() { Console.WriteLine("B"); } }; class MyClassC : MyClassB { public new void MyMethod() { Console.WriteLine("C"); } }; Now we can implement a downcast: MyClassA myA = (MyClassA) new MyClassC(); ((MyClassB) myA).MyMethod(); Notice that myA is in fact a MyClassC, but the downcast is to a MyClassB. However, as early binding is used, we see the MyClassB method print B. The declared type of myA is MyClassB when the method is called. If you now make the MyClassA MyMethod virtual and that of MyClassB override you will see no change and the MyClassB method is called – early binding is still used. Finally, if you also change the MyClassC implementation of MyMethod to override then it is the MyClassC MyMethod that is called – late binding is used for the final upcast and you see C printed. It can be subtle, but it is logical. Arrays of Value TypesThe earlier example of a sorting method brings us to an interesting problem in casting that isn’t much discussed. This isn’t a theoretical point but a practical decision made to make the language more efficient. When it happens it can leave you puzzled for some time. You might think that the previous example of sorting objects would work with: int[] testdata = new int[] { 1, 2, 3 }; int[] result = MySort((object[])testdata); After all, there is nothing wrong with the cast and the system should box the integer value type automatically into a reference type that can be cast to object. However, to avoid you unthinkingly writing this and causing the inefficient boxing of many integers this is not allowed and you will see the error message: Cannot convert from type int[] to object[] This leads some programmers to conclude that you can’t cast arrays. You can cast arrays as long as they have the same number of dimensions, but you can’t cast value arrays to an array of reference types because it is a very time consuming process. There seems to be no better way around this problem than to convert the value type array to an array of reference types by way of iteration – even if this fact is hidden by the way it is written. For example, you can use the Array static class and its Copy method, which will perform a cast on each element during the copy: int[] testdata = new int[] { 1, 2, 3 }; object[] obtestdata = new object[testdata.Length]; Array.Copy(testdata,obtestdata,testdata.Length); object[] result = MySort(obtestdata); Notice that the array sizes have to match and that you can’t cast the object[] back to int[] for the same reason, i.e. unboxing is inefficient. You can also use the newer generic method that is part of the Array static object to write the whole thing without using intermediate arrays: object[] result = MySort( Array.ConvertAll<int,object>(testdata, delegate(int i){return(object)i;})); but you still have to also do the conversion from object[] to int[]. No matter how you approach this problem, it is messy due to the syntax having to change for a method that works by upcasting to object when you move from reference to value variables. Part of a better solution is provided by custom casts. In chapter but not in this extract
Postlude When you realize how confused the use of casting is in C# and most modern languages you can’t help but be unhappy about it. If you want to put some order into your use of casting then use it only for passive type conversions and for any active type conversion that involves manipulation of the representation of a type. If your real purpose is to provide active type conversion for your classes, then see the TypeConverter class and the IConvertible interface in the C# documentation. Keep in mind that generics provide a good way of avoiding the use of casts, but that there are times when casting provides a more powerful solution to the same problem. Deep C#Buy Now From Amazon Chapter List
Extra Material <ASIN:1871962714> <ASIN:B09FTLPTP9> To be informed about new articles on I Programmer, sign up for our weekly newsletter, subscribe to the RSS feed and follow us on Twitter, Facebook or Linkedin.
Comments
or email your comment to: comments@i-programmer.info |
|||||
Last Updated ( Monday, 04 December 2023 ) |