views:

104

answers:

3

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> 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'
+4  A: 

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.

HTH,
Kent

Kent Boogaart
The link you provided explicitly writes that the operator is `explicit`. Am I missing something...?
strager
@strager: nope, you're right. It's `T` -> `Nullable<T>` that is implicit. Have updated my answer.
Kent Boogaart
"This is a testament to how ingrained the `Nullable<T>` structure is in the runtime." I don't see what this has to do with the runtime at all. I think your answer is correct except your conclusion.
strager
@strager: as long as we're picking nits: it's not a conclusion, it's an observation related to the conclusion. The conclusion was that the compiler is injecting an explicit dereferencing of the `Nullable<T>.Value` property. The fact that the C# compiler has intimate knowledge of `Nullable<T>` speaks to its special status in the platform.
Kent Boogaart
While the info in this answer is useful, I feel that your opening statement is misleading: "Because the C# compiler dereferences the `Nullable<T>` for you." To me this implies that the behavior the OP is seeing is a special case specific to enumerating over a sequence of some nullable type. But it isn't. You can also do `foreach (int x in listOfDoubles)` or `foreach (double x in listOfFloats)`, for example.
Dan Tao
@Kent: After having re-checked the spec there seems actually to be no special treatment of `Nullable<T>` here at all. What you see in the output here is simply the implementation of the explicit cast operator for conversions from `Nullable<T>` to `T`.
0xA3
@Kent Boogaart, No; I do disagree with your conclusion, and I agree with the conclusions of @Dan Tao and @0xA3. And your reiteration of the statement I quoted doesn't state anything about the runtime, which strengthens my statement: the interaction between `foreach` and `Nullable<T>` has nothing to do with the runtime.
strager
+3  A: 
Dan Tao
By default, won't `IEnumerable<T>`'s `GetEnumerator` be called, making the `foreach` "generic"?
strager
@strager: Yes, but there will still be a cast.
SLaks
@Slaks: there is no cast and having one would negate some of the performance benefits of generics. Check my post.
Kent Boogaart
@Kent: The fact that you can write `foreach int x in arrayList` means that the compiler *does* perform a cast where necessary. Where I guess I went wrong was to imply that this is *always* the case: you're right, it isn't. But the OP said: "...it would be nice if compiler reprimanded me." I wanted to explain why the compiler simply isn't going to do that.
Dan Tao
+3  A: 

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 type E and element type T. A foreach statement of the form

foreach (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.

0xA3
Gah, I spent way too long updating my answer to realize you basically covered the same ground in your own! One thing, though: I think *enumerator type* in the example is actually `List<int?>.Enumerator`, *not* `IEnumerator<int?>`. Am I right?
Dan Tao
@Dan Tao: Yes, you are right (as I understand the spec).
0xA3