An iteration variable of a different type than a collection?

I have a set of null ints.

Why does the compiler allow an iteration variable of type int not 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 } 

Of course, I should pay attention to what I tried, but it would be nice if the compiler talked me out :) Like here:

  foreach (bool i in collection) { // do sth } //Error 1 Cannot convert type 'int' to 'bool' 
+7
c # foreach nullable
source share
3 answers

Update

OK, initially I said: "the compiler adds castings to the foreach ." This is not entirely accurate: he will not always add castings. This is what really happens.

First of all, when you have this foreach :

 foreach (int x in collection) { } 

... here is the main 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 can hear you talking. What do you mean by [object] ? "

Here is what I mean. The foreach does not actually require an interface, which means that it is actually a bit magical. It only requires that the type of the object to be enumerated be provided by the GetEnumerator method, which in turn must provide an instance of some type that provides the MoveNext and Current properties.

So, I wrote [object] because the type e does not have to be an implementation of IEnumerator<int> or even IEnumerator - which also means that it is not necessary to implement IDisposable (hence the [dispose if necessary] ).

The part of the code that we care about to answer this question is the part in which I wrote [cast if possible] . It is clear that since the compiler does not require the actual implementation of IEnumerator<T> or IEnumerator , the e.Current type e.Current not be considered T , object or anything in between. Instead, the compiler determines the e.Current type based on the type returned by GetEnumerator at compile time. Then the following happens:

  • If the type is a local variable type ( x in the above example), direct assignment is used.
  • If the type is converted to the type of a local variable (by which I mean that a legitimate listing exists from e.Current to type x ), the listing is inserted.
  • Otherwise, the compiler will throw an error.

So, in the List<int?>.Enumerator script, we go to step 2, and the compiler sees that the List<int?>.Enumerator Property List<int?>.Enumerator type Current is of type int? which can be explicitly passed to int .

Thus, the string can be compiled in the equivalent:

 x = (int)e.Current; 

Now, what does the explicit statement for a Nullable<int> look like?

According to reflector:

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

So, the behavior described by Kent , as far as I can tell, is just compiler optimization: an explicit listing of (int)e.Current is built-in.

As a general answer to your question, I stick to my assertion that the compiler inserts into foreach loops if necessary.


Original answer

The compiler automatically adds casts if necessary in the foreach for the simple reason that before generics there was no IEnumerable<T> interface, only IEnumerable *. The IEnumerable interface provides an IEnumerator , which in turn provides access to the Current property of type object .

So, if the compiler didn’t do the throw for you, in the old days, the only way you could use foreach would be with a local variable of type object , which obviously would suck.

* And in fact, foreach does not require any interface at all - only the GetEnumerator method and associated type with MoveNext and a Current .

+3
source share

Since the C # compiler is looking for a 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 indicates how the Nullable<T> structure took root at run time.

+4
source share

The behavior you observe corresponds to section 8.8.4. Presentation of the foreach C # language specification. This section defines the semantics of the foreach as follows:

[...] The above steps, if successful, uniquely create a collection type C , an enumerator type E and an element type T A foreach form expression

 foreach (V v in x) embedded-statement 

then expand 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 example, the type of the collection will be List<int?> , The type of the enumerator will be List<int?>.Enumerator , and the type of the element will be int? .

If you fill out this information in the code snippet above, you will see that int? explicitly passed to int , calling Nullable<T> Explicit Conversion (Nullable<T> to T) . Executing this explicit drop operator, as described by Kent, simply returns the Nullable<T>.Value property.

+3
source share

All Articles