我卡试图转换使用(界)通配符泛型C#一些Java code。我的问题是,Java的似乎允许一个泛型类型既协变和逆变用通配符使用时。
[这是分拆从previous 问题处理有界通配符的简单的情况下, ]
的Java - 作品:
类默认地将Impl {}
接口IGeneric1<吨延伸默认地将Impl> {
无效方法1(IGeneric2<> VAL);
牛逼method1WithParam(T VAL);
}
接口IGeneric2<吨延伸默认地将Impl> {
无效方法2(IGeneric1<> VAL);
}
抽象类Generic2<吨延伸默认地将Impl>实现IGeneric2< T> {
//!使用通配符场
保护IGeneric1<> ELEM;
公共无效方法2(IGeneric1<> VAL1){
val1.method1(本);
//从通配符分配到通配符
ELEM = VAL1;
}
}
抽象类通用<吨延伸默认地将Impl>实现IGeneric1< T&GT ;, IGeneric2< T> {
公共无效方法1(IGeneric2<>将val2){
val2.method2(本);
}
}
C# - 不编译...
类默认地将Impl {}
接口IGeneric1< T>其中T:默认地将Impl {
//在Java中:
//无效方法1(IGeneric2<> VAL);
无效方法1< U>(IGeneric2< U> VAL)其中U:默认地将Impl; //看到这个Q值为什么
// http://stackoverflow.com/a/14277742/11545
牛逼method1WithParam(T到);
}
接口IGeneric2< T>其中T:默认地将Impl {
无效方法2< U>(IGeneric1< U> VAL)其中U:默认地将Impl;
}
抽象类Generic2< T,TU计算值:IGeneric2< T> //添加新型恩
其中T:默认地将Impl
凡恩:默认地将Impl
{
//在Java中:
//保护IGeneric1<> ELEM;
保护IGeneric1< TU> ELEM;
//在Java中:
//公共无效方法2(IGeneric1<> VAL1)
公共无效方法2< U>(IGeneric1< U> VAL)
其中U:恩//使用恩为约束
{
ELEM = VAL; //无法将源类型IGeneric1< U>
//为目标类型'IGeneric1< TU>
}
公共抽象无效method1WithParam(T到);
}
抽象类通用< T> :IGeneric1< T&GT ;, IGeneric2< T>其中T:默认地将Impl
{
//在Java中:
//公共无效方法1(IGeneric2<>将val2)
公共无效方法1< U>(IGeneric2< U>将val2)其中U:默认地将Impl
{
val2.method2(本);
}
公共抽象牛逼method1WithParam(T到);
公共抽象无效方法2< U>(IGeneric1< U> VAL)其中U:默认地将Impl;
公共抽象无效nonGenericMethod();
}
如果我修改接口IGeneric1< T>
到接口IGeneric1<出T>
上面的错误消失,但 method1WithParam(T)
抱怨方差:
参数,必须输入安全的。无效的差异:类型参数'T'必须是
contravariantly有效的IGeneric1<出T>。
解决方案
最后,让我说,肯定是开始看起来像一个设计评审是为了开始。最初的Java类聚合的 IGeneric1<>
成员,但不知道它的类型参数有没有可能叫 method1WithParam
就可以在一个类型安全的方式。
这意味着 ELEM
只能用于调用其方法1
成员,他的签名不依赖于对 IGeneric1
类型参数。由此可见,方法1
可以分解成一个非通用接口:
// C#code:
接口INotGeneric1 {
无效方法1< T>(IGeneric2< T> VAL)其中T:默认地将Impl;
}
接口IGeneric1< T> :INotGeneric1其中T:默认地将Impl {
牛逼method1WithParam(T到);
}
在此,类Generic2
可以聚合的 INotGeneric1
成员,而不是:
抽象类Generic2< T>:IGeneric2< T>其中T:默认地将Impl
{
保护INotGeneric1 ELEM;
//这是非常可能的,你想改变val的类型
//为INotGeneric1为好,没有明显的理由需要
// IGeneric1< U>
公共无效方法2< U>(IGeneric1< U> VAL)其中U:默认地将Impl
{
ELEM = VAL; //这是现在确定
}
}
当然,现在你不能叫 elem.method1WithParam
,除非你采取强制转换或反射,即使它知道这样的方法存在,它是通用的一些未知键入 X
作为类型参数。然而,这是同样的限制性作为Java code的;这只是C#编译器不会接受这个code,而Java只会抱怨,如果你试图调用 method1WithParam1
。
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'
// http://stackoverflow.com/a/14277742/11545
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>'.
解决方案
Let me start by saying that is definitely starting to look like a design review is in order. The original Java class aggregates an IGeneric1<?>
member, but without knowing its type argument there's no possibility to call method1WithParam
on it in a type-safe manner.
This means that elem
can be used only to call its method1
member, whose signature does not depend on the type parameter of IGeneric1
. It follows that method1
can be broken out into a non-generic interface:
// C# code:
interface INotGeneric1 {
void method1<T>(IGeneric2<T> val) where T : Impl;
}
interface IGeneric1<T> : INotGeneric1 where T : Impl {
T method1WithParam(T to);
}
After this, class Generic2
can aggregate an INotGeneric1
member instead:
abstract class Generic2<T>: IGeneric2<T> where T : Impl
{
protected INotGeneric1 elem;
// It's highly likely that you would want to change the type of val
// to INotGeneric1 as well, there's no obvious reason to require an
// IGeneric1<U>
public void method2<U>(IGeneric1<U> val) where U : Impl
{
elem = val; // this is now OK
}
}
Of course now you cannot call elem.method1WithParam
unless you resort to casts or reflection, even though it is known that such a method exists and it is generic with some unknown type X
as a type argument. However, that is the same restriction as the Java code has; it's just that the C# compiler will not accept this code while Java will only complain if you do try to call method1WithParam1
.