6

(制限付きの)ワイルドカードジェネリックを使用するJavaコードをC#に変換しようとして立ち往生しています。私の問題は、Javaでは、ワイルドカードを使用すると、ジェネリック型を共変と反変の両方にできるように見えることです。

[これは、制限付きワイルドカードのより単純なケースを扱った前の質問からのスピンオフです]

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>'.
4

2 に答える 2

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

于 2013-01-12T13:28:41.643 に答える
2

Javaでは、型をバリアントと共変の両方にすることはできません。あなたが持っているのはIGeneric1<?> elem、クラスGeneric2で宣言している間、そのメソッドを使用しないという事実から生じる幻想T method1WithParam(T val);です。したがって、Javaはこの宣言に問題はありません。ただし、を介して使用しようとするとすぐにエラーのフラグが立てられますelem

これを説明するために、以下は関数を呼び出そうとする関数test()をクラスに追加しますが、これはコンパイラエラーにつながります。不快な行はコメントアウトされているため、エラーを再現するには、再インストールする必要があります。Generic2elem.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();

次に、 Javaワイルドカードジェネリック<?>に相当する参照.NETを、共分散と逆分散で調べました。しかし、なぜこれが解決策と見なされるのか理解できないことを認めなければなりません。<T extends Impl>無制限のワイルドカードパラメータ化タイプ(<?>)や分散( )などのタイプ制限<? extends Impl>は、秒を最初に置き換えることが一般的な解決策としてどのように見えるかわかりません。ただし、場合によっては、ワイルドカードのパラメーター化されたタイプ(<?>)または分散タイプをyesよりも実際に使用する必要がない場合は、この変換を行うことができます。ただし、Javaコードで実際に使用しない場合は、これも修正する必要があります。

Javaジェネリックを使用すると、多くの不正確さを導入できますが、C#コンパイラではその可能性はありません。これは、C#ではクラスと構造体が完全に再構築可能であり、したがって分散(共変性と反変性の両方)をサポートしていないことを考えると特に当てはまります。これは、インターフェースの宣言とデリゲートにのみ使用できます。もし私が正確に覚えていれば。

最後に、ポリモーフィズムが関係している場合、不要なジェネリック型を使用する傾向が悪いことがよくあります。ワイルドカードのパラメーター化されたタイプと分散の有無にかかわらず。これは多くの場合、長くて複雑なコードにつながります。読みにくく、使いにくく、書きにくい。このすべてのJavaコードを調べて、ポリモーフィズムのみ、またはポリモーフィズムとジェネリックの組み合わせで、分散やワイルドカードのパラメーター化されたタイプを使用しない、はるかに単純なコードではなく、これらすべてのものが本当に必要かどうかを確認することを強くお勧めします。

于 2013-01-12T12:34:12.797 に答える