2014-09-22 34 views
9

Hãy nói rằng có những loại generic trong C#:Generic suy luận kiểu trong C#

class Entity<KeyType> 
{ 
    public KeyType Id { get; private set; } 
    ... 
} 

interface IRepository<KeyType, EntityType> where EntityType : Entity<KeyType> 
{ 
    EntityType Get(KeyType id); 
    ... 
} 

và các loại bê tông:

class Person : Entity<int> { ... } 

interface IPersonRepository : IRepository<int, Person> { ... } 

Bây giờ định nghĩa của PersonRepository là không cần thiết: thực tế là KeyType của Personint được tuyên bố rõ ràng, mặc dù nó có thể được suy ra từ thực tế là Person là một loại phụ của Entity<int>.

Nó sẽ được tốt đẹp để có thể xác định IPersonRepository như thế này:

interface IPersonRepository : IRepository<Person> { ... } 

và để cho các con số trình biên dịch ra rằng KeyTypeint. Có thể không?

+0

Làm cách nào để có thể biết 'KeyType'? Bạn có thể hiển thị cú pháp đầy đủ về những gì bạn mong đợi không? Định nghĩa giao diện cuối cùng của bạn có vẻ không hoàn chỉnh. –

+0

Tôi không nghĩ là có thể. Trình biên dịch không đủ "thông minh". Tôi sẽ không làm cho nó một câu trả lời mặc dù bởi vì "Tôi không thể nghĩ ra bất kỳ cách nào bạn có thể làm điều đó" là không đủ bằng chứng bạn không thể. – Falanwe

+3

Tôi giả sử dòng cuối cùng của mã phải là 'giao diện IPersonRepository: IRepository {...}' – Rawling

Trả lời

1

Hãy nói rằng chúng tôi muốn tuyên bố

interface IPersonRepository : IRepository<Person> { } 

Điều đó sẽ đòi hỏi rằng có một giao diện chung với một loại tham số IRepository<EntityType>.

interface IRepository<EntityType> where EntityType : Entity<KeyType> 
{ 
    EntityType Get(KeyType id); 
} 

Ở cuối dòng đầu tiên, bạn tham chiếu đến một điều gọi là KeyType, chưa được khai báo hay xác định. Không có loại được gọi là "KeyType".

này sẽ làm việc mặc dù:

interface IRepository<EntityType> where EntityType : Entity<int> 
{ 
    EntityType Get(int id); 
} 

Hoặc này:

interface IRepository<EntityType> where EntityType : Entity<string> 
{ 
    EntityType Get(string id); 
} 

Nhưng bạn không thể có cả hai định nghĩa mâu thuẫn cùng lúc tất nhiên. Rõ ràng, bạn không hài lòng với điều đó, bởi vì bạn muốn có thể xác định giao diện IRpository của bạn theo cách mà nó hoạt động với các loại khóa khác.

Vâng, bạn có thể, nếu bạn thực hiện nó chung chung trong các loại hình chính:

interface IRepository<KeyType, EntityType> where EntityType : Entity<KeyType> 
{ 
    EntityType Get(KeyType id); 
} 

Có một cách tiếp cận khác:

interface IRepository<KeyType> 
{ 
    EntityType<KeyType> Get(KeyType id); 
} 

Bây giờ bạn có thể định nghĩa

class PersonRepository : IRepository<int> 
{ 
    public EntityType<int> Get(int id) { ... } 
} 

Rõ ràng, bạn sẽ không hài lòng với điều đó, bởi vì bạn muốn nói rằng phương thức Get phải trả về một Person, không chỉ bất kỳ Entity<int> nào.

Giao diện chung với hai tham số loại trong giải pháp duy nhất. Và quả thực, có một mối quan hệ cần thiết giữa chúng, như thể hiện trong ràng buộc. Nhưng không có dự phòng ở đây: chỉ định int cho thông số loại không mang đủ thông tin.

Nếu chúng ta nói

class PersonRepository : IRepository<int, Person> 
{ 
    public Person Get(int id) { ... } 
} 

Có thực sự là dư thừa: Ghi rõ các tham số kiểu int là không cần thiết khi tham số kiểu Person đã được xác định.

Nó sẽ có thể đến với op với một cú pháp mà làm cho nó có thể suy ra KeyType. Ví dụ, Patrick Hoffman gợi ý:

class PersonRepository : IRepository<EntityType: Person>. 
{ 
    public Person Get(int id) { ... } 
} 

Trong khi về mặt lý thuyết có thể, tôi sợ rằng điều này sẽ thêm rất nhiều phức tạp để đặc tả ngôn ngữ và trình biên dịch, vì rất ít lợi ích. Trong thực tế, có bất kỳ lợi ích nào cả? Bạn chắc chắn sẽ không lưu các tổ hợp phím! So sánh hai loại này:

// current syntax 
class PersonRepository : IRepository<int, Person> 
{ 
    public Person Get(int id) { ... } 
} 

// proposed syntax 
class PersonRepository : IRepository<EntityType: Person> 
{ 
    public Person Get(int id) { ... } 
} 

Ngôn ngữ là gì và nó không quá tệ với tôi.

+2

'interface IPersonRepository: IRepository ' Biết rằng tham số kiểu đầu tiên cho 'IRepository' được cho là giống nhau đối với' Person' cho biết rõ ràng trong khai báo 'IPersonRepository' thừa, vì nó đã được khai báo:' class Người: Pháp nhân '. Vấn đề tôi có là nói với trình biên dịch rằng chúng thực sự được cho là giống nhau, tức là suy ra kiểu từ việc khai báo 'Person'. – proskor

+0

@proskor Bạn có một điểm, tôi sẽ cập nhật câu trả lời của mình. –

0

Không, điều đó không thể viết được vì nó không suy ra loại (trình biên dịch không). Bạn có thể làm như vậy (bạn cần phải là một phần của nhóm biên dịch C# mặc dù để có được nó), vì không có giá trị nào khác có thể là giá trị của KeyType đưa vào tham số kiểu cho Entity. Bạn không thể đặt trong một loại có nguồn gốc hoặc một loại lớp cơ sở.

Như những người khác đã nhận xét, điều đó có thể làm phức tạp mã của bạn hơn. Ngoài ra, điều này chỉ hoạt động trong trường hợp Entity<T> là một lớp, khi đó là một giao diện, nó không thể suy ra loại vì nó có thể có nhiều triển khai. (Có lẽ đó là lý do cuối cùng họ không xây dựng điều này)

+0

'bạn cần phải là một phần của đội biên dịch C# mặc dù để có được nó trong' - tốt, bạn luôn có thể ngã ba Roslyn, nếu bạn không nhớ không ai khác có thể biên dịch mã của bạn :) – Rawling

+2

@Rawling: I mong muốn thực hiện của bạn;) –

+0

Cú pháp sẽ giống như thế nào? 'interface IRepository trong đó EntityType: Entity ' là cú pháp hiện có với một nghĩa được xác định rõ. Tất nhiên, trình biên dịch không thể biên dịch nếu kiểu 'KeyType' không tồn tại. Xem thêm câu trả lời của tôi. –

1

Không, hệ thống loại C# không đủ nâng cao để thể hiện những gì bạn muốn. Tính năng cần thiết được gọi là loại được đánh giá cao hơn thường được tìm thấy trong các ngôn ngữ chức năng được nhập mạnh mẽ (Haskell, OCaml, Scala).

Làm việc theo cách của chúng tôi trở lại, bạn muốn để có thể viết

interface IRepository<EntityType<EntityKey>> { 
    EntityType<EntityKey> Get(KeyType id); 
} 

interface PersonRepository : IRepository<Person> { 
    Person Get(Int id); 
} 

nhưng trong C# không có cách nào để diễn tả EntityType hiện vật hoặc, nói cách khác, đó là tham số kiểu có một số thông số chung chung và sử dụng thông số chung đó trong mã của bạn.

Lưu ý phụ: Mẫu lưu trữ là Evil và phải chết trong lửa.

+0

Câu trả lời tuyệt vời. Nhưng tại sao mô hình kho lưu trữ lại là ác? Bạn sẽ đề xuất lựa chọn thay thế nào? – proskor

+0

Không có usecase cho một mẫu kho lưu trữ. Nó hoạt động chống lại bạn bởi vì nói chung họ là một subinterface của ORM bạn đang sử dụng bên dưới mà chỉ cần chuyển tiếp cuộc gọi.Nó không giúp mocking và/hoặc thử nghiệm (bạn không thể nghiêm túc giả lập nơi/nhận được khi bạn phải đưa vào tài khoản mà biểu thức nhà cung cấp cơ sở dữ liệu của bạn không và không cung cấp), nó không làm cho mã của bạn nhiều hơn modular, nó bổ sung thêm một lớp phức tạp, và trong trường hợp hiếm hoi bạn đang hoán đổi ORM của bạn (vâng, phải), bạn có thể phải nghiên cứu về logic nghiệp vụ bởi vì các đặc tính hiệu suất khác nhau. – Martijn

+0

Đối với các lựa chọn thay thế, chỉ cần sử dụng ORM của bạn trực tiếp thay vì gói nó trong một kho lưu trữ. – Martijn

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