简体   繁体   中英

C# type conversion inconsistent?

In C#, I cannot implicitly convert a long to an int .

long l = 5;
int i = l;  // CS0266: Cannot implicitly convert type 'long' to 'int'. An explicit conversion exists (are you missing a cast?)

This produces said error. And rightly so; if I do that, I am at risk of breaking my data, due to erroneous truncation. If I decide that I know what I am doing, then I can always do an explicit cast, and tell the compiler that it's okay to truncate, I know best.

int i = (int)l;  // OK

However, the same mechanism does not seem to apply when using a foreach loop.

IList<long> myList = new List<long>();
foreach (int i in myList)
{
}

The compiler does not even generate a warning here, even though it is essentially the same thing: an unchecked truncation of a long to an int , which might very well break my data.

So my question is simply: Why does this foreach not create the same error as the variable assignment does?

UPDATE: This question was the subject of my blog in July of 2013 . Thanks for the great question!

Why does this foreach not create the same error as the variable assignment does?

"Why" questions are difficult to answer because I don't know the "real" question you're asking. So instead of answering that question I'll answer some different questions.

What section of the specification justifies this behaviour?

As Michael Liu's answer correctly points out, it is section 8.8.4.

The whole point of an explicit conversion is that the conversion must be explicit in the code; that's why we have the cast operator; it's waving a big flag that says "there's an explicit conversion right here". This is one of the few times in C# where an explicit conversion is not extant in the code. What factors motivated the design team to invisibly insert an "explicit" conversion?

The foreach loop was designed before generics.

ArrayList myList = new ArrayList();
myList.Add("abc");
myList.Add("def");
myList.Add("ghi");

You don't want to have to say:

foreach(object item in myList)
{
    string current = (string)item;

In a world without generics you have to know ahead of time what types are in a list, and you almost always do have that knowledge . But this information is not captured in the type system. Therefore, you have to tell the compiler somehow, and you do that by saying

foreach(string item in myList)

This is your assertion to the compiler that the list is full of strings, just like a cast is an assertion that a particular item is a string.

You are completely correct that this is a misfeature in a world with generics. Since it would be breaking to change it now, we're stuck with it.

The feature is quite confusing; when I first started programming C# I assumed that it had the semantics of something like:

while(enumerator.MoveNext())
{
    if (!(enumerator.Current is string) continue;
    string item = (string)enumerator.Current;

That is, "for each object of type string in this list, do the following", when it really is "for each object in this list assert that the item is a string and do the following..." (If the former is what you actually want then use the OfType<T>() extension method.)

The moral of the story is: languages end up with weird "legacy" features when you massively change the type system in version 2.

Should the compiler produce a warning for this case in modern code, where generics are being used?

I considered it. Our research showed that

foreach(Giraffe in listOfMammals)

is so common that most of the time we'd be giving a warning for correct code. That creates trouble for everyone who compiles with "warnings as errors" turned on, and it's generally speaking badness to have a warning on code that is yes maybe a bit smelly but actually correct . We decided to not pursue the warning.

Are there other situations where the C# compiler invisibly inserts explicit conversions?

Yes. In fact someone asked a question about that just a few hours after this one:

Compiler replaces explicit cast to my own type with explicit cast to .NET type?

There are some extremely obscure interop scenarios where explicit conversions are inserted as well.

As defined in §8.8.4 of the C# 4.0 specification, a foreach statement of the form

foreach (V v in x) embedded-statement

is expanded to

{
    E e = ((C)(x)).GetEnumerator();
    try {
        V v;
        while (e.MoveNext()) {
            v = (V)(T)e.Current; // <-- note the explicit cast to V
            embedded-statement
        }
    }
    finally {
        … // Dispose e
    }
}

where C is the "collection type" and T is the "element type" inferred from x .

The cast to V ( int in your case) is what allows your example to compile.

The likely reason for the cast to V : in C# 1.0, before generics was added to the language, an explicit cast was usually needed anyway when enumerating through a collection like ArrayList , because the compiler could not automatically figure out the type of values in the collection.

The simple answer is foreach does an explicit cast behind the scenes. Another example:

    public class Parent { }
    public class Child : Parent { }

    IList<Parent> parents = new List<Parent>()
    {
        new Parent()
    };
    foreach (Child child in parents) { }

This will also not generate a compiler error, but will throw an InvalidCastException at runtime.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM