I have a collection of nullable ints.
Why does compiler allows to iteration variable be of type int not int? ?
List<int?> nullableInts = new List<int?>{1,2,3,null};
List<int&开发者_StackOverflow中文版gt; normalInts = new List<int>();
//Runtime exception when encounter null value
//Why not compilation exception?
foreach (int i in nullableInts)
{
//do sth
}
Of course I should pay attention to what I iterate through but it would be nice if compiler reprimanded me:) Like here:
foreach (bool i in collection)
{
// do sth
}
//Error 1 Cannot convert type 'int' to 'bool'
Because the C# compiler dereferences the Nullable<T>
for you.
If you write this code:
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());
}
The C# compiler produces:
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());
}
Note the explicit dereferencing of Nullable<int>.Value
. This is a testament to how ingrained the Nullable<T>
structure is in the runtime.
Update
OK, originally I said "the compiler adds casts to a foreach
loop." This isn't strictly accurate: it won't always add casts. Here's what's really happening.
First of all, when you have this foreach
loop:
foreach (int x in collection)
{
}
...here is the basic outline (in pseudo-C#) of what the compiler creates:
int x;
[object] e;
try
{
e = collection.GetEnumerator();
while (e.MoveNext())
{
x = [cast if possible]e.Current;
}
}
finally
{
[dispose of e if necessary]
}
What? I hear you saying. What do you mean by [object]
?"
Here's what I mean. The foreach
loop actually requires no interface, which means it's actually a little bit magical. It only requires that the type of object being enumerated exposes a GetEnumerator
method, which in turn must provide an instance of some type that provides a MoveNext
and a Current
property.
So I wrote [object]
because the type of e
does not necessarily have to be an implementation of IEnumerator<int>
, or even IEnumerator
-- which also means it doesn't necessarily have to implement IDisposable
(hence the [dispose if necessary]
part).
The part of the code we care about for the purpose of answering this question is the part where I wrote [cast if possible]
. Clearly, since the compiler doesn't require an actual IEnumerator<T>
or IEnumerator
implementation, the type of e.Current
cannot be assumed to be T
, object
or anything in between. Instead, the compiler determines the type of e.Current
based on the type returned by GetEnumerator
at compile time. Then the following happens:
- If the type is the type of the local variable (
x
in the above example), a straight assignment is used. - If the type is convertible to the type of the local variable (by which I mean, a legal cast exists from the type of
e.Current
to the type ofx
), a cast is inserted. - Otherwise, the compiler will raise an error.
So in the scenario of enumerating over a List<int?>
, we get to step 2 and the compiler sees that the List<int?>.Enumerator
type's Current
property is of type int?
, which can be explicitly cast to int
.
So the line can be compiled to the equivalent of this:
x = (int)e.Current;
Now, what does the explicit
operator look like for Nullable<int>
?
According to Reflector:
public static explicit operator T(T? value)
{
return value.Value;
}
So the behavior described by Kent is, as far as I can tell, simply a compiler optimization: the (int)e.Current
explicit cast is inlined.
As a general answer to your question, I stick by my assertion that the compiler inserts casts in a foreach
loop where needed.
Original Answer
The compiler automatically inserts casts where needed in a foreach
loop for the simple reason that before generics there was no IEnumerable<T>
interface, only IEnumerable
*. The IEnumerable
interface exposes an IEnumerator
, which in turn provides access to a Current
property of type object
.
So unless the compiler performed the cast for you, in olden times, the only way you could've used foreach
would've been with a local variable of type object
, which obviously would've sucked.
*And actually, foreach
doesn't require any interface at all -- only the GetEnumerator
method and an accompanying type with a MoveNext
and a Current
.
The behavior you are observing is according to section 8.8.4 The foreach statement of the C# language specification. This section defines the semantics of the foreach
statement as follows:
[...] The above steps, if successful, unambiguously produce a collection type
C
, enumerator typeE
and element typeT
. Aforeach
statement of the formforeach (V v in x) embedded-statement
is then expanded to:
{ E e = ((C)(x)).GetEnumerator(); try { V v; while (e.MoveNext()) { v = (V)(T)e.Current; embedded-statement } } finally { // Dispose e } }
According to the rules defined in the specification, in your sample the collection type would be List<int?>
, the enumerator type would be List<int?>.Enumerator
and the element type would be int?
.
If you fill this information into the above code snippet you will see that int?
is explicitly cast to int
by calling Nullable<T> Explicit Conversion (Nullable<T> to T)
. The implemenation of this explicit cast operator is, as described by Kent, to simply return the Nullable<T>.Value
property.
精彩评论