Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.NET equivalent for Java wildcard generics <?> with co- and contra- variance?

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]

like image 614
Cristian Diaconescu Avatar asked Jan 11 '13 11:01

Cristian Diaconescu


1 Answers

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;
}
like image 185
Jon Avatar answered Oct 13 '22 20:10

Jon