2010-09-14 29 views
7

Tôi có một bộ sưu tập các int vô giá trị.Biến lặp của loại khác với bộ sưu tập?

Tại sao trình biên dịch cho phép biến lặp lại thuộc loại int không int??

 List<int?> nullableInts = new List<int?>{1,2,3,null}; 
     List<int> normalInts = new List<int>(); 


     //Runtime exception when encounter null value 
     //Why not compilation exception? 
     foreach (int i in nullableInts) 
     { 
     //do sth 
     } 

Tất nhiên là tôi nên chú ý đến những gì tôi lặp thông qua nhưng nó sẽ được tốt đẹp nếu trình biên dịch khiển trách tôi :) Giống như ở đây:

 foreach (bool i in collection) 
     { 
      // do sth 
     } 

     //Error 1 Cannot convert type 'int' to 'bool' 
+0

Lỗi này có vẻ bình thường đối với tôi. Bạn có thể chuyển đổi từ int? để int, nhưng bạn không thể chuyển đổi một int thành bool. –

+0

@Adrian Tôi không nghĩ rằng giải thích này là chính xác. Nếu tôi có chuỗi thay vì bool thì sao? Int có thể được chuyển đổi thành chuỗi nhưng trình biên dịch sẽ stil phản đối. – nan

+1

Int không thể được chuyển đổi thành chuỗi. Không có chuyển đổi ngầm hoặc rõ ràng nào được xác định. Nếu u đang đề cập đến phương thức ToString() thì đó là một điều khác. –

Trả lời

3

Cập nhật

OK, ban đầu tôi đã nói "trình biên dịch thêm phôi vào một vòng lặp foreach." Đây không phải là đúng chính xác: nó sẽ không luôn luôn thêm phôi. Đây là những gì thực sự xảy ra.

Trước hết, khi bạn có foreach vòng lặp này:

foreach (int x in collection) 
{ 
} 

... đây là phác thảo cơ bản (trong pseudo-C#) về những gì các trình biên dịch tạo ra:

int x; 
[object] e; 
try 
{ 
    e = collection.GetEnumerator(); 
    while (e.MoveNext()) 
    { 
     x = [cast if possible]e.Current; 
    } 
} 
finally 
{ 
    [dispose of e if necessary] 
} 

Cái gì? Tôi nghe bạn nói. Ý anh là gì bởi [object]?"

Dưới đây là những gì tôi có ý nghĩa. Các vòng lặp foreach thực sự đòi hỏi không có giao diện, có nghĩa là nó thực sự là một chút huyền diệu. Nó chỉ đòi hỏi rằng các loại đối tượng được liệt kê cho thấy một GetEnumerator phương pháp, do đó phải cung cấp một thể hiện của một số loại hình cung cấp một MoveNext và một tài sản Current.

vì vậy, tôi đã viết [object] vì loại e không nhất thiết phải là một thực hiện IEnumerator<int>, hoặc thậm chí IEnumerator - cũng có nghĩa là nó không nhất thiết phải thực hiện IDisposable (do đó phần [dispose if necessary]).

Phần mã mà chúng tôi quan tâm với mục đích trả lời câu hỏi này là phần tôi viết [cast if possible]. Rõ ràng, vì trình biên dịch không yêu cầu thực thi IEnumerator<T> hoặc IEnumerator thực tế, loại e.Current không thể được giả định là T, object hoặc bất kỳ thứ gì ở giữa.Thay vào đó, trình biên dịch xác định loại e.Current dựa trên loại trả về GetEnumerator tại thời gian biên dịch. Sau đó, những điều sau đây sẽ xảy ra:

  1. Nếu loại kiểu của biến cục bộ (x trong ví dụ trên), một giao thẳng được sử dụng.
  2. Nếu loại là chuyển đổi thành loại biến cục bộ (theo ý tôi, một dàn diễn viên pháp lý tồn tại từ loại e.Current đến loại x), một diễn viên được chèn vào.
  3. Nếu không, trình biên dịch sẽ gây ra lỗi.

Vì vậy, trong kịch bản liệt kê trên một List<int?>, chúng tôi nhận lại bước 2 và trình biên dịch thấy rằng tài sản Current các List<int?>.Enumerator loại là loại int?, có thể được đúc một cách rõ ràng để int.

Vì vậy, các dòng có thể được biên dịch để tương đương với điều này:

x = (int)e.Current; 

Bây giờ, những gì hiện giao diện explicit điều hành như thế nào cho Nullable<int>?

Theo Reflector:

public static explicit operator T(T? value) 
{ 
    return value.Value; 
} 

Vì vậy the behavior described by Kent là, như xa như tôi có thể nói, chỉ đơn giản là một tối ưu hóa trình biên dịch: các diễn viên rõ ràng (int)e.Current được inlined.

Như một câu trả lời chung cho câu hỏi của bạn, tôi gắn bó với xác nhận của tôi rằng trình biên dịch chèn phôi vào vòng lặp foreach khi cần.


gốc trả lời

Trình biên dịch sẽ tự động chèn phôi nơi cần thiết trong một vòng lặp foreach với lý do đơn giản rằng trước khi Generics không có giao diện IEnumerable<T>, chỉ IEnumerable *. Giao diện IEnumerable thể hiện số IEnumerator, do đó cung cấp quyền truy cập vào thuộc tính Current thuộc loại object. Vì vậy, trừ khi trình biên dịch thực hiện các diễn viên cho bạn, trong thời cổ đại, cách duy nhất bạn có thể đã sử dụng foreach đã có được với một biến địa phương của loại object, mà rõ ràng là sẽ sucked.

* Và thực tế, foreach doesn't require any interface at all - chỉ có phương thức GetEnumerator và loại đi kèm với MoveNextCurrent.

+0

Theo mặc định, sẽ không được gọi là 'GetEnumerator' của' IEnumerable ', làm cho' foreach' "chung chung"? – strager

+0

@strager: Có, nhưng vẫn sẽ có một dàn diễn viên. – SLaks

+0

@ Slaks: không có diễn viên nào và có một người sẽ phủ nhận một số lợi ích hiệu suất của generics.Kiểm tra bài đăng của tôi. –

4

Bởi vì biên dịch C# dereferences các Nullable<T> cho bạn.

Nếu bạn viết mã này:

 var list = new List<int?>() 
     { 
      1, 
      null 
     }; 

     foreach (int? i in list) 
     { 
      if (!i.HasValue) 
      { 
       continue; 
      } 

      Console.WriteLine(i.GetType()); 
     } 

     foreach (int i in list) 
     { 
      Console.WriteLine(i.GetType()); 
     } 

Các biên dịch C# sản xuất:

foreach (int? i in list) 
{ 
    if (i.HasValue) 
    { 
     Console.WriteLine(i.GetType()); 
    } 
} 
foreach (int? CS$0$0000 in list) 
{ 
    Console.WriteLine(CS$0$0000.Value.GetType()); 
} 

Lưu ý dereferencing rõ ràng của Nullable<int>.Value. Đây là minh chứng cho cách cấu trúc Nullable<T> trong thời gian chạy.

+1

Liên kết bạn đã cung cấp ghi rõ ràng rằng toán tử là 'tường minh'. Tui bỏ lỡ điều gì vậy...? – strager

+0

@strager: không, bạn nói đúng. Đó là 'T' ->' Nullable 'đó là ngầm định. Đã cập nhật câu trả lời của tôi. –

+0

"Đây là minh chứng cho cách cấu trúc' Nullable 'trong thời gian chạy." Tôi không thấy những gì này đã làm với thời gian chạy ở tất cả. Tôi nghĩ câu trả lời của bạn là chính xác, ngoại trừ kết luận của bạn. – strager

3

Hành vi bạn đang quan sát theo phần 8.8.4 Tuyên bố foreach của đặc tả ngôn ngữ C#. Phần này xác định ngữ nghĩa của tuyên bố foreach như sau:

[...] Các bước trên, nếu thành công, tạo ra một loại bộ sưu tập rõ ràng C, loại điều tra E và loại phần tử T. Một tuyên bố foreach dạng

foreach (V v in x) embedded-statement 

sau đó được mở rộng để:

{ 
    E e = ((C)(x)).GetEnumerator(); 
    try { 
     V v; 
     while (e.MoveNext()) { 
      v = (V)(T)e.Current; 
      embedded-statement 
     } 
    } 
    finally { 
     // Dispose e 
    } 
} 

Theo các quy tắc được định nghĩa trong đặc tả, trong mẫu của bạn loại bộ sưu tập sẽ List<int?>, các loại điều tra số sẽ là List<int?>.Enumerator và loại phần tử sẽ là int?.

Nếu bạn điền thông tin này vào đoạn mã trên, bạn sẽ thấy rằng int? được truyền rõ ràng tới int bằng cách gọi Nullable<T> Explicit Conversion (Nullable<T> to T). Việc triển khai toán tử cast rõ ràng này, như được mô tả bởi Kent, đơn giản trả về thuộc tính Nullable<T>.Value.

+0

Rất tiếc, tôi đã dành quá nhiều thời gian để cập nhật câu trả lời của mình để nhận ra bạn về cơ bản đã bao phủ cùng một nền tảng! Một điều, mặc dù: Tôi nghĩ rằng * kiểu liệt kê * trong ví dụ này thực sự là 'Danh sách . Số đếm', * không *' IEnumerator '. Tôi có đúng không? –

+0

@Dan Tao: Vâng, bạn nói đúng (khi tôi hiểu thông số kỹ thuật). –

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