views:

581

answers:

3

I was reading an interview with Joshua Bloch in Coders at Work, where he lamented the introduction of generics in Java 5. He doesn't like the specific implementation largely because the variance support—Java's wildcards—makes it unnecessarily complex.

As far as I know, C# 3 doesn't have anything like explicit, bounded wildcards, e.g. you can't declare a method PriceBatch that takes a collecton of Asset or any Asset subclass (void PriceBatch(Collection<? extends Asset> assets) in Java?).

Does anyone know why wildcards and bounds haven't been added to C#? Were these features intentionally left out to make the language simpler, or is this something they just haven't gotten around to implement yet?

EDIT: Holy smoke, comments from Eric Lippert himself! After reading his and Paul's insightful comments, I realize that at least upper bounds are supported and that the above example can be translated to C# as:

void PriceBatch<T>(ICollection<T> assets) where T : Asset

Lower bounds, on the other hand, are apparently not supported as Eric says in his second comment, e.g. there is probably no way to directly translate this (somewhat contrived) Java code to C#:

public class Asset {}
public class Derivative extends Asset {}
public class VanillaOption extends Derivative {}

public static <T extends Asset> void copyAssets(Collection<T> src, Collection<? super T> dst) {
    for(T asset : src) dst.add(asset);
}

Collection<VanillaOption> src = new ArrayList<VanillaOption>();
[...]
Collection<Derivative> dst = new ArrayList<Derivative>();
[...]
copyAssets(src, dst);

Am I correct? If this is the case, is there a particular reason why C# has upper but not lower bounds?

+4  A: 

C# 4 introduces new features that allow covariance and contravariance in generics.

There are other SO posts that talk about this in more detail: http://stackoverflow.com/questions/245607/how-is-generic-covariance-contra-variance-implemented-in-c-4-0

The new feature does not enable this automatically in all types, but there is a new syntax that allows developers to specify whether generic arguments are covariant or contravariant.

C# versions prior to C# 4 had limited functionality similar to this as it pertains to delegates and certain array types. With regard to delegates, delegates that take in base parameter types are allowed. With regard to array types, I think it's valid unless there would be boxing involved. That is, an array of Customer can be case to an array of object. However, an array of ints cannot be cast to an array of objects.

Eilon
Arrays, too: String[] is also an Object[].
codekaizen
Updated my answer, thanks.
Eilon
+5  A: 

.net already has the equivalent of wildcards, more logically named generic type constraints, you can do what you describe with no problems

namespace ConsoleApplication3
{
    class Program
    {
        static void Main(string[] args)
        {
         List<a> a = new List<a>();
         List<b> b = new List<b>();
         List<c> c = new List<c>();
         test(a);
         test(b);
         test(c);

        }

        static void test<T>(List<T> a) where T : a
        {
            return;
        }
    }
    class a
    {

    }
    class b : a
    {

    }
    class c : b
    {

    }
}

Example 2

namespace ConsoleApplication3
{
    class Program
    {
        static void Main(string[] args)
        {
            ICollection<VanillaOption> src = new List<VanillaOption>();
        ICollection<Derivative> dst = new List<Derivative>();
         copyAssets(src, dst);
        }

        public static void copyAssets<T,G>(ICollection<T> src, ICollection<G> dst) where T : G {
            foreach(T asset in src) 
                dst.Add(asset);
        }
    }
    public class Asset {}
    public class Derivative : Asset {}
    public class VanillaOption : Derivative {}
}

This example represents a code conversion from your example in java.

I'm not really in a position to answer the actual question though!

Paul Creasey
Hi Paul, a question : the implication of your code example is that the generic constraint expressed in "where T : a" ... by virtue of the fact it will "handle" not only an instance of "a" itself, but, also, any object descended, no matter how "indirectly," from "a" : is equivalent to wildcards in generics in Java ? Just seeking to clarify for my own benefit; there's no criticism of your answer implied or intended. The fact that the letter "a" has three different meanings (class, internal variable, and parameter name) in your example made it harder for me to grok, fyi.
BillW
Yes, it is a bit unclear, apologies. I've written an example of your lower bounded code, functionally it is the same, but it doesn't use lower bounds!
Paul Creasey
Hi Paul and thanks for your updated answer. My question is really why C#3 only has a subset of Java's variance support in this respect and whether it's an intentional decision to do "right" what Java did "wrong", or has to do with CLR limitations or whatnot. It's difficult to find a meaningful example of when lower bounds would be useful, as my contrived example illustrates, and maybe that's part of the answer :)
ehnmark
+6  A: 

A complicated question.

First let's consider your fundamental question, "why is this illegal in C#?"

class C<T> where T : Mammal {} // legal
class D<T> where Giraffe : T {} // illegal

That is, a generic type constraint can say "T must be any reference type that could be assigned to a variable of type Mammal", but not "T must be any reference type, a variable of which could be assigned a Giraffe". Why the difference?

I don't know. That was long before my time on the C# team. The trivial answer is "because the CLR doesn't support it", but the team that designed C# generics was the same team that designed CLR generics, so that's really not much of an explanation.

My guess would be simply that as always, to be supported a feature has to be designed, implemented, tested, documented and shipped to customers; no one ever did any of those things for this feature, and therefore it is not in the language. I don't see a large, compelling benefit to the proposed feature; complicated features with no compelling benefits tend to be cut around here.

However, that's a guess. Next time I happen to chat with the guys who worked on generics -- they live in England, so its not like they're just down the hall from me, unfortunately -- I'll ask.

As for your specific example, I think Paul is correct. You do not need lower bound constraints to make that work in C#. You could say:

void Copy<T, U>(Collection<T> src, Collection<U> dst) where T : U 
{ 
    foreach(T item in src) dst.Add(item);
}

That is, put the constraint on T, not on U.

Eric Lippert
Thanks for the follow-up. The book Effective Java explains a scenario where this would be useful: say you define a type `MyStack<T>` with a method `PopAll` that takes a destination collection as parameter. If you have a `MyStack<Giraffe>` you should be able to copy its items to a `List<Animal>`, but to express that you'd have to say something like public void `PopAll<X>(ICollection<X> dst) where T : X` and that seems to be illegal indeed.
ehnmark
Yep, that would be useful. Interestingly, in C# though you cannot do that directly, you *can* do that by making an extension method on Stack<T>! That introduces a new type parameter which can then be constrained.
Eric Lippert