GOTCHA 9 Typeless ArrayList isn t type-safe


GOTCHA #9 Typeless ArrayList isn't type-safe

Boxing and unboxing enable value types to be treated as objects. Boxing is an implicit conversion of a value type to the System.Object type; i.e., an Object instance is created (on the heap) and the value is copied into it. Unboxing is an explicit conversion from the Object type to a value type.

Collections (i.e., non-generic collections) treat every one of their elements as the Object type. When dealing with primitive value types, adding a value to a collection involves boxing, and accessing it from the collection involves unboxing. As a result, you have two problems to worry about. One, the boxing and unboxing will affect performance due to the copy overhead. Second, the value has to be unboxed to the proper type. In this gotcha we focus mainly on the latter problem. Code like that in Example 1-18 might compile OK but fail at run time.

Example 1-18. Behavior of ArrayList

C# (ArrayList)

 using System; using System.Collections; namespace ArrayListClassCastException {     class Test     {         [STAThread]         static void Main(string[] args)         {             ArrayList myList = new ArrayList();             myList.Add(3.0);             myList.Add(3);                 // Oops. 3 is boxed in as int not double             double total = 0;             foreach(double val in myList) // Exception here.             {                 total += val;             }             Console.WriteLine(total);         }     } } 

VB.NET (ArrayList)

 Module Test     Sub Main()         Dim myList As New ArrayList         myList.Add(3.0)         myList.Add(3)         ' Oops. 3 is boxed in as integer not double         Dim total As Double = 0         Dim val As Double         For Each val In myList ' No Exception here.             total += val         Next         Console.WriteLine(total)     End Sub End Module 

The behavior of the C# code is different from the equivalent VB.NET version (even withOption Strict On).

Let's first consider the C# code. In the example, you first add 3.0 to the ArrayList myList. This gets boxed in as a double. Then you add a 3. However, this gets boxed in as an integer. When you enumerate over the items in the collection and treat them as double s, an InvalidCastException is thrown as shown in Figure 1-13.

Figure 1-13. Output from the C# version of Example 1-18


What is the reason for this exception? The value 3 that was boxed as an int is unboxed as a double.

Let's now consider the VB.NET code. The VB.NET version appears to be doing the same thing as the C# version. That is, you add 3.0 to the ArrayList myList. This is boxed in as a Double. Then you add a 3. This is boxed in as an Integer. But when you enumerate the items in the collection and treat them as Double, you get the correct total, 6, as shown in Figure 1-14.

Figure 1-14. Output from the VB.NET version of Example 1-18


That is interesting! Why would C# fail, but not VB.NET? The answer is in the translation of source to MSIL. Let's take a look at the MSIL generated from C# and the MSIL generated from VB.NET.

Example 1-19 shows what MSIL command is generated from the C# code for unboxing. It is the unbox statement, which instructs the CLR to unbox the object to a System.Double.

Example 1-19. Unboxing in MSIL translated from C#
 IL_0041:  unbox      [mscorlib]System.Double 

Example 1-20, on the other hand, shows the MSIL that VB.NET produces. Instead of a simple unbox statement, it invokes the FromObject method on the DoubleType class in the Microsoft.VisualBasic namespace.

This method silently converts the Integer to Double, so you get the correct answer rather than an exception.

Example 1-20. Unboxing in MSIL translated from VB.NET
 IL_0051:  call       float64 [Microsoft.VisualBasic]     Microsoft.VisualBasic.CompilerServices.DoubleType::FromObject( object) 

Of course, if you modify the VB.NET code to add a Char instead of an Integer, you will get an exception. Let's take a look at this in Example 1-21.

Example 1-21. Adding a Character in the VB.NET example of ArrayList

VB.NET (ArrayList)

         Dim myList As New ArrayList         myList.Add(3.0)         myList.Add(3)         ' Oops. 3 is boxed in as integer not double         myList.Add("a"c)         ' Boxing "a" as Char not double     ... 

Now the VB.NET version behaves like the C# one, although the exception originates in the DoubleType class instead of the unbox command, as shown in Figure 1-15.

Figure 1-15. Output from Example 1-21


The problems mentioned in this gotcha are specific to non-generic collections. This will not be a problem in .NET 2.0 if you use generics. Generics provide type-safe data structures that resemble C++ templates in some ways (though they differ in their capabilities and implementation). This leads to code that's more reusable and better in performance.

The C# code that utilizes generics to perform the same function as in Example 1-18 is shown in Example 1-22. The corresponding VB.NET code is shown in Example 1-23.

Example 1-22. Generics version of the C# code from Example 1-18

C# (ArrayList)

 using System; using System.Collections.Generic; namespace ArrayListClassCastException {     class Test     {         [STAThread]         static void Main(string[] args)         {             Collection<double> myList = new Collection<double>();             myList.Add(3.0);             myList.Add(3); // No problem. 3 is stored as 3.0.             double total = 0;             foreach(double val in myList)             {                 total += val;             }             Console.WriteLine(total);         }     } } 

Example 1-23. Generics version of the VB.NET code from Example 1-21

VB.NET (ArrayList)

 Imports System.Collections.Generic Module Test     Sub Main()         Dim myList As New Collection(Of Double)         myList.Add(3.0)         myList.Add(3) 'No problem 3 stored as 3.0         myList.Add("a"c)         'error BC30311: Value of type 'Char'         'cannot be converted to 'Double'         Dim total As Double = 0         Dim val As Double         For Each val In myList             total += val         Next         Console.WriteLine(total)     End Sub End Module 

When you use generics, there is no boxing and unboxing overhead. The value of 3 is converted to 3.0 at compile time based on the parametric type double/Double of the Collection. (You can see this by looking at the MSIL code generated.) In the case of the VB.NET code, if you pass a character to the Collection's Add() method, you get a compilation error since it can't be converted to double/Double.

IN A NUTSHELL

Be careful with collections that treat elements as objects. For one thing, you may incur some boxing and unboxing overhead; for another, you may trigger an InvalidCastException. This problem goes away with Generics, so once they are available use them for type safety and performance.

SEE ALSO

Gotcha #2, "struct and class differ in behavior," Gotcha #3, "Returning value types from a method/property is risky," Gotcha #4, "You can't force calls to your value-type constructors," Gotcha #5, "String concatenation is expensive," and Gotcha #30, "Common Language Specification Compliance isn't the default."



    .NET Gotachas
    .NET Gotachas
    ISBN: N/A
    EAN: N/A
    Year: 2005
    Pages: 126

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