I really do like the “Whidbey” generics for both C# and VB.net. In my opinion, they bring a good solution to some problems with gaps in strong typing to the .net family. Great stuff, really.
However, I'm not happy with one of the constraints on constraints. I'll endevor to explain. Hopefully this is either addressed or (at the least) acknowledged by those in the know. (Or, if I'm an idiot, feel free to let me know in comments)
For a good background on C# generics, see this article on MSDN: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnvs05/html/csharp_generics.asp
Okay, so let's say I have a generic collection that sorts items:
1public class SortEx<T>
2{
3 ...
4 public void Add(T value)
5 {
6 foreach( T item in m_collection)
7 {
8 if( value < item) { ... }
9 }
10 ...
11 }
12 ...
13}
This won't work. Why? well, because the compiler doesn't know if the line “(value < item)” is valid!. That is, is the “<” operator valid on any and every type that currently exists or will exist? No - so the compiler won't allow you to write this line.
To get around this issue, we use the nifty “constraints” feature:
1public class SortEx<T> where T:IComparable
2{
3 ...
4 public void Add(T value)
5 {
6 foreach( T item in m_collection)
7 {
8 if( value.compareTo(item) < 0)
9 { //... }
10 }
11 ...
12 }
13 ...
14
Note that since we've added “where T:IComparable”, only types that implement IComparable can be used to construct an instance of this generic. So the line “value.compareTo(item)” will be valid for any type that does exist or ever will exist that can be used as the type parameter in this generic class. Very handy.
Now we come to my issue. Note that to allow a comparison (normally done using the type's “<” operator), we had to add a constraint and turn it into a method call on an interface. Why couldn't we say “where T: operator<”? Answer - not supported, and that's the problem.
How is this a problem? After all, we worked around it! Well, imagine that we want to create a generic that will give us a running total of items added to it. That is, we call totaler.Add(value) for every value we come across, and at any time we can look at the property totaler.Total to get the current running total...
1public class RunTotal<T>
2{
3 private T m_total;
4 public T Total
5 {
6 get{ return m_total; }
7 }
8
9 public void Add(T value)
10 {
11 total += value;
12 }
13}
Okay, again this won't work. The compiler can't ensure that the operator “+” is valid for every type that does exist or will exist. However, in this case, there's nothing we can do about it. There's no way to add a constraint for the operator. There's no interface that will be supported by all addable types in the runtime. There's just nothing.
Now, we could have a big case statement and check if the type is an int, or a string, or a double, or whatever - then cast and add. But that would limit us to known types and not future types that are addable. Really, our hands are tied here and all we want to do is add. Ouch. Back to using Object, boxing, explicit casts and runtime errors instead of compile-time.
So, should the .net creators go about adding either an interface for each operation possible? If they do, it must be done now, or it will be worthless in the future (it may already be too late - if you created your own Complex class, for example, did you put this non-existant interface on it for addition?) However, this means adding a interfaces to a host of types in the runtime and FCL
Or, do they allow for constraints on operators, which may be just as complex? Since operator overloading is done with the specialname attribute and actually handled by compilers, this may just not be possible or at least very difficult.
Either way, I hope this is being addressed somehow. I admit it may be enough of an edge case that it's not important enough to fix, but it seems to me to be a shortcoming in generics that must either be dismissed as acceptable, or fixed right now. Once generics are out there for real, making fundimental changes is going to be that much harder.