Aktualizacja
OK, pierwotnie powiedziałem "kompilator dodaje rzuca do foreach
pętli". Nie jest to dokładne dopasowanie: nie będzie zawsze dodawać rzutów. Oto, co się naprawdę dzieje.
Przede wszystkim, kiedy masz ten foreach
pętlę:
foreach (int x in collection)
{
}
... tutaj jest podstawowy zarys (w pseudo-C#), co powoduje kompilator:
int x;
[object] e;
try
{
e = collection.GetEnumerator();
while (e.MoveNext())
{
x = [cast if possible]e.Current;
}
}
finally
{
[dispose of e if necessary]
}
Co? Słyszę, że mówisz. Co masz na myśli przez [object]
?”
Oto co mam na myśli. Pętla foreach
rzeczywiście wymaga żadnego interfejsu, co oznacza, że to rzeczywiście trochę magiczne. Wymaga jedynie, że typ obiektu wyliczone odsłania GetEnumerator
metoda, która z kolei musi zapewnić wystąpienie pewnego rodzaju, który zapewnia MoveNext
i właściwość Current
.
Więc napisałem [object]
ponieważ typ e
niekoniecznie muszą być implementacja IEnumerator<int>
lub nawet IEnumerator
- co oznacza również, że nie musi koniecznie wykonywać IDisposable
(stąd część [dispose if necessary]
).
Częścią kodu, na którym zależy nam w celu udzielenia odpowiedzi na to pytanie, jest część, w której napisałem: [cast if possible]
. Oczywiście, ponieważ kompilator nie wymaga rzeczywistej implementacji IEnumerator<T>
lub IEnumerator
, nie można przyjąć, że typ e.Current
to T
, object
lub cokolwiek pomiędzy.Zamiast tego kompilator określa typ e.Current
na podstawie typu zwróconego przez GetEnumerator
w czasie kompilacji. Następnie wykonywane są następujące kroki:
- Jeżeli typ jest typ zmiennej lokalnej (
x
w powyższym przykładzie), prosty zadanie jest używany.
- Jeśli typ to zamienny na typ zmiennej lokalnej (przez co mam na myśli, akt prawny istnieje od typu
e.Current
do typu x
), włożona jest obsada.
- W przeciwnym razie kompilator zgłosi błąd.
Więc w scenariuszu wyliczanie nad List<int?>
, mamy do kroku 2 i kompilator widzi, że Current
własność List<int?>.Enumerator
Type jest typu int?
, które mogą być wyraźnie oddane do int
.
Więc linia może zostać skompilowany do równowartości to:
x = (int)e.Current;
Teraz, co robi wygląd explicit
operator jak dla Nullable<int>
?
Według Reflektor:
public static explicit operator T(T? value)
{
return value.Value;
}
Więc the behavior described by Kent jest, o ile mogę powiedzieć, po prostu optymalizacja kompilatora: the (int)e.Current
wyraźny obsada jest inlined.
Jako ogólną odpowiedź na twoje pytanie, trzymam się mojego stwierdzenia, że kompilator wstawia odlewy w pętli foreach
tam, gdzie jest to konieczne.
Original Odpowiedź
Kompilator automatycznie wstawia rzuca gdzie potrzebne w foreach
pętli dla tego prostego powodu, że przed generycznych nie było IEnumerable<T>
interfejs, tylko IEnumerable
*. Interfejs IEnumerable
udostępnia IEnumerator
, który z kolei zapewnia dostęp do właściwości Current
typu object
.
Więc jeśli kompilator nie wykonał obsady dla ciebie, w dawnych czasach, jedynym sposobem, w jaki mogłeś użyć foreach
, byłaby zmienna lokalna typu object
, która oczywiście by się zassała.
* I rzeczywiście, foreach
doesn't require any interface at all - tylko metoda GetEnumerator
i towarzyszący typ z MoveNext
i Current
.
Ten błąd wygląda normalnie. Możesz przekonwertować z int? do int, ale nie możesz przekonwertować int do bool. –
@Adrian Nie sądzę, że to wyjaśnienie jest poprawne. Co gdybym miał string zamiast bool? Int może zostać przekonwertowane na ciąg znaków, ale kompilator będzie protestował. – nan
Int nie może zostać przekonwertowany na ciąg. Nie zdefiniowano żadnej niejawnej ani jawnej konwersji. Jeśli używasz metody ToString(), to jest inna rzecz. –