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.
[This is a spin-off from a previous question dealing with a simpler case of bounded-wildcards]
Java - works:
class Impl { }
interface IGeneric1<T extends Impl> {
void method1(IGeneric2<?> val);
T method1WithParam(T val);
}
interface IGeneric2<T extends Impl> {
void method2(IGeneric1<?> val);
}
abstract class Generic2<T extends Impl> implements IGeneric2<T> {
// !! field using wildcard
protected IGeneric1<?> elem;
public void method2(IGeneric1<?> val1) {
val1.method1(this);
//assignment from wildcard to wildcard
elem = val1;
}
}
abstract class Generic<T extends Impl> implements IGeneric1<T>, IGeneric2<T> {
public void method1(IGeneric2<?> val2) {
val2.method2(this);
}
}
C# - doesn't compile...
class Impl { }
interface IGeneric1<T> where T:Impl {
//in Java:
//void method1(IGeneric2<?> val);
void method1<U>(IGeneric2<U> val) where U : Impl; //see this Q for 'why'
// https://mcmap.net/q/826449/-net-equivalent-for-java-wildcard-generics-lt-gt-with-co-and-contra-variance
T method1WithParam(T to);
}
interface IGeneric2<T>where T:Impl {
void method2<U>(IGeneric1<U> val) where U : Impl;
}
abstract class Generic2<T, TU>: IGeneric2<T> //added new type TU
where T : Impl
where TU : Impl
{
//in Java:
//protected IGeneric1<?> elem;
protected IGeneric1<TU> elem;
//in Java:
//public void method2(IGeneric1<?> val1)
public void method2<U>(IGeneric1<U> val)
where U : TU //using TU as constraint
{
elem = val; //Cannot convert source type 'IGeneric1<U>'
//to target type 'IGeneric1<TU>'
}
public abstract void method1WithParam(T to);
}
abstract class Generic<T> : IGeneric1<T>, IGeneric2<T> where T : Impl
{
//in Java:
//public void method1(IGeneric2<?> val2)
public void method1<U>(IGeneric2<U> val2) where U : Impl
{
val2.method2(this);
}
public abstract T method1WithParam(T to);
public abstract void method2<U>(IGeneric1<U> val) where U : Impl;
public abstract void nonGenericMethod();
}
If I change interface IGeneric1<T>
to interface IGeneric1<out T>
the above error goes away, but method1WithParam(T)
complains about variance:
Parameter must be input-safe. Invalid variance: The type parameter 'T' must be
contravariantly valid on 'IGeneric1<out T>'.
Generic2.method2()
, you're assigning from anIGeneric1<?>
to anotherIGeneric1<?>
. In Java this means from one unknown instantiation ofIGeneric1
to another unknown, potentially different instantiation of that generic interface. (E.g.elem
can be aIGeneric1<Number>
andval
can be an `IGeneric<String>, and this is okay because the rest of your code doesn't care about which one it is.) – ForebrainIGeneric<>
. This is a completely different constraint, and my guess is this is the root problem of the compiler error. Now, your assessment is right there, because C# indeed does not allow you to "not care" about which instantiation of a generic type you're using. In C#, those instantiations are first-class types in their own right. So, to assign fromIGeneric<int>
toIGeneric<string>
would require a (failing) cast. – Forebrain