我一直试图将一些使用(有界)通配符泛型的Java代码转换为C#。我的问题是,Java与通配符一起使用时似乎允许泛型既协变又是协变。
[这是从以前的question衍生出来的,它处理了一个简单的有界通配符案例]
Java -工作原理:
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#-无法编译...
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://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();
}
如果我将
interface IGeneric1<T>
更改为interface IGeneric1<out T>
,则上述错误消失了,但是method1WithParam(T)
提示差异:Parameter must be input-safe. Invalid variance: The type parameter 'T' must be
contravariantly valid on 'IGeneric1<out T>'.
最佳答案
首先,我要说的是,设计审查肯定已经开始。原始的Java类聚集了一个IGeneric1<?>
成员,但是在不知道其类型参数的情况下,不可能以类型安全的方式对其进行调用method1WithParam
。
这意味着elem
仅可用于调用其method1
成员,该成员的签名不依赖于IGeneric1
的类型参数。因此,可以将method1
分解为非通用接口(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);
}
之后,
class Generic2
可以聚合一个INotGeneric1
成员: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
}
}
当然,现在您不能调用
elem.method1WithParam
,除非您诉诸强制转换或反射,即使已知存在这样的方法并且该方法是通用的,并且带有一些未知的X
类型作为类型参数。但是,这与Java代码具有相同的限制。只是C#编译器不会接受此代码,而Java仅在您尝试调用method1WithParam1
时才会提示。关于java - .NET是否等效于Java有界通配符(IInterf <?>)?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/14289518/