2013-01-12 6 views
6

(有界の)ワイルドカードジェネリックを使用するJavaコードをC#に変換しようとしています。私の問題は、Javaは、ワイルドカードと一緒に使用する場合、ジェネリック型を共変と反変にすることができるようです。Javaに拘束されたワイルドカード(IInterf <?>)に対応する.NET?

[この前の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(); 
} 

からに変更した場合上記のエラーが表示されなくなりますが、分散についてmethod1WithParam(T)文句:

Parameter must be input-safe. Invalid variance: The type parameter 'T' must be 
contravariantly valid on 'IGeneric1<out T>'. 
+0

私はJava genericsについてよく分かりません。しかし、そのJavaコードは型安全ですか? – Euphoric

+1

Javaコードの呼び出し方法を教えてください。私はまだ誰かがそのような怪物を作るのか理解するのが難しいと思う。 – Euphoric

+0

C#の分散制約は、単純化のために意図的により限定的であることに注意してください。あなたがJavaコードで表現しているものは、C#で単純に同等のものを持っていない可能性があります。 – millimoose

答えて

3

まず、デザインレビューが順調に進んでいると言い始めることにしましょう。元のJavaクラスはIGeneric1<?>メンバを集約しますが、その型引数を知らなければ、method1WithParamをタイプセーフな方法で呼び出すことはできません。

これは、elemmethod1のメンバーを呼び出すためにのみ使用できることを意味します。その署名は、タイプパラメータ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コードと同じ制限です。 method1WithParam1を呼び出そうとするとJavaが不平を言うのに対して、C#コンパイラはこのコードを受け入れないということだけです。

+0

私の疑惑を確認していただきありがとうございます。私はその分野でジェネリックメソッドをどのように呼び出すことができるかを具体的に質問する質問を書いた。私は何かが不足していると思った:http://stackoverflow.com/q/14295032/11545。 Javaコードベースの犯人インターフェイスは公開されているので、C#で1対1の対応関係が見つからない場合は、いくつかの外部ユースケースを破る可能性があると私は考えました。そのような可能なユースケースはないことが分かります。 –

2

Javaは型がバリアントと共変の両方にすることはできません。あなたが持っているのは、Generic2IGeneric1<?> elemを宣言している間に、そのメソッドT method1WithParam(T val);を使用していないという事実に由来する錯覚です。したがって、Javaではこの宣言に問題はありません。ただし、elemまで使用しようとするとすぐにエラーになります。

これを説明するには、test()elem.method1WithParam()関数を呼び出そうとするGeneric2クラスに追加しますが、コンパイラエラーが発生します。攻撃ラインは、コメントアウトされているので、あなたは、エラーを再現するために、それを再インストールする必要があります。

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<out T extends Impl>でバリアントになるようにインターフェイスIGeneric1<T extends Impl>を宣言しようとすると、 T method1WithoutParam();

第2に、私は参考文献.NET equivalent for Java wildcard generics <?> with co- and contra- variance?を見ましたが、なぜこれが解決策とみなされるのか分かりません。 <T extends Impl>などの型の制限は、無制限のワイルドカードパラメータ化タイプ(<?>)または分散(<? extends Impl>)とは関係がありません。最初の秒と置き換える方法が一般的な解決策であるとは思われません。しかし、場合によっては、ワイルドカードパラメータ化タイプ(<?>)または分散タイプをyesよりも使用する必要がない場合は、この変換を行うことができます。ただし、Javaコードで実際に使用しない場合は、このコードも修正する必要があります。

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

最後に、多型性が関与する場合、不要なジェネリック型を使用する傾向がよくありません。ワイルドカードでパラメータ化された型と分散を使用するかどうかを指定します。これはしばしば長く複雑なコードにつながります。読んで使用するのが難しく、書くのがさらに難しい。このJavaコードをすべて見て、ポリモーフィズムだけのコードや、多形性と汎用性はありますが、分散やワイルドカードのパラメータ化されていないタイプのコードの代わりに、このようなものをすべて用意することを強くお勧めします。

+0

うん、私もそれを試みた - しかし、私が欠けていた一般的な方法を使用する巧妙な方法があるかもしれないと思った。私はhttp://stackoverflow.com/q/14295032/11545でそれについて尋ねたが、私はあなたの答えとジョンの明確なことを推測する。 –

+0

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

関連する問題