2012-06-26 47 views
11

Tôi có một phương thức factory đơn giản cung cấp một cá thể triển khai cụ thể dựa trên một tham số kiểu generic được cung cấp. Nếu các lớp cụ thể kế thừa từ một lớp cơ sở trừu tượng chung với tham số kiểu tôi không thể truyền chúng. Trình biên dịch cho tôi biết Error 2 Cannot convert type 'Car' to 'VehicleBase<T>'. Nó hoạt động tốt nếu tôi thay thế lớp trừu tượng cho một giao diện có cùng tham số kiểu, hoặc nếu tôi loại bỏ tham số kiểu generic từ lớp trừu tượng.Không thể truyền kiểu dẫn xuất đến lớp trừu tượng cơ sở với tham số kiểu

interface IWheel 
{ 
} 

class CarWheel : IWheel 
{ 
} 

abstract class VehicleBase<T> 
{ 
} 

class Car : VehicleBase<CarWheel> 
{ 
} 

class VehicleFactory 
{ 
    public static VehicleBase<T> GetNew<T>() 
    { 
     if (typeof(T) == typeof(CarWheel)) 
     { 
      return (VehicleBase<T>)new Car(); 
     } 
     else 
     { 
      throw new NotSupportedException(); 
     } 
    } 
} 

Điều này không biên dịch trên (VehicleBase<T>)new Car(). Đây có phải là một khiếm khuyết trình biên dịch, hay đây có thể là một quyết định thiết kế có chủ ý để xử lý các lớp trừu tượng và các giao diện với các tham số kiểu khác nhau không?

Để giải quyết sự cố, tôi luôn có thể tạo lớp trừu tượng triển khai giao diện và sử dụng giá trị trả về cho phương thức nhà máy của mình, nhưng tôi vẫn muốn biết tại sao hành vi này xảy ra.

Trả lời

5

Đây không phải là lỗi trình biên dịch cũng như quyết định có chủ ý. Loại tham số trên các lớp chung là neither covariant nor contravariant, tức là không có mối quan hệ thừa kế giữa các chuyên môn của cùng một lớp chung chung. Từ tài liệu:

Trong .NET Framework phiên bản 4, thông số kiểu biến thể bị giới hạn trong giao diện chung và loại đại biểu chung.

Có nghĩa là đoạn mã sau sẽ biên dịch, vì nó sử dụng một giao diện thay vì một lớp trừu tượng:

interface IWheel 
{ 
} 

class CarWheel : IWheel 
{ 
} 

interface IVehicleBase<T> 
{ 
} 

class Car : IVehicleBase<CarWheel> 
{ 
} 

class VehicleFactory 
{ 
    public static IVehicleBase<T> GetNew<T>() 
    { 
     if (typeof(T) == typeof(CarWheel)) 
     { 
      return (IVehicleBase<T>)new Car(); 
     } 
     else 
     { 
      throw new NotSupportedException(); 
     } 
    } 
} 

Kiểm tra "Covariance and Contravariance in Generics" để biết thêm và ví dụ.

Ngoài ra còn có một Covariance and Contravariance FAQ tại C# FAQ blog với biết thêm, và một 11-part series! về chủ đề bởi Eric Lippert

+0

Đây là giải pháp mà tôi đã sử dụng. Cảm ơn bạn đã nhắc tôi về phương sai trong giao diện/đại biểu mặc dù - điều đó có ý nghĩa và trả lời câu hỏi của tôi. – dahvyd

+0

Cảm ơn bạn đã nhắc tôi. Nó quá dễ dàng để quên khi bạn chỉ cần có được một số mã ra cửa –

9

Đó không phải là chứng minh, bởi vì mã chung cần phải làm việc (với IL giống nhau) cho mỗi thể T, và không có gì để nói là Car : VehicleBase<float>, ví dụ. Trình biên dịch không phân tích quá mức thực tế rằng các lợn nái kiểm tra if rằng TCarWheel - trình kiểm tra tĩnh xử lý mỗi câu lệnh riêng biệt, nó không cố gắng hiểu nguyên nhân và ảnh hưởng của điều kiện.

Để buộc nó, đúc để object ở giữa:

return (VehicleBase<T>)(object)new Car(); 

Tuy nhiên! Cách tiếp cận của bạn không thực sự "chung chung" như vậy.

+0

Âm thanh lý, nhưng tại sao sự khác biệt nếu tôi sử dụng một giao diện? Đây là những gì tôi không hiểu. – dahvyd

+0

Nó không chỉ là điều này không thể chứng minh được. Chỉ các giao diện chung mới có thể có các loại biến thể. Mã này sẽ không hoạt động ngay cả khi bạn đã chỉ định các ràng buộc kiểu thích hợp –

3

Điều này dường như làm việc:

return new Car() as VehicleBase<T>; 

My đoán lý do tại sao nó là như vậy:
Như trường hợp loại chung của VehicleBase<T> không liên quan, nó không thể được chứng minh rằng casting họ có thể làm việc:

enter image description here

Nếu T thuộc loại Blah, phôi sẽ không hoạt động. Bạn không thể quay trở lại đối tượng và sau đó lấy nhánh khác (không có nhiều thừa kế trong C# sau khi tất cả).

Bằng cách truyền trở lại trước object trước đó, bạn lại mở khả năng dàn diễn viên có thể hoạt động, vì vẫn có thể có đường dẫn xuống VehicleBase<CarWheel>. Dĩ nhiên, giao diện có thể xuất hiện ở bất kỳ nơi nào trong cây này bên dưới object, do đó cũng sẽ hoạt động.

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