2013-01-12 50 views
6

我一直在試圖將一些使用(有界)通配符泛型的Java代碼轉換爲C#。我的問題是,Java似乎允許通用類型與通配符一起使用時是協變和逆變。.NET等價於Java有界通配符(IInterf <?>)?

[這是從以前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>'. 
+0

我不太瞭解Java泛型。但是,Java代碼是否是類型安全的? – Euphoric

+1

您能否提供該Java代碼將如何或應該被調用?我仍然很難理解爲什麼有人會出現這樣的怪物。 – Euphoric

+0

請注意,爲簡單起見,C#的方差約束故意更加嚴格。在Java代碼中表達的人完全有可能在C#中沒有簡單的等價物。 – millimoose

回答

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時發出抱怨。

+0

感謝您確認我的懷疑。我特意寫了一個問題,問我如何可能在該領域調用通用方法;我以爲我錯過了一些東西:http://stackoverflow.com/q/14295032/11545。 Java代碼庫中的罪魁禍首是公共的,因此我認爲如果我在C#中找不到1:1的對應關係,我可能會破壞一些可能的外部用例。原來沒有這種可能的用例。 –

2

Java不允許一個類型既變型和協變。你所擁有的是一種錯覺,因爲你在類別Generic2中聲明IGeneric1<?> elem,而不使用它的方法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>)無關,我不知道如何將第一個秒替換爲通用解決方案。但是,在某些情況下,如果您不需要使用通配符參數化類型(<?>)或差異類型比yes,則可以進行此轉換。但是,如果您沒有在Java代碼中真正使用它們,那麼也應該更正此問題。

對於Java泛型,您可能會引入大量的不精確性,但您不會在C#編譯器中獲得這種機會。考慮到在C#中,類和結構是完全可驗證的,因此不支持方差(協方差和反變量),這一點尤其真實。你只能用它來聲明一個接口和代表;如果我沒記錯的話。最後,當涉及多態時,使用不必要的泛型類型往往是不好的趨勢;有或沒有通配符參數化類型和方差。這通常會導致一個漫長而複雜的代碼;很難閱讀和使用,甚至更難寫。我強烈建議你看看所有這些Java代碼,看看是否真的有必要擁有所有這些東西,而不是僅使用多態的簡單代碼,或者將多態與泛型結合使用,但沒有差異或通配符參數化類型。

+0

是的,我也試過 - 但認爲可能有一種聰明的方式使用通用方法,我錯過了。我在http://stackoverflow.com/q/14295032/11545上詢問了這個問題,但我想你的回答和Jon明白了。 –

+0

關於你的最後一段 - 我完全同意。我花了太多時間試圖弄清楚代碼的作用以及如何簡化代碼(它比這個例子更加糾結),以至於我可以向其他人解釋有問題的部分,並且自己琢磨一下該過程。 OTOH,我必須承認過去曾犯過類似的複雜情況;這是一個滑坡。 –