2009-03-11 8 views
5

Gần đây tôi đã gặp sự cố khi cố gắng AddRange (IEnumerable) vào Danh sách. Có lẽ là một vấn đề cổ điển, nhưng tôi chưa thực sự hiểu nó.C# Không thể chuyển đổi từ IEnumerable <Base> thành IEnumerable <Derived>

Tôi hiểu rằng các phương pháp mong đợi tham số Danh sách không hài lòng với Danh sách, vì chúng có thể cố thêm một Cơ sở vào Danh sách, điều này rõ ràng là không thể.

Nhưng nếu tôi nhận được điều này một cách chính xác, vì IEnumerables tự mình không thể thay đổi, nó nên làm việc trong trường hợp này.

Mã i nghĩ đến ngoại hình như thế này:

class Foo 
{ 
} 

class Bar : Foo 
{ 
} 

class FooCol 
{ 
    private List<Foo> m_Foos = new List<Foo>(); 

    public void AddRange1(IEnumerable<Foo> foos) 
    { 
     m_Foos.AddRange (foos); // does work 
    } 

    public void AddRange2<T>(IEnumerable<T> foos) where T : Foo 
    { 
     m_Foos.AddRange (foos); // does not work 
    } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     FooCol fooCol = new FooCol(); 

     List<Foo> foos = new List<Foo>(); 
     List<Bar> bars = new List<Bar>(); 

     fooCol.AddRange1 (foos); // does work 
     fooCol.AddRange1 (bars); // does not work 

     fooCol.AddRange2 (foos); // does work 
     fooCol.AddRange2 (bars); // does work 
    } 
} 

tôi đã cố gắng để vượt qua một gợi ý để trình biên dịch trong phương pháp AddRange2, nhưng điều này chỉ chuyển đến vấn đề xung quanh.

Cách suy nghĩ của tôi có thiếu sót không? Đây có phải là giới hạn của ngôn ngữ hay là do thiết kế?

IIRC, hỗ trợ cho loại hoạt động này đã được thêm vào Java 1.5, vì vậy có thể nó sẽ được thêm vào C# tại một số điểm trong tương lai, quá ...?

+0

Sao y các câu hỏi khác nhau, bao gồm http://stackoverflow.com/questions/632399 –

+0

Thực tế, tôi xin lỗi. Google-jutsu của tôi vẫn cần cải thiện. – mafu

+0

Không sao cả. Nó sẽ là tốt đẹp để có một câu hỏi thường gặp cho mỗi thẻ, IMO ... –

Trả lời

18

Đây là hiệp phương sai và sẽ được sửa trong C# 4.0/.NET 4.0. Hiện tại, tùy chọn chung là câu trả lời hay nhất (đối với IEnumerable<T> - not IList<T> etc).

Nhưng trong phương pháp chung, bạn phải suy nghĩ theo điều khoản của T. Bạn cũng có thể sử dụng Cast<T> hoặc OfType<T> với LINQ để đạt được điều gì đó tương tự.

+1

Cảm ơn bạn về thuật ngữ mới, hiệp phương sai. Không biết về sự tồn tại. – Sung

0

Có workaround với phương pháp khuyến nông:

public static IEnumerable<TBase> ToBaseEnumerable<TBase, TDerived>(this IEnumerable<TDerived> items) where TDerived : TBase { 
    foreach(var item in items) { 
     yield return item; 
    } 
} 
... 
IEnumerable<Employee> employees = GetEmployees(); //Emplyoee derives from Person 
DoSomethingWithPersons(employees.ToBaseEnumerable<Person, Employee>()); 

nhưng "< Person, Employee>" là chút lúng túng: /.

+0

Truyền () sẽ đơn giản hơn ... –

2

Trong C# 3.0, bạn có thể sử dụng phương thức mở rộng "Truyền". Nếu bạn nhập System.Linq và sau đó sử dụng mã này:

public void AddRange2<T>(IEnumerable<T> foos) where T : Foo 
{ 
    m_Foos.AddRange (foos.Cast<Foo>()); 
} 

Sau đó, nó sẽ phù hợp với bạn.

0

Giải pháp truyền của khóa học có thể tạo ra ngoại lệ cho lớp học. Người đã đăng công việc mở rộng có thể liệt kê xung quanh nói rằng điều đó thật khó xử. tôi đã đưa ra một giải pháp mà chỉ là một nửa như vụng về, không biết nếu tôi sẽ sử dụng nó:

public static class UpTo<T> 
{ 
    public static IEnumerable<T> From<F>(IEnumerable<F> source) where F:T 
    { 
     // this cast is guaranteed to work 
     return source.Select(f => (T) f); 
    } 
} 

Cách sử dụng:

động vật có vú IEnumerable = tối đa <Mammal> .Từ (cũi. Chó)