2014-04-22 16 views
5

Chương trình sau đây không biên dịch, vì phù hợp với lỗi, trình biên dịch chọn phương thức có tham số T đơn lẻ làm độ phân giải, không thành công vì List<T> không phù hợp với các ràng buộc chung của duy nhất T. Trình biên dịch không nhận ra rằng có một phương pháp khác có thể được sử dụng. Nếu tôi loại bỏ phương thức đơn T, trình biên dịch sẽ tìm đúng phương thức cho nhiều đối tượng.Độ phân giải phương pháp mở rộng chung không thành công

Tôi đã đọc hai bài đăng blog về độ phân giải phương pháp chung, một từ JonSkeet here và một bài khác từ Eric Lippert here, nhưng tôi không thể tìm thấy giải thích hoặc cách giải quyết vấn đề của mình.

Rõ ràng, có hai phương pháp có tên khác nhau sẽ hoạt động, nhưng tôi thích thực tế là bạn có một phương pháp duy nhất cho những trường hợp đó.

namespace Test 
{ 
    using System.Collections.Generic; 

    public interface SomeInterface { } 

    public class SomeImplementation : SomeInterface { } 

    public static class ExtensionMethods 
    { 
    // comment out this line, to make the compiler chose the right method on the line that throws an error below 
    public static void Method<T>(this T parameter) where T : SomeInterface { } 

    public static void Method<T>(this IEnumerable<T> parameter) where T : SomeInterface { } 
    } 

    class Program 
    { 
    static void Main() 
    { 
     var instance = new SomeImplementation(); 
     var instances = new List<SomeImplementation>(); 

     // works 
     instance.Method(); 

     // Error 1 The type 'System.Collections.Generic.List<Test.SomeImplementation>' 
     // cannot be used as type parameter 'T' in the generic type or method 
     // 'Test.ExtensionMethods.Method<T>(T)'. There is no implicit reference conversion 
     // from 'System.Collections.Generic.List<Test.SomeImplementation>' to 'Test.SomeInterface'. 
     instances.Method(); 

     // works 
     (instances as IEnumerable<SomeImplementation>).Method(); 
    } 
    } 
} 
+0

Bạn đã thử 'instances.Method ();'? – Dmitry

+0

@Dmitry thực sự sẽ hoạt động. Tôi sẽ kiểm tra một vài điều. – nvoigt

Trả lời

6

độ phân giải Phương pháp nói rằng closer is better. Xem bài đăng trên blog để biết các quy tắc chính xác.

Ý nghĩa gần hơn là gì? Trình biên dịch sẽ thấy nếu nó có thể tìm thấy kết hợp chính xác, nếu nó không thể tìm thấy vì một lý do nào đó nó sẽ tìm các phương thức tương thích có thể tiếp theo và vv.

Trước tiên, hãy thực hiện phương thức biên dịch đó bằng cách xóa ràng buộc SomeInterface.

public static class ExtensionMethods 
{ 
    public static void Method<T>(this T parameter) //where T : SomeInterface 
    { } 

    public static void Method<T>(this IEnumerable<T> parameter) //where T : SomeInterface 
    { } 
} 

Bây giờ biên dịch là hạnh phúc để biên dịch, và lưu ý rằng cả hai phương pháp gọi Goes to Method(T) hơn Method(IEnumerable<T>). Tại sao vậy?

Bởi vì Method(T) gần hơn theo nghĩa có thể lấy bất kỳ loại nào làm thông số và cũng không yêu cầu bất kỳ chuyển đổi nào.

Tại sao Method(IEnumerable<T>) không gần hơn?

Đó là bởi vì bạn có các loại thời gian biên dịch của các biến như List<T>, vì vậy nó cần một chuyển đổi tài liệu tham khảo List<T>-IEnumerable<T>. Đó là gần gũi hơn nhưng đến nay không thực hiện chuyển đổi nào cả.

Quay lại câu hỏi của bạn.

Tại sao instances.Method(); không biên dịch?

Một lần nữa, như đã nói trước đây sử dụng Method(IEnumerable<T>), chúng tôi cần một số chuyển đổi tham chiếu, vì vậy rõ ràng không gần hơn. Bây giờ chúng tôi chỉ còn lại một phương pháp rất gần là Method<T>. Nhưng vấn đề là bạn đã hạn chế nó với SomeInterface và rõ ràng List<SomeImplementation>() không thể chuyển đổi thành SomeInterface.

Vấn đề là (đang đoán) kiểm tra các ràng buộc chung xảy ra sau khi trình biên dịch chọn quá tải gần hơn. Điều đó làm mất hiệu lực quá tải tốt nhất đã chọn trong trường hợp này.

Bạn có thể dễ dàng khắc phục bằng cách thay đổi loại tĩnh của biến thành IEnumerable<SomeImplementation> sẽ hoạt động và bây giờ bạn biết tại sao.

IEnumerable<SomeImplementation> instances = new List<SomeImplementation>(); 
+0

Ok, điều đó không thực sự giải quyết được vấn đề của tôi (tôi vẫn sẽ phải đặt tên khác để mọi người có thể sử dụng nó mà không có bất ngờ), nhưng ít nhất đó là một lời giải thích khá tốt * tại sao *. – nvoigt

1

Bạn đã cố gắng thực hiện một trong những đầu tiên mà không Generics, vì nó nên cư xử như nhau:

public static void Method(this SomeInterface parameter) { /*...*/ } 

Hoặc, như Dmitry đề nghị, bằng cách gọi thứ hai cách sau:

instances.Method<SomeImplementation>(); 

Nhưng tại đây bạn cần phải thêm <SomeImplementation> vào mọi cuộc gọi ...

+0

Đó là một ý tưởng hay. Có lẽ tôi nên mở rộng ví dụ của mình. Trong dự án thực sự của chúng tôi T bị ràng buộc để triển khai thực hiện * hai * giao diện khác nhau, đó là lý do chúng tôi đang sử dụng Generics. – nvoigt

+0

Ah, OK. Trong trường hợp đó ý tưởng của tôi không hoạt động (nhưng sẽ làm việc cho trường hợp này - chỉ cần kiểm tra nó) ... – ChrFin

+0

Tôi đã thử rất nhiều thứ và không thể làm việc với Generics trong cả hai trường hợp mà không cần thêm mã gọi phương thức và IMO nó sẽ hoạt động - có thể người khác có thể làm sáng tỏ hành vi này (hoặc nó có thể thực sự là một lỗi trong giải pháp phương pháp) ... – ChrFin

1
  1. Trong khi tôi biết bạn không muốn nó, tôi nghĩ bạn nên thực sự nghĩ lại nếu tên phương pháp nên giống nhau. Tôi không thể thấy làm thế nào cùng một tên có thể hành động trên một thể hiện, và tập hợp các trường hợp như vậy. Ví dụ: nếu tên phương thức của bạn là Shoot cho T, thì phương pháp khác phải giống như ShootThemAll hoặc một cái gì đó tương tự.

  2. Hoặc nếu không bạn nên thực hiện nhiệm vụ của bạn hơi khác nhau:

    IEnumerable<SomeImplementation> instances = new List<SomeImplementation>(); 
    instances.Method(); //now this should work 
    
  3. Là một lựa chọn cuối cùng, như Dimitry nói trong bình luận, bạn phải xác định rõ ràng các đối số kiểu.

    instances.Method<SomeImplementation>(); 
    
Các vấn đề liên quan