2012-02-25 5 views
28

tôi đã viết phương pháp này mở rộng (trong đó biên dịch):Flatten IEnumerable <IEnumerable <>>; hiểu Generics

public static IEnumerable<J> Flatten<T, J>(this IEnumerable<T> @this) 
              where T : IEnumerable<J> 
{ 
    foreach (T t in @this) 
     foreach (J j in t) 
      yield return j; 
} 

Đoạn code dưới đây gây ra một lỗi thời gian biên dịch (không có phương pháp phù hợp được tìm thấy), tại sao?:

IEnumerable<IEnumerable<int>> foo = new int[2][]; 
var bar = foo.Flatten(); 

Nếu tôi thực hiện phần mở rộng như dưới đây, tôi nhận được không có lỗi thời gian biên dịch:

public static IEnumerable<J> Flatten<J>(this IEnumerable<IEnumerable<J>> @this) 
{ 
    foreach (IEnumerable<J> js in @this) 
     foreach (J j in js) 
      yield return j; 
} 

Chỉnh sửa (2): Câu hỏi này tôi xem xét trả lời, nhưng nó lớn lên một câu hỏi liên quan quá tải độ phân giải và loại hạn chế. Câu hỏi này tôi đặt ở đây: Why aren't type constraints part of the method signature?

+1

Chỉnh sửa của bạn không hoạt động vì bạn có quá nhiều vùng lân cận. 'foo.Flatten , int>();' sẽ hoạt động. – dlev

Trả lời

65

Trước tiên, bạn không cần Flatten(); phương thức đó đã tồn tại và được gọi là SelectMany(). Bạn có thể sử dụng nó như sau:

IEnumerable<IEnumerable<int>> foo = new [] { new[] {1, 2}, new[] {3, 4} }; 
var bar = foo.SelectMany(x => x); // bar is {1, 2, 3, 4} 

Thứ hai, nỗ lực đầu tiên của bạn không hoạt động vì suy luận kiểu chung chỉ hoạt động dựa trên các đối số cho phương pháp chứ không phải ràng buộc chung chung với phương pháp. Vì không có đối số nào trực tiếp sử dụng tham số chung J, công cụ suy luận kiểu không thể đoán được J là gì và do đó không nghĩ rằng phương pháp của bạn là một ứng cử viên.

Chỉnh sửa để xem cách SelectMany() giải quyết vấn đề này: yêu cầu đối số Func<TSource, TResult> bổ sung. Điều đó cho phép loại công cụ suy luận để xác định cả hai loại chung chung, vì chúng đều có sẵn chỉ dựa trên các đối số được cung cấp cho phương thức.

+1

@Daryl: Vì nó phải là 'Flatten , int> (foo)' – BrokenGlass

+2

@Daryl Ràng buộc chung không được coi là một phần của chữ ký phương thức; cho * cách khác *, xem liên kết này: http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx – dlev

+1

@Daryl : Đừng - chắc chắn có một đường cong học tập ở đây, đây không phải là khía cạnh dễ nhất của C# để hiểu.Chỉ cần cố gắng để làm chủ nó đặt bạn trước 95% của phần còn lại đã được ;-) – BrokenGlass

13

Câu trả lời của dlev là tốt; Tôi chỉ nghĩ rằng tôi muốn thêm một chút thông tin.

Cụ thể, tôi lưu ý rằng bạn đang cố gắng sử dụng Generics để thực hiện một loại hiệp phương sai trên IEnumerable<T>. Trong C# 4 trở lên, IEnumerable<T> đã là biến thể.

Ví dụ thứ hai của bạn minh họa điều này. Nếu bạn có

List<List<int>> lists = whatever; 
foreach(int x in lists.Flatten()) { ... } 

sau đó gõ suy luận sẽ lý do đó List<List<int>> là mui trần để IE<List<int>>, List<int> là mui trần để , và do đó, vì hiệp phương sai, IE<List<int>> là mui trần để IE<IE<int>>. Điều đó mang lại suy luận kiểu gì đó để tiếp tục; nó có thể suy ra rằng T là int, và mọi thứ đều tốt.

Điều này không hiệu quả trong C# 3. Cuộc sống khó hơn một chút trong thế giới không hiệp phương sai nhưng bạn có thể sử dụng phương pháp mở rộng Cast<T> một cách thận trọng.