18

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?

skaffman
  • 398,947
  • 96
  • 818
  • 769
ehnmark
  • 2,656
  • 3
  • 24
  • 20
  • 5
    I don't understand the question; C# has bounded generics and has since C# 2. Is what you're getting at that Java supports *call-site* covariance and contravariance on constructed generic types? Because C# doesn't support that; C# 4 will support *declaration-site* variance. – Eric Lippert Jan 03 '10 at 01:29
  • 6
    Or is what you're getting at the fact that type parameter bounds on generic methods are only must-derive-from bounds, and not must-be-derived-from bounds? C# does not support the latter; Java and Scala do I believe. – Eric Lippert Jan 03 '10 at 01:30

3 Answers3

25

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
  • 647,829
  • 179
  • 1,238
  • 2,067
  • 2
    Thanks for the follow-up. The book Effective Java explains a scenario where this would be useful: say you define a type `MyStack` with a method `PopAll` that takes a destination collection as parameter. If you have a `MyStack` you should be able to copy its items to a `List`, but to express that you'd have to say something like public void `PopAll(ICollection dst) where T : X` and that seems to be illegal indeed. – ehnmark Jan 04 '10 at 14:36
  • 8
    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! That introduces a new type parameter which can then be constrained. – Eric Lippert Jan 04 '10 at 15:03
  • Another example I could see would be covariance of `IImmutableList` in `System.Collections.Immutable`; currently, `IImmutableList Add(T item)` prevents that, but an `IImmutableList Add(TNew item) where TNew : T` would save it. (Well, so would not implementing the mutable interface on the immutable type to begin with, but I suppose they had reasons to go with that design.) Here, extension methods wouldn't get you as far. – fuglede Oct 01 '17 at 19:57
8

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

There are other SO posts that talk about this in more detail: 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.

Community
  • 1
  • 1
Eilon
  • 25,582
  • 3
  • 84
  • 102
7

.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
  • 28,321
  • 10
  • 54
  • 90
  • 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 Jan 03 '10 at 07:35
  • 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 Jan 03 '10 at 11:15
  • 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 Jan 03 '10 at 11:56
  • ehnmark: C# implemented generics before Java did. One factor in the decision could be that .NET implements generics in both the C# language and the CLR, whereas Java implements generics in the Java language but not in the JVM. Not having to implement any support in the JVM might have made it more straightforward to add this feature. – Nate C-K Jun 12 '12 at 20:04