2010-10-13 30 views
5

Có thể hơi phức tạp, nhưng tôi tự hỏi tại sao. Trong System.Linq.Enumerable.cs của System.Core.dll ta có:Phương pháp mở rộng và kiểm tra thời gian biên dịch

public static int Count<TSource>(this IEnumerable<TSource> source); 

trong mã của tôi Tôi đang làm một cái gì đó xấu xa:

namespace Test 
{ 
    public static class Extensions 
    { 
    public static int Count<TSource>(this IEnumerable<TSource> source) 
    { 
     return -1; //evil code 
    } 
    } 

    //commented temporarily 
    //public static class CommentedExtensions 
    //{ 
    // public static int Count<TSource>(this IEnumerable<TSource> source) 
    // { 
    //  return -2; //another evil code 
    // } 
    //} 

    public static void Main(string[] args) 
    { 
    Console.WriteLine(Enumerable.Range(0,10).Count()); // -1, evil code works 
    Console.Read(); 
    } 
} 

Nếu tôi bỏ ghi chú CommentedExtensions, tôi sẽ nhận được một lỗi biên dịch nói "gọi đây là mơ hồ blabla " như mong đợi. Nhưng tại sao tôi không nhận được lỗi này ở lần đầu tiên? Nó cũng mơ hồ!

EDIT Sau một thử nghiệm khác, tôi thấy rằng tôi sẽ không nhận được lỗi biên dịch nếu phương thức mở rộng ở các không gian tên khác nhau, thậm chí chúng hoàn toàn giống nhau. Tại sao nó được phép? Nó mang lại sự gọi mơ hồ về các phương thức trong C#.

EDIT2 Tôi biết thực tế hai số Count khác nhau trong IL. Trong thực tế nó đang gọi

Enumerable.Count(Enumerable.Range(0,10)) 

và phương pháp khuyến nông ác của tôi đang kêu gọi:

MyExtension.Count(Enumerable.Range(0,10)) 

vì vậy họ là khác nhau. Nhưng tôi vẫn nghĩ đó là mùi hôi. Chúng ta có phương pháp mở rộng "thực sự" không? mà có thể ngăn chặn hành vi xấu xa?

Trả lời

4

Mục 7.6.5.2 của C# language specification mô tả cách trình biên dịch sẽ xác định phương pháp khuyến nông trong phạm vi, và trong đó phương pháp khuyến nông được ưu tiên hơn những người khác:

Việc tìm kiếm C [(một phương pháp mở rộng ứng cử viên)] tiền thu được như sau:

  • Bắt đầu với việc kê khai kèm theo không gian tên gần nhất, tiếp tục với từng tuyên bố không gian tên kèm theo, và kết thúc với các đơn vị biên soạn chứa, các nỗ lực liên tiếp được thực hiện để tìm một bộ ứng cử viên của phương pháp khuyến nông:
    • Nếu không gian tên cho hay compilat đơn vị ion trực tiếp chứa các khai báo kiểu không kiểu generic với các phương thức mở rộng đủ điều kiện Mj, thì tập hợp các phương thức mở rộng đó là ứng cử viên
    • Nếu không gian tên được nhập bằng cách sử dụng chỉ thị vùng tên trong không gian tên đã cho hoặc đơn vị biên dịch trực tiếp chứa loại không chung khai báo Ci với các phương thức mở rộng đủ điều kiện Mj, thì tập các phương thức mở rộng đó là tập ứng viên.

Nghĩa là nếu bạn có phương pháp khuyến nông trong không gian tên tương tự so với mã mà từ đó bạn gọi cho họ, những phương pháp khuyến nông được chọn. Các phương thức mở rộng trong một không gian tên bao quanh sẽ được chọn lựa trên các không gian tên khác đã được nhập khẩu.

2

Dường như C# trông trong không gian tên hiện đầu tiên

Trong ví dụ này, IL là

.method public hidebysig static void Main(string[] args) cil managed 
{ 
    .entrypoint 
    // Code size  27 (0x1b) 
    .maxstack 8 
    IL_0000: nop 
    IL_0001: ldc.i4.0 
    IL_0002: ldc.i4.s 10 
    IL_0004: call  class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> [System.Core]System.Linq.Enumerable::Range(int32, 
                                    int32) 
    IL_0009: call  int32 Test.Extensions::Count<int32>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>) 
    IL_000e: call  void [mscorlib]System.Console::WriteLine(int32) 
    IL_0013: nop 
    IL_0014: call  int32 [mscorlib]System.Console::Read() 
    IL_0019: pop 
    IL_001a: ret 
} // end of method Program::Main 

Nếu tôi chuyển phương pháp chính vào không gian tên khác (XXX) trong trường hợp này trình biên dịch giải quyết phương thức cho phiên bản System.Linq

namespace Test 
{ 
    public static class Extensions 
    { 
     public static int Count<TSource>(this IEnumerable<TSource> source) 
     { 
      return -1; //evil code 
     } 
    } 

} 

namespace XXX{ 

    public static class Program 
    { 
     public static void Main(string[] args) 
     { 
      Console.WriteLine(Enumerable.Range(0, 10).Count()); // -1, evil code works 
      Console.Read(); 
     } 
    } 
} 


.method public hidebysig static void Main(string[] args) cil managed 
{ 
    .entrypoint 
    // Code size  27 (0x1b) 
    .maxstack 8 
    IL_0000: nop 
    IL_0001: ldc.i4.0 
    IL_0002: ldc.i4.s 10 
    IL_0004: call  class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> [System.Core]System.Linq.Enumerable::Range(int32, 
                                    int32) 
    IL_0009: call  int32 [System.Core]System.Linq.Enumerable::Count<int32>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>) 
    IL_000e: call  void [mscorlib]System.Console::WriteLine(int32) 
    IL_0013: nop 
    IL_0014: call  int32 [mscorlib]System.Console::Read() 
    IL_0019: pop 
    IL_001a: ret 
} // end of method Program::Main 

Để sử dụng phương pháp của bạn một cách rõ ràng trong ví dụ sau ou sử dụng

namespace XXX{ 
    using Test; 
    public static class Program 
    { 
     public static void Main(string[] args) 
     { 
      Console.WriteLine(Enumerable.Range(0, 10).Count()); // -1, evil code works 
      Console.Read(); 
     } 

    } 
} 

.method public hidebysig static void Main(string[] args) cil managed 
{ 
    .entrypoint 
    // Code size  27 (0x1b) 
    .maxstack 8 
    IL_0000: nop 
    IL_0001: ldc.i4.0 
    IL_0002: ldc.i4.s 10 
    IL_0004: call  class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> [System.Core]System.Linq.Enumerable::Range(int32, 
                                    int32) 
    IL_0009: call  int32 Test.Extensions::Count<int32>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>) 
    IL_000e: call  void [mscorlib]System.Console::WriteLine(int32) 
    IL_0013: nop 
    IL_0014: call  int32 [mscorlib]System.Console::Read() 
    IL_0019: pop 
    IL_001a: ret 
} // end of method Program::Main 
0

Nếu bạn tạo một lớp mới và thêm sử dụng vào cả hai không gian tên và sau đó sử dụng phương pháp được xác định trong cả hai không gian tên, lỗi sẽ xuất hiện lại.

Phương pháp mở rộng được phân biệt bằng không gian tên, không phải bởi các lớp tĩnh mà chúng được khai báo. Trình biên dịch có thể biết được hai phương thức nào nếu hai phương thức mở rộng được định nghĩa trong cùng một không gian tên.

0

Tôi nghĩ, bạn đang viết Hai phương pháp với cùng một tình trạng quá tải chống lại các nguyên tắc của OOP.

Nếu phương pháp mở rộng nằm trong cùng phạm vi không gian tên như cách sử dụng của bạn, thì nó sẽ được ưu tiên (vì đây là quá tải gần nhất được tìm thấy tại nơi sử dụng) trên System.Core.dll, các phương thức mở rộng tồn tại trong cả hai không gian tên.

Để chứng minh điều này, hãy đổi tên phương thức tiện ích mở rộng thành MyCount1 (...) & MyCount2 (...), sau đó nó sẽ hoạt động cho bạn.

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