Last considerations about generics
As we’ve seen, constraints will help in writing “type safe generic code” (not sure if this designation really exists. if it doesn’t, then I’ve invented ). But even with generics, there are still a couple of scenarios which might caught you by surprise. Lets start with casts and with a simple example:
The previous class won’t compile because there’s no way for the compiler to know that it’s possible to convert something into a String. At least, not when it’s compiling the generic open class! Notice that you can’t even apply a constraint to solve this problem. There is, however, a workaround (which isn’t really type safe):
Since any object can be cast to Object, then the compiler won’t complain about that first cast. The other cast (String) is now possible, though it might throw an exception at runtime. In fact, there’s a better option of doing the previous cast:
Notice, however, that you’ll only be able to do this whenever you’re converting something into a reference type.
Since generic type arguments can be replaced by any concrete type, then you might be wondering how can you initialize a “generic” variable to its default value. The problem here is specifying its default value. I mean, if you’re talking about reference types, then it’s ok to set it to null. However, doing it with a value type results in a compile type error. To solve this problem, Microsoft introduced the default keyword:
Now, the compiler is happy. When T is replaced by a reference type, aux is initialized with null. On the other hand, if T is replaced by a value type, then aux reference some memory space (big enough to save a value of that type) with all its bits initialized to 0. Btw, there’s also an interesting gotcha regarding the use of null: you can’t use it to initialize a variable, but you can use it with the operators == and !=. Here’s and example:
When T is replaced by a value type, the JIT compiler won’t emit the native code for the if part because a value type never is null.
Notice that if we had constrained T to a struct, then yes, we’d be getting a compile error on the if clause.
It’s also important to understand that comparing two variables of the same generic type results in an error if that generic type isn’t constrained to a reference type:
If T were constrained to a reference type, the code would have compiled without any errors. However, constraining T to a a value type, will always result in an error. Since you cannot constrain T to a specific value type (a value type is always sealed and we’ve seen that we can’t do that because it’s more efficient to write code that uses directly that type), you’re limited to saying that it’s a value type. And this information isn’t enough to make the compiler do its work and emit the correct code for the comparison.
A final note: you can’t use generic type variables as operands (ie, you can’t use them with the operators +, –, *, etc.), making it impossible to write expressions which work for any numeric type. This is a tremendous problem, especially for those guys that work in the financial world…
And that’s it for now! Stay tuned for more.