.NET等价于Java有界通配符(IInterf<;?>;)?
我一直在尝试将一些使用(有界)通配符泛型的Java代码翻译成C。我的问题是,Java似乎允许泛型类型在与通配符一起使用时既可以是协变的,也可以是逆变的 [这是以前处理有界通配符的简单情况的衍生产品] Java-工作原理:.NET等价于Java有界通配符(IInterf<;?>;)?,java,.net,generics,covariance,bounded-wildcard,Java,.net,Generics,Covariance,Bounded Wildcard,我一直在尝试将一些使用(有界)通配符泛型的Java代码翻译成C。我的问题是,Java似乎允许泛型类型在与通配符一起使用时既可以是协变的,也可以是逆变的 [这是以前处理有界通配符的简单情况的衍生产品] Java-工作原理: class Impl { } interface IGeneric1<T extends Impl> { void method1(IGeneric2<?> val); T method1WithParam(T val); } int
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);
}
}
Java不允许类型同时是变量和协变的。您的错觉源于这样一个事实:当您在类
Generic2
中声明IGeneric1 elem
时,您没有使用其方法t method1WithParam(t val)代码>;因此,Java认为这个声明没有任何问题。但是,当您试图通过elem
使用错误时,它会立即标记错误
为了说明这一点,下面将向Generic2
类添加一个函数test()
,该函数将尝试调用elem.method1WithParam()
函数,但这会导致编译器错误。攻击行已被注释掉,因此您需要重新安装它以重现错误:
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;
}
public void test() {
Impl i = new Impl();
// The following line will generate a compiler error:
// Impl i2 = elem.method1WithParam(i); // Error!
}
}
抽象类Generic2实现IGeneric2{
//!!使用通配符的字段
受保护的IGeneric1元素;
公共作废方法2(IGeneric1 val1){
方法1(本);
//从通配符到通配符的赋值
elem=val1;
}
公开无效测试(){
Impl i=新的Impl();
//以下行将生成编译器错误:
//Impl i2=elem.method1WithParam(i);//错误!
}
}
Java编译器的这个错误证明了我们不能将泛型类型同时用作协变和逆变类型,这是错误的;即使某些声明似乎证明了事实相反。使用C#编译器,在出现编译错误之前,您甚至没有机会接近它:如果您试图将接口IGeneric1
声明为与IGeneric1
的变体;您会自动获得T method1WithoutParam()的编译错误代码>
第二,我看了参考资料,但我必须承认,我不明白为什么这可以被视为一个解决方案。诸如
之类的类型限制与无界通配符参数化类型(
)或差异(
)或差异类型无关。如果不是“是”,则可以进行此转换。然而,如果您没有在Java代码中真正使用它们,那么也应该纠正这一点
使用Java泛型,可以引入很多不精确性,但使用C#编译器就不会有这样的机会。考虑到在C#中,类和结构是完全可重新定义的,因此不支持方差(协方差和逆变方差),这一点尤其正确。您只能将其用于接口声明和委托;如果我没记错的话
最后,当涉及多态性时,通常存在使用不必要的泛型类型的不良倾向;有或没有通配符参数化类型和差异。这通常会导致冗长复杂的代码;很难阅读和使用,甚至更难书写。我强烈建议您查看所有这些Java代码,看看是否真的有必要使用这些东西,而不是一个简单得多的代码,只使用多态性,或者将多态性与泛型结合起来,但不使用方差或通配符参数化类型。首先让我说,这肯定开始看起来像一个设计审查已就绪。原始Java类聚合了一个IGeneric1
成员,但如果不知道其类型参数,就不可能以类型安全的方式对其调用method1WithParam
这意味着elem
只能用于调用其method1
成员,其签名不依赖于IGeneric1
的类型参数。因此,method1
可以分解为非通用接口:
// C# code:
interface INotGeneric1 {
void method1<T>(IGeneric2<T> val) where T : Impl;
}
interface IGeneric1<T> : INotGeneric1 where T : Impl {
T method1WithParam(T to);
}
当然,现在您不能使用param调用elem.method1WithParam
,除非您使用强制转换或反射,即使已知这样一个方法存在,并且它是泛型的,具有一些未知类型X
作为类型参数。然而,这与Java代码具有相同的限制;只是C#编译器不会接受这段代码,而Java只会在您尝试使用param1调用method1WithParam1
时抱怨,我对Java泛型不太了解。但是这种Java代码类型安全吗?您能否提供如何调用或应该如何调用该Java代码?我仍然很难理解为什么会有人做出这样一个怪物。请注意,为了简单起见,C#的方差约束有意地更加严格。你在Java代码中表达的东西很可能没有一个简单的C语言对应物。那么我想,是的,C语言让我更难射中自己的脚:)(尽管这并没有让我的生活变得轻松)@Cristi:你对原始代码的翻译/理解也是错误的。在JavaGeneric2.method2()
中,您将从一个IGeneric1
分配到另一个IGeneric1
。在Java中,这意味着从IGeneric1
的一个未知实例化到该通用接口的另一个未知的、可能不同的实例化。(例如,elem
可以是一个IGeneric1
和val
可以是一个`IGeneric,这没关系,因为您的其余代码不关心它是哪一个。)感谢您确认我的怀疑。我专门写了一个问题,问我如何可能调用该领域的泛型方法;我以为我错过了什么。Java代码库中的罪魁祸首接口是一个公共接口,因此我认为如果在C#中找不到1:1对应关系,我可能会破坏一些可能的外部用例。事实证明,没有这样的可能用例。是的,我也尝试过——但我认为可能存在cl
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;
}
public void test() {
Impl i = new Impl();
// The following line will generate a compiler error:
// Impl i2 = elem.method1WithParam(i); // Error!
}
}
// C# code:
interface INotGeneric1 {
void method1<T>(IGeneric2<T> val) where T : Impl;
}
interface IGeneric1<T> : INotGeneric1 where T : Impl {
T method1WithParam(T to);
}
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
}
}