Javaバウンドワイルドカード(IInterf <?>)に相当する.NET?

.NET equivalent for Java bounded wildcard (IInterf<?>)?


質問 written by Community @2017-05-23 12:12:34Z

: 6 : 2 : 540

(バインドされた)ワイルドカードジェネリックを使用する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)ますが、 method1WithParam(T)が分散について不平を言っています。

Parameter must be input-safe. Invalid variance: The type parameter 'T' must be
contravariantly valid on 'IGeneric1<out T>'.
コメント 1

Javaジェネリックはあまり知りません。しかし、そのJavaコードはタイプセーフですか?

written by 陶酔 @2013-01-12 08:06:27Z

コメント 2

Javaコードをどのように呼び出すか、または呼び出す必要があるかを教えてください。なぜ誰かがそのような怪物をするのか理解するのは難しいと思う。

written by 陶酔 @2013-01-12 08:15:07Z

コメント 3

C#の分散制約は、単純化のために意図的に制限されていることに注意してください。Javaコードで表現しているものが、C#で単純な同等物を持たないことは完全に可能です。

written by ミリムース @2013-01-12 16:08:40Z

コメント 4

@millimooseそれでは、C#のYayが足で自分を撃つのを難しくしてくれたと思います:)(この機会に私の人生は楽になりませんでしたが)-Cristi Diaconescu

written by @2013-01-12 16:11:56Z

コメント 5

@Cristi:翻訳/元のコードの理解も間違っています。Java Generic2.method2()では、 IGeneric1<?>から別のIGeneric1<?>割り当てています。Javaでは、 IGeneric1 1つの未知のインスタンス化から、その汎用インターフェースの別の未知の潜在的に異なるインスタンス化までを意味します。(たとえば、 elemIGeneric1<Number>あり、 valは `IGeneric <String>である可能性があります。コードの残りの部分はどちらを気にしないので問題ありません。)

written by millimoose @2013-01-12 16:14:00Z

回答 1 written by Jon @2013-01-12 13:34:45Z
3

まず、デザインレビューが適切に行われているように見え始めていると言ってみましょう。 元のJavaクラスはIGeneric1<?>メンバを集約しますが、そのタイプ引数を知らないと、タイプセーフな方法でmethod1WithParamを呼び出すことはmethod1WithParamません。

つまり、 elemmethod1メンバーを呼び出すためにのみ使用でき、その署名はIGeneric1 typeパラメーターに依存しません。 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メンバーを集約できます。

After this, class Generic2 can aggregate an INotGeneric1 member instead:

もちろん、キャストやリフレクションにelem.method1WithParamない限り、 elem.method1WithParam呼び出すことはできません。そのようなメソッドが存在し、未知のタイプXをタイプ引数として持つことがわかっている場合でも。 ただし、これはJavaコードの制限と同じです。 Javaがmethod1WithParam1を呼び出そうとした場合にのみ文句を言うのに対して、C#コンパイラはこのコードを受け入れないだけです。

コメント 1

私の疑いを確認してくれてありがとう。そのフィールドで汎用メソッドをどのように呼び出すことができるかを具体的に尋ねる質問を書きました。stackoverflow.com/q/14295032/11545という何かが足りないと思った。Javaコードベースの犯人インターフェイスは公開されているため、C#で1:1の対応関係が見つからない場合、外部のユースケースを壊すかもしれないと考えました。そのような可能なユースケースはありません。

written by クリスティディアコネスク @2013-01-12 15:57:02Z

回答 2 written by Community @2017-05-23 11:43:08Z
2

Javaでは、型をバリアントと共変の両方にすることはできません。 あなたが持っているのは、クラスGeneric2IGeneric1<?> elem宣言している間、そのメソッドT method1WithParam(T val);使用しないという事実に起因する幻想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<T extends Impl>IGeneric1<out T extends Impl>バリアントとして宣言しようとすると、 T method1WithoutParam();コンパイルエラーが自動的に表示されT method1WithoutParam();

第二に、 Javaワイルドカードジェネリック<?>の等価。 しかし、なぜこれが解決策と見なされるのか理解できないことを認めなければなりません。 <T extends Impl>などの型制限は、無制限のワイルドカードパラメーター化型( <?> )または分散( <? extends Impl> )とは関係がなく、秒を最初のものに置き換える方法がわかりません一般的なソリューション。 ただし、場合によっては、ワイルドカードパラメーター化タイプ( <?> )またはyesよりも分散タイプを実際に使用する必要がない場合、この変換を行うことができます。 ただし、Javaコードで実際に使用しない場合は、これも修正する必要があります。

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

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

コメント 1

うん、私もそれを試してみました-ジェネリックメソッドを使用する賢い方法があるかもしれないと思った、私は行方不明だった。私はstackoverflow.com/q/14295032/11545でそれについて尋ねましたが、私はあなたの答えを推測し、ジョンはそれを明確にしています。

written by クリスティディアコネスク @2013-01-12 16:02:45Z

コメント 2

最後の段落について-私は完全に同意します。私はコードが何をし、それを単純化する方法を理解しようとして(この例よりもはるかに絡み合っている)、問題のある部分を他の誰かに説明し、プロセス。OTOH、私は過去に同様の合併症を有罪にしたことを認めなければなりません。滑りやすい斜面です。

written by クリスティディアコネスク @2013-01-12 16:09:58Z