2010-08-20 31 views
16

Tôi biết điều này là cũ, nhưng tôi vẫn không hiểu rõ những vấn đề đó. Bất cứ ai có thể cho tôi biết lý do tại sao sau đây không hoạt động (ném một ngoại lệ runtime về đúc)?Generics and casting - không thể truyền lớp kế thừa đến lớp cơ sở

public abstract class EntityBase { } 
public class MyEntity : EntityBase { } 

public abstract class RepositoryBase<T> where T : EntityBase { } 
public class MyEntityRepository : RepositoryBase<MyEntity> { } 

Và bây giờ dòng đúc:

MyEntityRepository myEntityRepo = GetMyEntityRepo(); // whatever 
RepositoryBase<EntityBase> baseRepo = (RepositoryBase<EntityBase>)myEntityRepo; 

Vì vậy, bất cứ ai có thể giải thích như thế nào đây là không hợp lệ? Và, tôi không có tâm trạng để giải thích - có một dòng mã tôi có thể sử dụng để thực sự làm diễn viên này không?

+1

Cảm ơn mọi người vì câu trả lời. Để làm cho nó ngắn - Bây giờ tôi đã giải quyết vấn đề này với một giao diện cơ sở (RepositoryBase : IRepository). Hóa ra tôi chỉ cần thực hiện các hàm trên cá thể mà tôi nhận được và để cho chính lớp đó xử lý những thứ khác. – Jefim

+0

Xem câu hỏi thường gặp [C# Hiệp phương sai và câu hỏi thường gặp] (http://blogs.msdn.com/b/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx) –

Trả lời

24

RepositoryBase<EntityBase>không lớp cơ sở là MyEntityRepository. Bạn đang tìm kiếm phương sai chung tồn tại trong C# ở mức giới hạn, nhưng sẽ không áp dụng ở đây.

Giả sử lớp RepositoryBase<T> bạn đã có một phương pháp như thế này:

void Add(T entity) { ... } 

Bây giờ xem xét:

MyEntityRepository myEntityRepo = GetMyEntityRepo(); // whatever 
RepositoryBase<EntityBase> baseRepo = (RepositoryBase<EntityBase>)myEntityRepo; 
baseRepo.Add(new OtherEntity(...)); 

Bây giờ bạn đã thêm một loại khác nhau của thực thể để một MyEntityRepository ... và điều đó có thể không đúng.

Về cơ bản, phương sai chung chỉ an toàn trong một số trường hợp nhất định. Cụ thể, hiệp phương sai (đó là những gì bạn mô tả ở đây) chỉ an toàn khi bạn chỉ nhận được giá trị "ngoài" của API; generic contravariance (hoạt động theo cách khác) chỉ an toàn khi bạn chỉ đưa giá trị vào "API" (ví dụ: so sánh chung có thể so sánh hai hình dạng theo khu vực có thể được xem là so sánh các ô vuông).

Trong C# 4 điều này có sẵn cho các giao diện chung và đại biểu chung, không phải các lớp - và chỉ với các loại tham chiếu. Xem MSDN để biết thêm thông tin, đọc < cắm > đọc C# in Depth, 2nd edition, chương 13 </cắm > hoặc Eric Lippert blog series về chủ đề. Ngoài ra, tôi đã nói chuyện một giờ về điều này tại NDC vào tháng 7 năm 2010 - video có sẵn here; chỉ cần tìm kiếm "phương sai".

+0

Cảm ơn Jon, cả hai vì giải thích và liên kết đến tài liệu rất thú vị. Tôi chắc chắn sẽ xem xét việc đọc sách và xem video. – Jefim

6

Điều này yêu cầu hiệp phương sai hoặc đối nghịch, có hỗ trợ giới hạn trong .Net và không thể được sử dụng trên các lớp trừu tượng. Tuy nhiên, bạn có thể sử dụng phương sai trên các giao diện, do đó, một giải pháp khả thi cho vấn đề của bạn là tạo một IRepository mà bạn sử dụng thay cho lớp trừu tượng.

public interface IRepository<out T> where T : EntityBase { //or "in" depending on the items. 
    } 
    public abstract class RepositoryBase<T> : IRepository<T> where T : EntityBase { 
    } 
    public class MyEntityRepository : RepositoryBase<MyEntity> { 
    } 

    ... 

    IRepository<EntityBase> baseRepo = (IRepository<EntityBase>)myEntityRepo; 
11

Bất cứ khi nào ai đó hỏi câu hỏi này, tôi cố gắng lấy một ví dụ của họ và dịch nó một cái gì đó sử dụng các lớp học nổi tiếng hơn mà rõ ràng là trái pháp luật (điều này là những gì Jon Skeet has done in his answer; nhưng tôi lấy nó một bước xa hơn bằng cách thực hiện bản dịch này).

Hãy thay thế MyEntityRepositoryMyStringList với, như thế này:

class MyStringList : List<string> { } 

Bây giờ, bạn dường như muốn MyEntityRepository là bột nhôm để RepositoryBase<EntityBase>, những lập luận được rằng điều này nên được tốt vì MyEntity xuất phát từ EntityBase.

Nhưng string có nguồn gốc từ object, phải không? Vì vậy, theo logic này, chúng tôi có thể truyền một số MyStringList đến List<object>.

Hãy xem những gì có thể xảy ra nếu chúng ta cho phép điều đó ...

var strings = new MyStringList(); 
strings.Add("Hello"); 
strings.Add("Goodbye"); 

var objects = (List<object>)strings; 
objects.Add(new Random()); 

foreach (string s in strings) 
{ 
    Console.WriteLine("Length of string: {0}", s.Length); 
} 

Uh-oh. Đột nhiên, chúng tôi đang liệt kê một số List<string> và chúng tôi đến một đối tượng Random. Điều đó không tốt.

Hy vọng điều này khiến cho vấn đề dễ hiểu hơn một chút.

+4

Tôi đã bắt đầu tiến thêm một bước trong các ví dụ - Tôi đã từng sử dụng chuỗi và đối tượng, nhưng theo gợi ý của Eric Lippert tôi đã bắt đầu sử dụng các đối tượng thế giới thực ... Fruit/Apple/Banana hoạt động tốt về mặt "bạn có thể" t thêm một quả táo vào một bó chuối ". –

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