views:

458

answers:

5
long[] b = new long[1];
int i1 = b[0]; // compile error as it should

// no warning at all, large values gets converted to negative values silently
foreach (int i2 in b) 
{
}

class Customer : Person{}
Person[]p = new Person[]{mypers};

// no warning at all, throws typecastexception at runtime
foreach (Customer c in p) 
{
}

I know they cannot simply fix it, because it would break existing programs.

But why don't they make a compatibility option in c# compiler where I can turn on typesafe foreach block generation at least for new programs or programs where I am sure it works?

+16  A: 
foreach (var c in p) 
{
}

avoids the problem nicely, doesn't it?

jalf
foreach (Person c in p) also would "solve" it, that is not the point..
codymanix
You would need to add a check for `if (c is Customer)` inside in either case.
Justin
jalf
@jalf Calm down. It's a legitimate question, just poorly worded.
Josh Stodola
using "var" everywhere won't make programs easier to read.
codymanix
@codymanix - Now I'm really confused. You don't want to use type inference using `var` but you also don't want the compiler to try typecasting even after you explicitly specify a different type in the `foreach` loop. What exactly do you want the behavior to be? (keep in mind that foreach can iterate over collections that have varying types as well).
Justin Niessner
@Josh: legitimate questions aren't tagged with `[rant]` as this was. @codymanix: I think `var` is perfectly readable.
jalf
To the [rant] keyword: Sadly to see that nobody here has my kind of humour...
codymanix
I like to see the types Iam operating on and do not want to have the need to browse into the collection to see what kind of type it actually will return.
codymanix
@codymanix, I started using var a while ago, and my code is stille very readable, without using comment. Just give your variables Logical names like: personList etc
Nealv
Ah I got it. Instead of using explicit typing you use var and make the intended type part of the variable name..
codymanix
@jaff: In response to your first comment, that's why the Subjective and Argumentative close reason exists.
R. Bemrose
I always type `foreach( var f in foo )` and use Resharper to "specify type explicitly" to change `var` into whatever type it is.
Greg
@R. Bernrose: and that's why I voted to close as Subjective and Argumentative before it was reopened. ;)
jalf
+4  A: 

You are at liberty to create an extension method on IEnumerable that is type safe:

public static void Each(this IEnumerable @this, Action action)
{
    if (@this == null) throw new ArgumentNullException("this");
    if (action == null) throw new ArgumentNullException("action");

    foreach (TElement element in @this)
    {
        action(element);
    }
}

It is subtly different to the foreach loop and will occur additional overhead, but will ensure type safety.

Paul Ruane
I've never seen the @ before, potentially stupid question but what's that do?
AndyC
@AndyC http://stackoverflow.com/questions/91817/whats-the-use-meaning-of-the-character-in-variable-names-in-c
Justin
@ allows you to use a C# keyword as a variable name. So you can do questionable things like:int @foreach = 1;long @if = 2;string @class = "foo";
AlfredBr
You can use keywords as normal variables with the @ sign. normally you use it when interoperating with other languages which expose members that is a keyword in the other language.
codymanix
It is simply added to let you use a reserved word as a variable name.
JohannesH
Cheers guys, never knew that!
AndyC
+5  A: 

foreach is behaving the only way it can.

When foreach iterates over a collection, it doesn't necessarily know anything about the types that will be returned (it could by a collection of interface implementations, or simply objects in the case of the older .NET 'bag' style collections).

Consider this example:

var bag = new ArrayList();
bag.Add("Some string");
bag.Add(1);
bag.Add(2d);

How would foreach behave in this instance (the collection has a string, an int, and a double)? You could force foreach to iterate over the most compatible type which, in this case, is Object:

foreach(object o in bag)
{
    // Do work here
}

Interestingly enough, using var will do exactly that:

foreach(var o in bag)
{
    // o is an object here
}

But the members of object won't really allow you to do anything useful.

The only way to do anything useful is to rely on the developer to specify the exact type they want to use and then attempt to cast to that type.

Justin Niessner
+1  A: 

As for the why:

Using arrays, the compiler in fact translates the foreach statement into something that looks like the following.

private static void OriginalCode(long[] elements)
{
    foreach (int element in elements)
    {
        Console.WriteLine(element);
    }
}

private static void TranslatedCode(long[] elements)
{
    int element;
    long[] tmp1 = elements;
    int tmp2 = 0;

    while (tmp2 < elements.Length)
    {
        // the cast avoids an error
        element = (int)elements[tmp2++];
        Console.WriteLine(element);
    }
}

As you can see, the generated cast avoids the runtime error and of course leads to the semantic error in your case.

Btw, the same goes for IEnumerable and foreach which translates into the following code leading to the same behaviour and problem.

private static void OriginalCode(IEnumerable<long> elements)
{
    foreach (int element in elements)
    {
        Console.WriteLine(element);
    }
}

private static void TranslatedCode(IEnumerable<long> elements)
{
    int element;
    IEnumerator<long> tmp1 = elements.GetEnumerator();

    try
    {
        while (tmp1.MoveNext())
        {
            element = (int)tmp1.Current;
            Console.WriteLine(element);
        }
    }
    finally
    {
        (tmp1 as IDisposable).Dispose();
    }
}
Roland Sommer
I don't believe that foreach gets translated into an simple if-statement. Maybe you mean a for-statement :)
codymanix
I have posted an answer before comparing while, for, foreach, and goto to the point that they will all compile to the same IL. See it here http://stackoverflow.com/questions/2447559/c-does-function-get-called-for-each-iteration-of-a-foreach-loop/2447646#2447646
Matthew Whited
@codymanix Ops sorry, should be a while loop :) corrected
Roland Sommer
@Matthew Whited: Indeed, the IL is pretty much the same for a lot of statement blocks. I have choosen the while loop for demonstration because I think it's the easiest way to show the cast.
Roland Sommer
A: 

EDIT: Clarified and corrected based on codymax's comment

Based on my interpretation of section 8.8.4 in the C# Spec foreach is expanded as as below. (See the part that begins "If the type X of expression is an array type then there is an implicit reference conversion from X to the System.Collections.IEnumerable interface.."

The first case converts values above int.MaxValue to -1. This is because operations don't throw overflow by default . Since the first case the conversion is in an unchecked context section 7.6.12 describes what's supposed to happen which is

the result is truncated by discarding any high-order bits that do not fit in the destination type.

The second does as expected throw a runtime InvalidCastException. This is probably because the type returned by Enumerator.Current. There's probably a section that describes this but I didn't look for it.

        long[] b = long[0] ;


        System.Collections.IEnumerator le = ((long[])(b)).GetEnumerator();

        int i2;
        while (le.MoveNext())
        {
            i2 = (int)(long)le.Current;

        }




        Person[] p = new Person[1] { mypers };

        System.Collections.IEnumerator e = ((Person[])(p)).GetEnumerator();

        Customer c;
           while (e.MoveNext())
           {
               c = (Customer)(Person)e.Current;

            }
Conrad Frix
Firstly it is wrong that values above int.MaxValue are converted to -1, they convert to the corresponding negative value instead.Secondly, foreach on arrays does *not* create a GetEnumerator call but instead a simple for-loop.Besides, the double-cast to Person and then to Customer is superfluous.
codymanix
@codymax You're right about the conversion so I updated my answer. Why do you think it doesn't do a GetEnumerator? The double cast may be superfluous in some cases but perhaps not in all.
Conrad Frix