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:
- Nếu loại là kiểu của biến cục bộ (
x
trong ví dụ trên), một giao thẳng được sử dụng.
- 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.
- 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 MoveNext
và Current
.
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. –
@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
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. –