2013-07-02 35 views
24

Tôi đã tạo ra một vài giao diện và các lớp học chung để làm việc với các cuộc hẹn chương trình nghị sự:Tại sao kết quả ràng buộc kiểu chung loại không có lỗi chuyển đổi tham chiếu ngầm định?

interface IAppointment<T> where T : IAppointmentProperties 
{ 
    T Properties { get; set; } 
} 

interface IAppointmentEntry<T> where T : IAppointment<IAppointmentProperties> 
{ 
    DateTime Date { get; set; } 
    T Appointment { get; set; } 
} 

interface IAppointmentProperties 
{ 
    string Description { get; set; } 
} 

class Appointment<T> : IAppointment<T> where T : IAppointmentProperties 
{ 
    public T Properties { get; set; } 
} 

class AppointmentEntry<T> : IAppointmentEntry<T> where T : IAppointment<IAppointmentProperties> 
{ 
    public DateTime Date { get; set; } 
    public T Appointment { get; set; } 
} 

class AppointmentProperties : IAppointmentProperties 
{ 
    public string Description { get; set; } 
} 

Tôi đang cố gắng sử dụng một số những hạn chế trên các thông số loại để đảm bảo rằng chỉ các loại hợp lệ có thể được xác định. Tuy nhiên, khi xác định một hạn định rằng T phải thực hiện IAppointment<IAppointmentProperties>, trình biên dịch đưa ra một lỗi khi sử dụng một lớp học đó là Appointment<AppointmentProperties>:

class MyAppointment : Appointment<MyAppointmentProperties> 
{ 
} 

// This goes wrong: 
class MyAppointmentEntry : AppointmentEntry<MyAppointment> 
{ 
} 

class MyAppointmentProperties : AppointmentProperties 
{ 
    public string ExtraInformation { get; set; } 
} 

Lỗi này là:

The type 'Example.MyAppointment' cannot be used as type parameter 'T' in the generic type or method 'Example.AppointmentEntry<T>'. There is no implicit reference conversion from 'Example.MyAppointment' to 'Example.IAppointment<Example.IAppointmentProperties>'.

Ai có thể giải thích tại sao điều này không hoạt động?

+5

Điều này thật kỳ quặc. ** NHƯNG **: đây là một cách sử dụng quá mức của thuốc generic. Tôi chỉ có thể đọc những gì (tôi cho là) ​​rất, rất đơn giản mã. –

Trả lời

91

Hãy đơn giản hóa:

interface IAnimal { ... } 
interface ICage<T> where T : IAnimal { void Enclose(T animal); } 
class Tiger : IAnimal { ... } 
class Fish : IAnimal { ... } 
class Cage<T> : ICage<T> where T : IAnimal { ... } 
ICage<IAnimal> cage = new Cage<Tiger>(); 

Câu hỏi của bạn là: tại sao là dòng cuối cùng bất hợp pháp?

Bây giờ tôi đã viết lại mã để đơn giản hóa nó, cần phải rõ ràng. An ICage<IAnimal> là một cái lồng mà bạn có thể đặt bất kỳ động vật, nhưng Cage<Tiger> có thể chỉ giữ hổ, vì vậy điều này phải là bất hợp pháp.

Nếu nó không được trái pháp luật thì bạn có thể làm điều này:

cage.Enclose(new Fish()); 

Và hey, bạn chỉ cần đặt một con cá vào một cái lồng hổ.

Hệ thống kiểu không cho phép chuyển đổi đó vì làm như vậy sẽ vi phạm quy tắc rằng khả năng của loại nguồn không được là ít hơn so với khả năng của loại mục tiêu. (Đây là một dạng của nguyên tắc thay thế Liskov nổi tiếng ".)

Cụ thể hơn, tôi sẽ nói rằng bạn đang lạm dụng Generics. Thực tế là bạn đã tạo ra các mối quan hệ kiểu quá phức tạp để bạn phân tích chính mình là bằng chứng rằng bạn nên đơn giản hoá toàn bộ sự việc; nếu bạn không giữ tất cả các mối quan hệ kiểu thẳng và bạn đã viết điều thì người dùng của bạn chắc chắn sẽ không thể giữ nó thẳng.

+0

Việc đơn giản hóa của bạn làm cho nó rõ ràng hơn rất nhiều! Tôi sẽ cố gắng thiết kế lại các lớp học để sử dụng generics một cách rõ ràng hơn. Đánh dấu là câu trả lời. – Rens

+1

Đây là một lời giải thích rất tốt/đơn giản. Tôi nhớ đọc cuốn sách của Jon Skeet C# chiều sâu nó giải thích C# generic loại hiệp phương sai đối xứng rất tốt. Rất khuyên bạn nên. –

+0

"Bạn chỉ cần đặt một con cá vào một cái lồng hổ." Có phải đó là một con hổ? :-) – twelveFunKeys

6

Vì bạn đã khai báo lớp học MyAppointment bằng loại bê tông thay vì giao diện. Bạn nên khai báo như sau:

class MyAppointment : Appointment<IAppointmentProperties> { 
} 

Bây giờ chuyển đổi có thể xảy ra hoàn toàn.

Bằng cách khai báo AppointmentEntry<T> với các hạn chế where T: IAppointment<IAppointmentProperties> bạn đang tạo một hợp đồng theo đó loại không xác định cho AppointmentEntry<T>phải chứa bất kỳ loại được khai báo với IAppointmentProperties. Bằng cách khai báo loại có lớp bê tông bạn đã vi phạm hợp đồng đó (nó thực hiện a loại IAppointmentProperties nhưng không phải bất kỳ loại nào).

+0

Có, bạn đã đúng. Nhưng tôi muốn khai báo lớp 'MyAppointment' bằng cách sử dụng kiểu' MyAppointmentProperties' cụ thể (nó không nằm trong ví dụ cho đến bây giờ, lời xin lỗi của tôi) để mở rộng các thuộc tính của 'IAppointmentProperties'. Có thể chỉ định một hợp đồng cho phép điều đó không? – Rens

+0

Bạn có thể khai báo với hai tham số kiểu generic chung thay vì lồng chúng: 'class AppointmentEntry : IAppointmentEntry nơi TAppointment: IAppointment ' Tuy nhiên, tôi cảnh báo bạn (như những người khác) không quá mức phân cấp kiểu của bạn trừ khi có một lý do rất thuyết phục để làm như vậy. –

+0

Cảm ơn bạn đã làm rõ. Tôi sẽ thấy để làm cho hệ thống phân cấp kiểu kém phức tạp hơn nữa – Rens

0

Nó sẽ có tác dụng nếu bạn tái xác định giao diện mẫu từ:

interface ICage<T> 

để

interface ICage<out T> 

(xin chú ý đến out từ khóa)

sau đó báo cáo kết quả sau đây là đúng:

ICage<IAnimal> cage = new Cage<Tiger>(); 
6

Đã có một câu trả lời rất hay của Eric. Chỉ muốn nhân cơ hội này để nói về số Invariance, Hiệp phương saiContravariance tại đây.

Đối với các định nghĩa xin vui lòng xem https://msdn.microsoft.com/en-us/library/dd799517(v=vs.110).aspx


Hãy nói rằng có một sở thú.

abstract class Animal{} 
abstract class Bird : Animal{} 
abstract class Fish : Animal{} 
class Dove : Bird{} 
class Shark : Fish{} 

Sở thú đang di dời, vì vậy động vật của nó cần được di chuyển từ vườn thú cũ sang vườn thú mới.

bất biến

Trước khi chúng tôi di chuyển chúng, chúng ta cần phải đưa các động vật vào thùng chứa khác nhau. Các thùng chứa đều thực hiện các hoạt động tương tự: đặt một con vật vào trong đó hoặc lấy một con vật ra khỏi nó.

interface IContainer<T> where T : Animal 
{ 
    void Put(T t); 
    T Get(int id); 
} 

Rõ ràng cho cá chúng ta cần một chiếc xe tăng:

class FishTank<T> : IContainer<T> where T : Fish 
{ 
    public void Put(T t){} 
    public T Get(int id){return default(T);} 
} 

Vì vậy, các cá thể được đưa vào và lấy ra từ các bể (hy vọng vẫn còn sống):

IContainer<Fish> fishTank = new FishTank<Fish>(); //Invariance, the two types have to be the same 
fishTank.Put(new Shark());   
var fish = fishTank.Get(8); 

Giả sử chúng tôi được phép để thay đổi thành IContainer<Animal>, sau đó bạn có thể vô tình đặt một con chim bồ câu trong bể, mà thảm họa lãng quên sẽ xảy ra.

IContainer<Animal> fishTank = new FishTank<Fish>(); //Wrong, some animal can be killed 
fishTank.Put(new Shark()); 
fishTank.Put(new Dove()); //Dove will be killed 

Contravariance

Nhằm nâng cao cao hiệu quả, các dicides đội ngũ quản lý vườn thú để tách tải và xử lý (quản lý luôn luôn làm điều này) dỡ bỏ. Vì vậy, chúng tôi có hai hoạt động riêng biệt, một cho tải chỉ, dỡ bỏ khác.

interface ILoad<in T> where T : Animal 
{ 
    void Put(T t); 
} 

Sau đó, chúng tôi có một lồng chim:

class BirdCage<T> : ILoad<T> where T : Bird 
{ 
    public void Put(T t) 
    { 
    } 
} 

ILoad<Bird> normalCage = new BirdCage<Bird>(); 
normalCage.Put(new Dove()); //accepts any type of birds 

ILoad<Dove> doveCage = new BirdCage<Bird>();//Contravariance, Bird is less specific then Dove 
doveCage.Put(new Dove()); //only accepts doves 

Hiệp phương sai

Các trong sở thú mới, chúng tôi có một đội ngũ cho dỡ vật.

interface IUnload<out T> where T : Animal 
{ 
    IEnumerable<T> GetAll(); 
} 

class UnloadTeam<T> : IUnload<T> where T : Animal 
{ 
    public IEnumerable<T> GetAll() 
    { 
     return Enumerable.Empty<T>(); 
    } 
} 

IUnload<Animal> unloadTeam = new UnloadTeam<Bird>();//Covariance, since Bird is more specific then Animal 
var animals = unloadTeam.GetAll(); 

Từ quan điểm của nhóm, không quan trọng những gì bên trong, họ chỉ dỡ vật nuôi ra khỏi các vật chứa.