2013-01-12 26 views
6

Tôi đang cố gắng dịch một số mã Java sử dụng các ký tự đại diện wildcard (bounded) thành C#. Vấn đề của tôi là, Java dường như cho phép một kiểu generic là cả hai biến thể và contravariant khi được sử dụng với một ký tự đại diện..NET tương đương với Java wildcard (IInterf <?>)?

[Đây là một spin-off từ trước question đối phó với một trường hợp đơn giản của giáp-wildcard]

Java - hoạt động:

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# - không biên dịch ...

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(); 
} 

Nếu tôi thay đổi interface IGeneric1<T> thành interface IGeneric1<out T> các lỗi trên sẽ biến mất, nhưng method1WithParam(T) phàn nàn về phương sai:

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

Tôi không biết nhiều về Generics Java. Nhưng mã Java có an toàn không? – Euphoric

+1

Bạn có thể vui lòng cung cấp mã Java sẽ được gọi hay không? Tôi vẫn thấy khó hiểu tại sao một người nào đó lại tạo nên một quái vật như vậy. – Euphoric

+0

Lưu ý rằng các hạn chế về phương sai của C# được cố ý hạn chế hơn vì mục đích đơn giản. Nó hoàn toàn có thể là những gì bạn đang thể hiện trong mã Java đơn giản là không có một tương đương đơn giản trong C#. – millimoose

Trả lời

3

Hãy để tôi bắt đầu bằng cách nói rằng chắc chắn là bắt đầu trông giống như một đánh giá thiết kế là theo thứ tự. Lớp Java ban đầu tập hợp thành viên IGeneric1<?>, nhưng không biết đối số kiểu của nó thì không có khả năng gọi số method1WithParam trên đó theo cách an toàn.

Điều này có nghĩa là elem chỉ có thể được sử dụng để gọi thành viên method1, chữ ký của nó không phụ thuộc vào thông số loại IGeneric1. Nó sau đó method1 có thể được chia ra thành một giao diện không chung chung:

// C# code: 
interface INotGeneric1 { 
    void method1<T>(IGeneric2<T> val) where T : Impl; 
} 

interface IGeneric1<T> : INotGeneric1 where T : Impl { 
    T method1WithParam(T to); 
} 

Sau này, class Generic2 có thể tổng hợp một thành viên INotGeneric1 thay vì:

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 
    } 
} 

Tất nhiên bây giờ bạn không thể gọi elem.method1WithParam trừ khi bạn nghỉ mát để phôi hoặc phản xạ, mặc dù người ta biết rằng một phương pháp như vậy tồn tại và nó là chung với một số loại không rõ X như một đối số kiểu. Tuy nhiên, đó là cùng một hạn chế như mã Java có; nó chỉ là trình biên dịch C# sẽ không chấp nhận mã này trong khi Java sẽ chỉ phàn nàn nếu bạn cố gắng gọi method1WithParam1.

+0

Cảm ơn bạn đã xác nhận sự nghi ngờ của mình. Tôi đã viết một câu hỏi đặc biệt hỏi làm thế nào tôi có thể có thể gọi các phương pháp chung chung trên lĩnh vực đó; Tôi nghĩ rằng tôi đã thiếu một cái gì đó: http://stackoverflow.com/q/14295032/11545. Giao diện thủ phạm trong codebase Java là một công cộng, vì vậy tôi nghĩ rằng tôi có thể phá vỡ một số trường hợp sử dụng bên ngoài có thể nếu tôi không thể tìm thấy một tương ứng 1: 1 trong C#. Hóa ra không có trường hợp sử dụng như vậy. –

2

Java không cho phép một loại để được cả hai biến thể và hiệp biến. Những gì bạn có là một ảo tưởng xuất phát từ thực tế là trong khi bạn đang khai báo IGeneric1<?> elem trong lớp Generic2, bạn không sử dụng phương thức của nó T method1WithParam(T val);; do đó Java không thấy bất kỳ vấn đề nào với tuyên bố này. Tuy nhiên, nó sẽ gắn cờ một lỗi ngay sau khi bạn sẽ cố gắng sử dụng nó thông qua elem.

Để minh họa điều này, sau đây thêm hàm test() vào lớp Generic2 sẽ cố gắng gọi hàm elem.method1WithParam() nhưng điều này dẫn đến lỗi máy nén. Dòng tấn công đã được nhận xét ra, vì vậy bạn cần phải cài đặt lại nó để sao chép các lỗi:

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! 
    } 
} 

Lỗi này từ trình biên dịch Java chứng minh rằng chúng ta không thể sử dụng một loại generic như cả hiệp biến và contravariant và điều này; ngay cả khi một số tuyên bố dường như chứng minh điều ngược lại. Với trình biên dịch C#, bạn thậm chí không có cơ hội nhận được điều đó trước khi nhận được lỗi biên dịch: nếu bạn cố gắng khai báo giao diện IGeneric1<T extends Impl> là biến thể với IGeneric1<out T extends Impl>; bạn tự động gặp lỗi biên dịch cho T method1WithoutParam();

Thứ hai, tôi đã xem tham chiếu .NET equivalent for Java wildcard generics <?> with co- and contra- variance? nhưng tôi phải thừa nhận rằng tôi không hiểu tại sao điều này có thể được xem là giải pháp. Hạn chế loại như <T extends Impl> không liên quan gì đến loại tham số ký tự không bị ràng buộc (<?>) hoặc phương sai (<? extends Impl>) và tôi không thấy cách thay thế giây bằng giây đầu tiên có thể được xem là giải pháp chung. Tuy nhiên, trong một số trường hợp, nếu bạn không thực sự cần sử dụng loại tham số ký tự đại diện (<?>) hoặc loại phương sai hơn có, bạn có thể thực hiện chuyển đổi này. Tuy nhiên, nếu bạn không thực sự sử dụng chúng trong mã Java của bạn, điều này cũng nên được sửa chữa.

Với Generics Java, bạn có thể giới thiệu rất nhiều sự không chính xác nhưng bạn sẽ không nhận được cơ hội đó với trình biên dịch C#. Điều này đặc biệt đúng khi xem xét rằng trong C#, các lớp và cấu trúc hoàn toàn có thể tái tạo và do đó, không hỗ trợ phương sai (cả hiệp phương sai và đối xứng). Bạn có thể sử dụng nó chỉ để khai báo một giao diện và cho các đại biểu; nếu tôi nhớ chính xác.

Cuối cùng, khi sự đa hình có liên quan, thường có khuynh hướng xấu sử dụng các loại chung không cần thiết; có hoặc không có các loại tham số và phương sai tham số. Điều này thường dẫn đến một đoạn mã dài và phức tạp; khó đọc và sử dụng và thậm chí khó viết hơn.Tôi sẽ đề nghị bạn xem xét tất cả các mã Java này và xem liệu nó có thực sự cần thiết để có tất cả những thứ này thay vì một mã đơn giản hơn nhiều chỉ với đa hình hay một sự kết hợp của đa hình với chung chung hay không.

+0

Vâng, tôi đã thử điều đó quá - nhưng nghĩ rằng có thể có một cách thông minh của việc sử dụng các phương pháp chung chung, mà tôi đã mất tích. Tôi hỏi về nó trên http://stackoverflow.com/q/14295032/11545 nhưng tôi đoán câu trả lời của bạn và Jon rõ ràng rằng lên. –

+0

Về đoạn cuối cùng của bạn - Tôi hoàn toàn đồng ý.Tôi đã dành quá nhiều thời gian chỉ cố gắng tìm ra mã và làm thế nào để đơn giản hóa nó (nó rối hơn nhiều so với ví dụ này) đến mức tôi có thể giải thích các bit có vấn đề cho người khác, và tự mình mò mẫm quá trình. OTOH, tôi phải thừa nhận mình đã phạm tội biến chứng tương tự trong quá khứ; đó là một con dốc trơn. –

Các vấn đề liên quan