2012-06-14 34 views
34

xem xét giả tạo, ví dụ tầm thường này:Tại sao Linq Cast này thất bại khi sử dụng ToList?

var foo = new byte[] {246, 127}; 
    var bar = foo.Cast<sbyte>(); 
    var baz = new List<sbyte>(); 
    foreach (var sb in bar) 
    { 
     baz.Add(sb); 
    } 
    foreach (var sb in baz) 
    { 
     Console.WriteLine(sb); 
    } 

Với sự kỳ diệu của Complement Hai của, -10 và 127 được in ra cửa sổ Console. Càng xa càng tốt. Những người có đôi mắt sắc sảo sẽ thấy rằng tôi đang lặp lại trên một số đếm và thêm nó vào một danh sách. Điều đó nghe có vẻ như ToList:

var foo = new byte[] {246, 127}; 
    var bar = foo.Cast<sbyte>(); 
    var baz = bar.ToList(); 
    //Nothing to see here 
    foreach (var sb in baz) 
    { 
     Console.WriteLine(sb); 
    } 

Ngoại trừ việc đó không hoạt động. Tôi nhận được ngoại lệ này: Loại

Ngoại lệ: System.ArrayTypeMismatchException

nhắn: Nguồn kiểu mảng không thể được gán cho loại mảng đích.

tôi thấy ngoại lệ này rất đặc biệt vì

  1. ArrayTypeMismatchException - Tôi không làm bất cứ điều gì với mảng, bản thân mình. Điều này có vẻ là một ngoại lệ nội bộ.
  2. Cast<sbyte> hoạt động tốt (như trong ví dụ đầu tiên), đó là khi sử dụng ToArray hoặc ToList sự cố thể hiện chính nó.

Tôi đang nhắm mục tiêu .NET v4 x86, nhưng điều tương tự cũng xảy ra trong 3.5.

Tôi không cần bất kỳ lời khuyên nào về cách giải quyết vấn đề, tôi đã quản lý để làm điều đó. Điều tôi muốn biết là tại sao hành vi này lại xảy ra ngay từ đầu?

EDIT:

thậm chí lạ, thêm một lựa chọn công bố vô nghĩa làm cho ToList để làm việc một cách chính xác:

var baz = bar.Select(x => x).ToList(); 
+0

Với 'Chọn' kết quả là' {-10, 127} '. Có sự cố truyền ở đây. Thông báo lỗi thú vị chắc chắn. – asawyer

+0

@Lieven yes, mà tôi thu thập được, bởi vì tại sao 'Select (x => x)' trước 'ToList' sửa nó? Đó là một phép chiếu vô nghĩa bởi vì điều tương tự được chiếu trở lại. – vcsjones

+2

Tôi có một lời giải thích ... nó chỉ mất một chút thời gian để viết ra. Câu hỏi tuyệt vời. –

Trả lời

26

Được rồi, điều này thực sự phụ thuộc vào một vài điểm kỳ quặc kết hợp:

  • Mặc dù trong C# bạn không thể truyền trực tiếp số byte[] đến số sbyte[], CLR cho phép nó:

    var foo = new byte[] {246, 127}; 
    // This produces a warning at compile-time, and the C# compiler "optimizes" 
    // to the constant "false" 
    Console.WriteLine(foo is sbyte[]); 
    
    object x = foo; 
    // Using object fools the C# compiler into really consulting the CLR... which 
    // allows the conversion, so this prints True 
    Console.WriteLine(x is sbyte[]); 
    
  • Cast<T>() tối ưu hóa như vậy là nếu nó nghĩ rằng nó không cần phải làm gì (thông qua một kiểm tra is như trên) nó sẽ trả về tham chiếu ban đầu - vì vậy mà đang xảy ra ở đây.

  • ToList() đại biểu đến constructor của List<T> dùng một IEnumerable<T>

  • Đó constructor được tối ưu hóa cho ICollection<T> sử dụng CopyTo ... và đó là gì đang thất bại.Dưới đây là một phiên bản mà không có phương pháp gọi khác hơn CopyTo:

    object bytes = new byte[] { 246, 127 }; 
    
    // This succeeds... 
    ICollection<sbyte> list = (ICollection<sbyte>) bytes; 
    
    sbyte[] array = new sbyte[2]; 
    
    list.CopyTo(array, 0); 
    

Bây giờ nếu bạn sử dụng một Select bất cứ lúc nào, bạn không kết thúc với một ICollection<T>, vì vậy nó đi qua hợp pháp (đối với CLR) byte/sbyte chuyển đổi cho từng phần tử, thay vì cố gắng sử dụng triển khai mảng CopyTo.

+2

Vâng, tôi nhận thấy rằng loại giá trị của thanh tại thời gian chạy là byte [], thay vì System.Linq.Enumerable.CastIterator . Cảm ơn vì đã giải thích! – JDB

+0

sau khi đọc câu hỏi và câu trả lời, có nghĩa là .ToList sẽ cần phải được sử dụng trong chuyển đổi loại an toàn không? – Turbot

+1

@Turbot: Tôi không hiểu nhận xét của bạn. Bạn có thể rephrase nó? –

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