.NET等效于Java有界通配符(IInterf <! - ? - >)?

时间:2013-01-12 02:16:41

标签: java .net generics covariance bounded-wildcard

我一直试图将一些使用(有界)通配符泛型的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>'.

2 个答案:

答案 0 :(得分:3)

首先我要说的是,设计审查肯定会开始变得井井有条。原始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);
}

在此之后,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时抱怨。

答案 1 :(得分:2)

Java不允许类型变量和协变。你所拥有的是一种幻觉,因为当你在课堂IGeneric1<?> elem中宣布Generic2时,你不会使用它的方法T method1WithParam(T val);;因此Java认为此声明没有任何问题。但是,只要您尝试通过elem使用它就会标记错误。

为了说明这一点,下面向test()类添加一个函数Generic2,该函数将尝试调用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!
    }
}

来自Java编译器的这个错误证明我们不能将泛型类型用作协变和逆变这两个;即使某些声明似乎证明相反。使用C#编译器,在获得编译错误之前,您甚至没有机会接近它:如果您尝试将接口IGeneric1<T extends Impl>声明为IGeneric1<out T extends Impl>的变体;您自动收到T method1WithoutParam();

的编译错误

其次,我看了参考.NET equivalent for Java wildcard generics <?> with co- and contra- variance?,但我必须承认,我不明白为什么这可以被视为一种解决方案。类型限制(例如<T extends Impl>)与无界通配符参数化类型(<?>)或方差(<? extends Impl>)无关,我看不到如何用第一个替换秒数作为一般解决方案。但是,在某些情况下,如果您不需要使用通配符参数化类型(<?>)或方差类型而不是,则可以进行此转换。但是,如果您在Java代码中没有真正使用它们,那么也应该更正此代码。

使用Java泛型,你可以引入很多不精确但你不会用C#编译器获得这个机会。考虑到在C#中,类和结构是完全可恢复的,因此不支持方差(协方差和逆变),这尤其正确。您只能将其用于声明接口和委托;如果我没记错的话。

最后,当涉及多态性时,通常存在使用不必要的泛型类型的不良倾向;有或没有通配符参数化类型和方差。这通常会导致代码冗长而复杂;难以阅读和使用,甚至更难写。我强烈建议你查看所有这些Java代码,看看是否真的有必要拥有所有这些东西而不是更简单的代码,只有多态或多态与通用但没有方差或通配符参数化类型的组合。