2013-05-24 31 views
31

Giả sử tôi có nhu cầu về phương thức trợ giúp riêng tư đơn giản và trực quan trong mã nó có ý nghĩa như một phương thức mở rộng. Có cách nào để đóng gói trợ giúp đó cho lớp duy nhất thực sự cần sử dụng nó không?Có thể có phương pháp mở rộng riêng tư không?

Ví dụ, tôi cố gắng này:

class Program 
{ 
    static void Main(string[] args) 
    { 
     var value = 0; 
     value = value.GetNext(); // Compiler error 
    } 

    static int GetNext(this int i) 
    { 
     return i + 1; 
    } 
} 

Trình biên dịch không "nhìn thấy" các phương pháp GetNext() mở rộng. Lỗi này là:

phương pháp mở rộng phải được định nghĩa trong một lớp tĩnh phi generic

Hội chợ đủ, vì vậy tôi quấn nó trong lớp học riêng của mình, nhưng vẫn còn gói gọn trong phạm vi đối tượng mà nó thuộc về:

class Program 
{ 
    static void Main(string[] args) 
    { 
     var value = 0; 
     value = value.GetNext(); // Compiler error 
    } 

    static class Extensions 
    { 
     static int GetNext(this int i) 
     { 
      return i + 1; 
     } 
    } 
} 

Vẫn không có súc sắc. Bây giờ lỗi trạng thái:

Phương thức mở rộng phải được xác định trong lớp tĩnh cấp cao nhất; Tiện ích mở rộng là một lớp lồng nhau.

Có lý do thuyết phục nào cho yêu cầu này không? Có những trường hợp mà một phương thức trợ giúp thực sự cần được đóng gói riêng, và có những trường hợp mã này sạch hơn và dễ đọc hơn/có thể hỗ trợ nếu phương thức trợ giúp là một phương thức mở rộng. Đối với những trường hợp hai giao điểm này, cả hai có thể được thỏa mãn hoặc chúng ta phải chọn cái kia hay không?

+1

Đọc thông báo lỗi đầu tiên nói gì, nó muốn bạn lớp chương trình tĩnh, Bạn đã thử làm cho nó tĩnh và thử mã của bạn? – Bearcat9425

+0

Câu hỏi liên quan: http://stackoverflow.com/questions/2145576/how-can-i-implement-an-extention-property-class-for-primitive-types-in-a-clean-w – cvraman

+1

@ Bearcat9425, rằng sẽ * sửa lỗi "trường hợp sử dụng cụ thể này, nhưng không thể áp dụng chung. Tôi tưởng tượng câu hỏi này là dành cho khả năng ứng dụng chung, không dành cho các ứng dụng giao diện mẫu. –

Trả lời

15

Tôi tin rằng tốt nhất bạn có thể nhận được trong trường hợp chung là internal static lớp học với internal static phương pháp mở rộng. Vì nó sẽ nằm trong hội đồng của bạn, chỉ những người bạn cần để ngăn chặn việc sử dụng phần mở rộng là các tác giả của hội đồng - vì vậy một số không gian tên được đặt tên rõ ràng (như My.Extensions.ForFoobarOnly) có thể đủ để gợi ý để tránh sử dụng sai mục đích.

Các tối thiểu internal hạn chế đề cập trong bài viết implement extension

Các lớp phải được hiển thị cho mã khách hàng ... phương pháp có ít nhất khả năng hiển thị tương tự như các lớp chứa.

Lưu ý: Tôi sẽ làm cho phần mở rộng công anyway để đơn giản hóa kiểm tra đơn vị, nhưng đặt trong một số namespace tên một cách rõ ràng như Xxxx.Yyyy.Internal những người dùng khác của hội đồng sẽ không mong đợi các phương pháp để được hỗ trợ/callable. Về cơ bản dựa vào quy ước khác hơn là thực thi thời gian biên dịch.

+0

Tôi có thể sẽ chơi với các quy ước trong một thời gian dài sắp tới, cuối cùng đến một cái gì đó có ý nghĩa. Đó là một trường hợp hiếm hoi mà tôi không lo lắng khủng khiếp, nó chỉ là vấn đề rời khỏi codebase trong một trạng thái trực quan và hỗ trợ cho bất cứ ai phải hỗ trợ nó trong tương lai (bao gồm cả bản thân tôi). Cảm ơn! – David

+0

Alexei: nếu nó chỉ là để thử nghiệm đơn vị, nó sẽ không được tốt hơn để rời phần mở rộng nội bộ và phơi bày internals để dự án thử nghiệm của bạn bằng cách sử dụng InternalsVisibleToAttribute (https://msdn.microsoft.com/en-us/library/system.runtime. compilerservices.internalsvisibletoattribute (v = vs.110) .aspx)? – Przemo

+0

@Przemo 'InternalsVisibleToAttribute' thực sự có thể được sử dụng, nhưng nó giết chết một số khả năng sử dụng của các phương pháp mở rộng vì chúng không còn hiển thị trong tự động hoàn thành theo như tôi biết. Đó là sự lựa chọn cá nhân nhiều hơn hoặc ít hơn cho hầu hết các dự án (ngoại trừ một số thư viện được thiết kế để sử dụng rất rộng) - bạn cần cân nhắc sự tiện lợi và độ tinh khiết của API trong trường hợp cụ thể của bạn. Các dự án tôi làm việc nói chung là "nhỏ" và không phơi bày các API công cộng thực sự - vì vậy 'công khai' hoạt động tốt trong các trường hợp của tôi. –

1

Mã này biên dịch và các công trình:

static class Program 
{ 
    static void Main(string[] args) 
    { 
     var value = 0; 
     value = value.GetNext(); // Compiler error 
    } 

    static int GetNext(this int i) 
    { 
     return i + 1; 
    } 
} 

Chú ý đến static class Program dòng đó là những gì các trình biên dịch cho biết là cần thiết.

+1

Điều này làm việc tốt trong ví dụ giả tạo của tôi, tôi thừa nhận. Nhưng làm cho lớp tĩnh không phải lúc nào cũng là một lựa chọn khả thi. Đối với các mô hình miền, nó có thể gây ra các vấn đề lớn. – David

+0

Đó là lý do tại sao chúng được gọi là "Phương pháp mở rộng" và thực hành tốt nhất là xác định chúng trong một lớp tĩnh riêng biệt, được đặt trong một không gian tên khác nhau. –

+1

"Các phương pháp hay nhất" là một thuật ngữ tương đối. Đóng gói cũng là một thực hành tốt nhất. Có những trường hợp đặt các hàm trợ giúp (trong trường hợp này là các phương thức mở rộng) trong một lớp riêng biệt (hoặc một không gian tên hoàn toàn riêng biệt) dẫn đến trừu tượng bị rò rỉ, liên kết chặt chẽ với chức năng trợ giúp đó với nội bộ của đối tượng duy nhất mà nó trợ giúp. – David

34

Có lý do thuyết phục nào cho yêu cầu này không?

Đó là câu hỏi sai. Câu hỏi được yêu cầu bởi nhóm thiết kế ngôn ngữ khi chúng tôi thiết kế tính năng này là:

Có lý do thuyết phục nào để cho phép khai báo các phương thức mở rộng trong các loại tĩnh lồng nhau không?

Vì các phương pháp mở rộng được thiết kế để làm cho LINQ hoạt động và LINQ không có kịch bản mà các phương pháp mở rộng sẽ riêng tư cho một loại, câu trả lời là "không, không có lý do thuyết phục".

Bằng cách loại bỏ khả năng đặt các phương thức mở rộng trong các kiểu lồng nhau tĩnh, không có quy tắc nào để tìm kiếm các phương thức mở rộng trong các loại lồng nhau tĩnh cần thiết, được lập luận, thiết kế, xác định, thực hiện, kiểm tra, ghi chép, vận chuyển cho khách hàng hoặc tương thích với mọi tính năng trong tương lai của C#. Đó là một khoản tiết kiệm chi phí đáng kể.

+7

'" tìm kiếm các phương pháp mở rộng "' - Có phần tôi không nghĩ đến ở đó. Thật dễ dàng để hét lên từ phía sau các khán giả rằng "điều này sẽ hoạt động" trong khi quên đi nỗ lực cần thiết để làm cho nó hoạt động. Hoàn toàn có ý nghĩa bây giờ, và kết hợp với câu trả lời của Alexei đến một giải pháp rất khả thi. Cảm ơn! – David

+4

Điều này nói với tôi là thời gian thiết kế không thực sự thiết kế các phương pháp mở rộng cho mỗi lần, họ thiết kế LINQ. Các trường hợp sử dụng cho các phương thức mở rộng là riêng tư là giống nhau đối với mọi phương thức khác là riêng tư. Không có gì đặc biệt về các phương pháp mở rộng. – Puppy

+0

Về mặt lý thuyết đây là câu trả lời đúng. Nhóm thiết kế phải thiết lập ranh giới cho mã nào có thể làm để quản lý nó. – DerpyNerd

1

Tôi tin rằng đó là cách họ triển khai việc biên dịch các Phương thức tiện ích mở rộng.

Nhìn vào IL, có vẻ như họ thêm một số thuộc tính bổ sung vào phương thức.

.method public hidebysig static int32 GetNext(int32 i) cil managed 
{ 
    .custom instance void [System.Core]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() 
    .maxstack 2 
    .locals init (
     [0] int32 num) 
    L_0000: nop 
    L_0001: ldarg.0 
    L_0002: ldc.i4.1 
    L_0003: add 
    L_0004: dup 
    L_0005: starg.s i 
    L_0007: stloc.0 
    L_0008: br.s L_000a 
    L_000a: ldloc.0 
    L_000b: ret 
} 

Có thể có một số điều rất cơ bản mà chúng tôi đang thiếu mà không làm cho nó hoạt động, đó là lý do tại sao hạn chế được đưa ra. Cũng có thể là họ muốn ép buộc thực hành mã hóa. Thật không may, nó không hoạt động và phải ở trong các lớp tĩnh cấp cao nhất.

0

Điều này được lấy từ ví dụ trên microsoft msdn. Phương thức Extesnion phải được định nghĩa trong một lớp tĩnh. Xem cách lớp tĩnh được xác định trong một không gian tên khác và được nhập vào. Bạn có thể xem ví dụ tại đây http://msdn.microsoft.com/en-us/library/bb311042(v=vs.90).aspx

namespace TestingEXtensions 
{ 
    using CustomExtensions; 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      var value = 0; 
      Console.WriteLine(value.ToString()); //Test output 
      value = value.GetNext(); 
      Console.WriteLine(value.ToString()); // see that it incremented 
      Console.ReadLine(); 
     } 
    } 
} 

namespace CustomExtensions 
{ 
    public static class IntExtensions 
    { 
     public static int GetNext(this int i) 
     { 
      return i + 1; 
     } 
    } 
}