.NET等价的Java界通配符(IInterf<>)?通配符、Java、NET、GT

2023-09-04 01:58:23 作者:美汉子

我卡试图转换使用(界)通配符泛型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笔录 第38篇练习学生管理系统升级版

解决方案

最后,让我说,肯定是开始看起来像一个设计评审是为了开始。最初的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.