6

I'm stuck trying to translate some Java code that uses (bounded) wildcard generics to C#. My problem is, Java seems to allow a generic type to be both covariant and contravariant when used with a wildcard. For instance:

Java:

interface IInterf { }

class Impl implements IInterf { }

interface IGeneric1<T extends Impl> {
    void method1(IGeneric2<?> val);
    void method1WithParam(T val);
}

interface IGeneric2<T extends Impl> {
    void method2(IGeneric1<?> val);
}

abstract class Generic<T extends Impl> implements IGeneric1<T>, IGeneric2<T> {
    public void method1(IGeneric2<?> val2) {
        val2.method2(this);
    }
}

...works.

C# equivalent (?)

interface IInterf { }

class Impl : IInterf { }

interface IGeneric1<T> where T:Impl {
  //Java was: 
  //void method1(IGeneric2<?> val2);
    void method1(IGeneric2<Impl> val);
    void method1WithParam(T to);
}

interface IGeneric2<T>where T:Impl {
    void method2(IGeneric1<Impl> val);
}

abstract class Generic<T> : IGeneric1<T>, IGeneric2<T> where T : Impl
{
  //Java was: 
  //public void method1(IGeneric2<?> val2) {
    public void method1(IGeneric2<Impl> val2)
    {
         val2.method2(this); //'this': Argument type 'Generic<T>' is not 
                             //assignable to parameter type 'IGeneric1<Impl>'
    }

    public abstract void method1WithParam(T to);
    public abstract void method2(IGeneric1<Impl> val);
}

...fails to compile - see the error in the comment. Which is to be expected, since IGeneric's generic parameter is not marked 'out' for covariance.

If I change this:

interface IGeneric1<T> where T:Impl {

to this

interface IGeneric1<out T> where T:Impl 

the error goes away, but another one appears, for the declaration of the method that takes a generic parameter inside the same interface:

interface IGeneric1<T> where T:Impl {
    void method1WithParam(T val);  //Parameter must be input-safe. 
                      //Invalid variance: The type parameter 'T' must be
                      //contravariantly valid on 'IGeneric1<out T>'.

Suggestions?

[Also see the follow-up question for a somewhat harder scenario]

Community
  • 1
  • 1
Cristian Diaconescu
  • 34,633
  • 32
  • 143
  • 233

1 Answers1

7

You need to translate the Java wildcard generic methods to C# methods that are generic in their own right. For example, this:

interface IGeneric2<T extends Impl> {
    void method2(IGeneric1<?> val);
}

should be translated to

interface IGeneric2<T>where T:Impl {
    void method2<U>(IGeneric1<U> val) where U:Impl;
}

It is necessary to repeat the type constraint for T specified by IGeneric1<T> as the type constraint for U.

The reason for this is that in the Java version there are implicit constraints for the type arguments of the parameters of method1 and method2: if the parameter must be some kind of IGeneric1<X> then X must obviously be an Impl because otherwise it could not possibly implement IGeneric1 for that type.

In C# the constraints must be explicit, so you repeat what IGeneric1<T> and IGeneric2<T> require of T.

So the equivalent code would be:

interface IInterf { }

class Impl : IInterf { }

interface IGeneric1<T> where T:Impl {
    void method1<U>(IGeneric2<U> val) where U:Impl;
    void method1WithParam(T to);
}

interface IGeneric2<T>where T:Impl {
    void method2<U>(IGeneric1<U> val) where U:Impl;
}

abstract class Generic<T> : IGeneric1<T>, IGeneric2<T> where T : Impl
{
    public void method1<U>(IGeneric2<U> val2) where U:Impl
    {
        val2.method2(this);
    }

    public abstract void method1WithParam(T to);
    public abstract void method2<U>(IGeneric1<U> val) where U:Impl;
}
Jon
  • 428,835
  • 81
  • 738
  • 806
  • Is the Java version of `method2` actually bounded? According to the documentation the OP posted, a bounded wildcard declares the constraint inline. There is no such constraint in the Java code of the OP. I assume calling `method2` with an `IGeneric1` would be valid. – Daniel Hilgarth Jan 11 '13 at 11:59
  • @DanielHilgarth: There is an implicit constraint in that `IGeneric1` and `IGeneric2` require that `T` extends `Impl`. `method1` and `method2` say "I 'll take any kind of `IGenericX`", but it is not possible to actually *have* a type implement `IGenericX` for just *any* value of the generic type parameter. Unless I am horribly mistaken, because my Java is sub-par. – Jon Jan 11 '13 at 12:01
  • Of course, you are correct - that's where the constraint comes from. In C# you have to explicitly "repeat" that constraint on the methods, while in Java this is implicit. Thanks. – Daniel Hilgarth Jan 11 '13 at 12:03
  • @DanielHilgarth: It does need the constraint because `IGenericX` requires it. The point is that `T` in `Generic` and `U` in `method1` need not be the same type. – Jon Jan 11 '13 at 12:04
  • I think you answered to an old version of my comment :-) – Daniel Hilgarth Jan 11 '13 at 12:05
  • @DanielHilgarth: You type too fast ;-) – Jon Jan 11 '13 at 12:05
  • Yes, and I thought too slow to keep up with the typing ;-) – Daniel Hilgarth Jan 11 '13 at 12:06
  • @Jon Your suggestion worked great - for most part of the codebase, of which my example was just a small part. But I hit another roadblock. Would you mind taking a look at the updated question? Thx – Cristian Diaconescu Jan 11 '13 at 14:30
  • @CristiDiaconescu: Your update is another question altogether. Please post it as such and reference this one in it. – Daniel Hilgarth Jan 11 '13 at 15:29
  • @Daniel Good point. Question restored to initial state. Here's the new scenario: http://stackoverflow.com/questions/14289518/net-equivalent-for-java-bounded-wildcard-iinterf – Cristian Diaconescu Jan 12 '13 at 02:18