2010-01-09 47 views
44

Nhìn vào ví dụ sau đây (một phần lấy từ MSDN Blog):C# vấn đề sai: Gán Danh sách <Derived> như Danh sách <Base>

class Animal { } 
class Giraffe : Animal { } 

static void Main(string[] args) 
{ 
    // Array assignment works, but... 
    Animal[] animals = new Giraffe[10]; 

    // implicit... 
    List<Animal> animalsList = new List<Giraffe>(); 

    // ...and explicit casting fails 
    List<Animal> animalsList2 = (List<Animal>) new List<Giraffe>(); 
} 

Đây có phải là một vấn đề hiệp phương sai? Điều này sẽ được hỗ trợ trong bản phát hành C# trong tương lai và có cách giải quyết thông minh nào không (chỉ sử dụng .NET 2.0)?

Trả lời

95

Vâng điều này chắc chắn sẽ không được hỗ trợ trong C# 4. Có một vấn đề cơ bản:

List<Giraffe> giraffes = new List<Giraffe>(); 
giraffes.Add(new Giraffe()); 
List<Animal> animals = giraffes; 
animals.Add(new Lion()); // Aargh! 

Giữ hươu cao cổ an toàn: chỉ cần nói không với đúng không an toàn.

Phiên bản mảng hoạt động vì mảng làm phương sai loại tham chiếu hỗ trợ, với kiểm tra thời gian thực hiện. Điểm của generics là cung cấp biên dịch thời gian loại an toàn.

Trong C# 4, sẽ có hỗ trợ cho an toàn phương sai chung, nhưng chỉ dành cho giao diện và đại biểu. Vì vậy, bạn sẽ có thể làm:

Func<string> stringFactory =() => "always return this string"; 
Func<object> objectFactory = stringFactory; // Safe, allowed in C# 4 

Func<out T>hiệp biến trong TT chỉ được sử dụng ở một vị trí đầu ra. Hãy so sánh điều đó với Action<in T> đó là contravariant trong TT chỉ được sử dụng ở một vị trí đầu vào đó, làm cho an toàn này:

Action<object> objectAction = x => Console.WriteLine(x.GetHashCode()); 
Action<string> stringAction = objectAction; // Safe, allowed in C# 4 

IEnumerable<out T> là hiệp biến là tốt, làm đúng điều này trong C# 4, như chỉ ra bởi những người khác:

IEnumerable<Animal> animals = new List<Giraffe>(); 
// Can't add a Lion to animals, as `IEnumerable<out T>` is a read-only interface. 

Xét về làm việc xung quanh này trong tình huống của bạn trong C# 2, bạn cần phải duy trì một danh sách, hoặc bạn sẽ được hạnh phúc tạo ra một danh sách mới? Nếu được chấp nhận, List<T>.ConvertAll là bạn của bạn.

+0

+1 chỉ để thêm vào câu trả lời của Jon (không phải là anh ấy cần bất kỳ trợ giúp nào), theo ví dụ 'Func ' to 'Func ' và thực tế là 'T' là contravariant trong' Hành động'. 'Hành động = Hành động ' sẽ không hoạt động trong C# 4. –

+0

Cảm ơn bạn đã chỉ ra vấn đề an toàn loại. Điều đó thực sự hữu ích. – AndiDog

+0

Có gì sai với 'animals.Add (new Lion()); // Aargh! Nếu bạn có thể tạo một 'Sư tử' và sử dụng nó như là một 'Động vật', và nếu bạn đang sử dụng tất cả các phần tử trong' động vật' như 'Động vật', thì vấn đề là gì? – Jeff

11

Nó sẽ làm việc trong C# 4 cho IEnumerable<T>, vì vậy bạn có thể làm:

IEnumerable<Animal> animals = new List<Giraffe>(); 

Tuy nhiên List<T> không phải là một dự báo covarient, vì vậy bạn không thể gán danh sách như bạn đã làm ở trên kể từ khi bạn có thể làm điều này:

List<Animal> animals = new List<Giraffe>(); 
animals.Add(new Monkey()); 

Rõ ràng là không hợp lệ.

6

Về mặt số List<T>, tôi e rằng bạn không may mắn. Tuy nhiên, .NET 4.0/C# 4.0 thêm hỗ trợ cho các giao diện covariant/contravariant. Cụ thể, IEnumerable<T> hiện được định nghĩa là IEnumerable<out T>, có nghĩa là thông số loại hiện là covariant.

Điều này có nghĩa bạn có thể làm một cái gì đó như thế này trong C# 4.0 ...

// implicit casting 
IEnumerable<Animal> animalsList = new List<Giraffe>(); 

// explicit casting 
IEnumerable<Animal> animalsList2 = (IEnumerable<Animal>) new List<Giraffe>(); 

Lưu ý: Các loại Mảng cũng đã được hiệp biến (ít nhất là kể từ khi .NET 1.1).

Tôi nghĩ rằng đó là một sự xấu hổ mà hỗ trợ phương sai không được thêm vào cho IList<T> và các giao diện chung tương tự khác (hoặc các lớp chung chung thậm chí), nhưng tốt thôi, ít nhất chúng ta có thứ gì đó.

+7

Bạn không thể biến IList một cách an toàn một cách an toàn với chú thích phương sai của khai báo. –

4

Hiệp phương sai/đối xứng không thể được hỗ trợ trên các bộ sưu tập có thể thay đổi như những người khác đã đề cập vì không thể đảm bảo an toàn loại cả hai cách tại thời gian biên dịch; tuy nhiên, bạn có thể thực hiện chuyển đổi một chiều nhanh chóng trong C# 3.5, nếu đó là những gì bạn đang tìm kiếm:

List<Giraffe> giraffes = new List<Giraffe>(); 
List<Animal> animals = giraffes.Cast<Animal>().ToList(); 

Tất nhiên nó không giống nhau, nó không thực sự hiệp phương sai - bạn thực sự tạo một danh sách khác, nhưng đó là một "cách giải quyết" để nói.

Trong .NET 2.0, bạn có thể tận dụng lợi thế của mảng hiệp phương sai để đơn giản hóa mã:

List<Giraffe> giraffes = new List<Giraffe>(); 
List<Animal> animals = new List<Animal>(giraffes.ToArray()); 

Nhưng hãy lưu ý rằng bạn đang thực sự tạo ra bộ sưu tập hai mới đây.

+0

Tôi nghĩ rằng đây là những cách giải quyết khá tốt cho ứng dụng đơn giản của tôi. Ít nhất chúng cũng tốt cho khả năng đọc - không phải cho hiệu suất. – AndiDog

+0

@JohnAskew: Tôi không chắc điểm của bạn ở đây là gì - không có phương thức 'Cast' nào trên' IList ', đó là phương thức mở rộng' Có thể đếm được .Cast ', lấy một' IEnumerable '. thành phần 'T2' và sau đó trả về nó (dưới dạng một' IEnumerable ', không phải là' IList '). Nó không liên quan gì đến 'IList ', lưu lại thực tế là 'IList ' xảy ra để kế thừa từ 'IEnumerable ' và do đó hỗ trợ phương thức mở rộng 'Enumerable '. Không có hiệp ước hiệp phương nào ở đó cả. – Aaronaught

+0

Rất tiếc ... Tôi đã nhấp xóa thay vì chỉnh sửa: -S –

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