2012-01-14 27 views
18

Mỗi lần như vậy, tôi làm cho một giao diện đơn giản phức tạp hơn bằng cách thêm ràng buộc tham số kiểu tự tham chiếu ("phản xạ") vào nó. Ví dụ, tôi có thể tắt chức năng này:Ràng buộc tham số kiểu phản xạ: X <T> trong đó T: X <T> - bất kỳ lựa chọn thay thế đơn giản nào khác?

interface ICloneable 
{ 
    ICloneable Clone(); 
} 

class Sheep : ICloneable 
{ 
    ICloneable Clone() { … } 
} //^^^^^^^^^^ 

Sheep dolly = new Sheep().Clone() as Sheep; 
           //^^^^^^^^ 

thành:

interface ICloneable<TImpl> where TImpl : ICloneable<TImpl> 
{ 
    TImpl Clone(); 
} 

class Sheep : ICloneable<Sheep> 
{ 
    Sheep Clone() { … } 
} //^^^^^ 

Sheep dolly = new Sheep().Clone(); 

lợi thế chính: Loại thực hiện (chẳng hạn như Sheep) bây giờ có thể tham khảo bản thân thay vì kiểu cơ sở của nó, làm giảm nhu cầu loại đúc (như được chứng minh bởi dòng cuối cùng của mã).

Trong khi điều này rất hay, tôi cũng nhận thấy rằng các ràng buộc tham số kiểu này không trực quan và có xu hướng trở nên thực sự khó hiểu trong các tình huống phức tạp hơn. *)

Câu hỏi: Có ai biết của một mã mẫu C# mà đạt được tác dụng tương tự hoặc một cái gì đó tương tự, nhưng trong một thời trang dễ dàng hơn để nắm bắt?


*) mẫu mã này có thể unintuitive và khó hiểu ví dụ trong các cách sau: "Nếu T là một X<T>, sau đó X<T> thực sự là một X<X<…<T>…>>"

  • Việc kê khai X<T> where T : X<T> dường như là đệ quy, và người ta có thể tự hỏi tại sao các trình biên dịch doesn't get stuck in an infinite loop, lý luận, (Nhưng những hạn chế rõ ràng là không được giải quyết như thế.)

  • Đối với cơ quan thực hiện, nó có thể không được rõ ràng những gì loại nên được chỉ định vào vị trí của TImpl. (Các ràng buộc cuối cùng sẽ chăm sóc đó.)

  • Khi bạn thêm thông số loại khác và phân loại các mối quan hệ giữa các giao diện chung khác nhau với kết hợp, mọi thứ sẽ không thể quản lý được một cách nhanh chóng.

+3

Bạn sẽ rất vui khi biết rằng điều này đủ phổ biến để có tên: Nó được gọi là 'Mẫu mẫu tò mò định kỳ' (viết tắt là CRTP). – Cameron

+1

... và không liên quan gì đến các ràng buộc (vì các mẫu C++ chuẩn không có chúng). – Krizz

+0

Có lý do nào đó không chỉ là 'giao diện ICloneable {T Clone(); } '? –

Trả lời

19

lợi thế chính: Loại thực hiện bây giờ có thể tham khảo bản thân thay vì kiểu cơ sở của nó, giảm nhu cầu đối với loại đúc

Mặc dù nó có vẻ như bởi các loại ràng buộc đề cập đến chính nó nó buộc các loại thực hiện để làm như vậy, đó thực sự không phải là những gì nó làm. Mọi người sử dụng mẫu này để cố gắng thể hiện các mẫu của biểu mẫu "ghi đè phương thức này phải trả về loại của lớp ghi đè", nhưng đó không thực sự là ràng buộc được thể hiện hoặc được thi hành bởi hệ thống kiểu.Tôi đưa ra một ví dụ ở đây:

http://blogs.msdn.com/b/ericlippert/archive/2011/02/03/curiouser-and-curiouser.aspx

Trong khi điều này là rất tốt đẹp, tôi cũng đã nhận thấy rằng những khó khăn kiểu tham số không phải là trực quan và có xu hướng trở nên thực sự khó hiểu trong các tình huống phức tạp hơn

Đúng. Tôi cố tránh mô hình này. Thật khó để giải thích.

Có ai biết một mẫu mã C# khác đạt được hiệu quả tương tự hoặc kiểu tương tự, nhưng theo kiểu dễ hiểu không?

Không có trong C#, không. Bạn có thể xem xét xem xét hệ thống kiểu Haskell nếu loại điều này làm bạn quan tâm; "Loại cao hơn" của Haskell có thể đại diện cho các loại mẫu đó.

Việc kê khai X<T> where T : X<T> dường như là đệ quy, và người ta có thể tự hỏi tại sao các trình biên dịch không gặp khó khăn trong một vòng lặp vô hạn, lập luận: "Nếu T là một X<T>, sau đó X<T> thực sự là một X<X<…<T>…>>."

Trình biên dịch không bao giờ đi vào vòng lặp vô hạn khi lập luận về các mối quan hệ đơn giản như vậy. Tuy nhiên, loại phụ danh nghĩa của các loại chung với contravariance nói chung là không thể xác minh. Có nhiều cách để buộc trình biên dịch vào các hồi quy vô hạn, và trình biên dịch C# không phát hiện ra và ngăn chặn chúng trước khi bắt tay vào hành trình vô hạn. (Tuy nhiên, tôi hy vọng sẽ phát hiện thêm điều này trong trình biên dịch Roslyn nhưng chúng ta sẽ thấy.)

Xem bài viết của tôi về chủ đề này nếu bạn quan tâm. Bạn cũng sẽ muốn đọc giấy liên kết.

http://blogs.msdn.com/b/ericlippert/archive/2008/05/07/covariance-and-contravariance-part-twelve-to-infinity-but-not-beyond.aspx

+0

Cảm ơn câu trả lời chi tiết. Bài đăng trên blog của bạn chạm vào móng trên đầu. 1 cũng chỉ ra rằng trình biên dịch C# thông minh hơn so với tập thể Borg khi nói đến các thingies dường như vô hạn. :) – stakx

6

Thật không may, không phải là một cách để ngăn chặn hoàn toàn này, và một generic ICloneable<T> không có hạn chế loại là đủ. Ràng buộc của bạn chỉ giới hạn các tham số có thể có đối với các lớp mà bản thân chúng triển khai thực hiện nó, điều đó không có nghĩa là các lớp đó hiện đang được triển khai thực hiện.

Nói cách khác, nếu thực hiện Cow thực hiện ICloneable<Cow>, bạn vẫn sẽ dễ dàng thực hiện Sheep triển khai ICloneable<Cow>.

tôi chỉ đơn giản sẽ sử dụng ICloneable<T> mà không hạn chế vì hai lý do:

  1. tôi nghiêm túc nghi ngờ bao giờ bạn sẽ làm cho một sai lầm của việc sử dụng một số loại sai.

  2. Giao diện có nghĩa là hợp đồng đối với các phần mã khác, không được sử dụng để mã trên máy tự động. Nếu một phần của mã mong đợi ICloneable<Cow> và bạn vượt qua một Sheep có thể làm điều đó, nó có vẻ hoàn toàn hợp lệ từ thời điểm đó.

+1

+1 "[' giao diện X trong đó T: X {} '] chỉ giới hạn các tham số có thể có đối với các lớp mà bản thân thực hiện nó, điều đó không có nghĩa là chúng hiện đang được triển khai." Rất ngắn gọn, nổi bật! –

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