简体   繁体   中英

foreach won't type check even when enumerated type is sealed

I have a similar problem to this question . But instead my SomeClass implements SomeInterface . In this case, even if I mark SomeClass as sealed , compiler time type check still don't kick in. Example below.

Question 1: Why wouldn't the compiler give an error as the type of the loop variable isn't compatible with the type of elements of the source enumerable?

Having compiler type check is important to me, as I'm refactoring a big project, changing the enumeration source ( source in a foreach (SomeClass foo in source) ) from IEnumerable<SomeClass> to IEnumerable<SomeInterface> . Sometime I forget to change the enumerator type declaration in the loop from SomeClass to SomeInterface , and the project still compiles. That would break things really bad!!

Question 2: What suggestion do you have to make this refactoring type safe?

Note that I can't change loop variable type to be implicit var because it's exiting code base. But I am able to change the declaration of the interface and its concrete implementation classes.

class Program
{
    static void Main(string[] args)
    {
        IEnumerable<SomeInterface> someFoos = new SomeInterface[] { new SomeOtherClass(), new SomeOtherClass(), new SomeOtherClass() };
        foreach (SomeClass foo in someFoos)
        {
            foo.ToString(); // runtime InvalidCastException
        }
    }
}

interface SomeInterface { }

sealed class SomeClass : SomeInterface
{
    public override string ToString()
    {
        return "SomeClass";
    }
}

class SomeOtherClass : SomeInterface
{
    public override string ToString()
    {
        return "SomeOtherClass";
    }
}

You have asked two questions in one question. As almost always happens, you've had one of your two questions answered. In the future, consider asking one question per question; you are more likely to have your question answered.

I'll answer your first question.

Question 1: Why wouldn't the compiler give an error as the type of the loop variable isn't compatible with the type of elements of the source enumerable?

This is a "why not" question, and it is very difficult to give answers to "why not" questions; they presuppose that the world ought to be a different way and ask why it isn't. There are literally an infinite number of reasons why the world isn't the way you want it to be. My advice is that you stop asking "why not?" questions here. Instead, try asking questions about how the world is .

So let me clarify the question. We have

foreach(X item in collection)

where collection is a sequence of Y .

Question: what must the relationship between X and Y be?

Y must be explicitly convertible to X . That is, a cast operator of the form (X) must be legal to apply to an item of type Y .

This is one of the rare situations in which C# will automatically insert a cast operation on your behalf without a cast operator being manifest in the code.

Question: What language design principles suggest that this situation ought to be rare?

C# is designed to be a statically-checked type-safe language; this conversion moves the type check to runtime rather than compile-time, decreasing safety.

Question: Language design decisions require making tradeoffs between competing principles. What was the compelling scenario that caused the C# design team to include an "invisible" cast in the language?

The semantics of the foreach loop were developed before generics were added to the language. This means that it was likely that a sequence would be IEnumerable , and therefore would be a sequence of object s. But the user likely has knowledge that a particular sequence is, say, all strings. Therefore, in C# 1.0, this code would be extremely common:

foreach(object item in collection)
{
    string s = (string)item;
    ...

Which seems needlessly verbose. Therefore the language design team decided to allow the conversion to the loop variable type to be explicit.

One might reasonably note that this adds the potential for bugs that you've discovered. An alternative feature might be to say that the conversion only happens if converting from object . There are possibly other design choices that could have been made. The C# design team chose to make the conversion explicit and that's what we've got to live with.

In a counterfactual world where generic collections were available from day one, the design would likely be different. But that sort of counterfactual reasoning isn't really very productive.

To answer your second question:

Question 2: What suggestion do you have to make this refactoring type safe?

The refactoring you are describing is not type safe! The contract was "I'll give you a sequence of mammals", and the code that consumes this contractual obligation quite reasonably assumes that everything in there is going to produce milk, give birth to live young, and grow hair. (Leaving aside the monotremes.) You are changing that contract to "actually, I'll give you a sequence of animals", and there could be a squid or a lizard in there now. You are weakening a contract, and that's always dangerous. You need to get buy-in from all the parties to that contract before you weaken it,.

You could do something like this, which would should be perfectly safe:

foreach (SomeClass foo in someFoos.Where(foo => foo is SomeClass))
{
    // Do something specific with a SomeClass type here
}

Otherwise, if you don't need to do something that is specific to SomeClass, then just use the interface in your loop:

foreach (SomeInterface foo in someFoos)
{
    // Do something with SomeInterface properties or methods here
}

Since you mentioned that you can't change the loop but you can change the classes, then another possibility would be to un-seal SomeClass and have SomeOtherClass inherit from SomeClass . This will allow your existing loop to work as well:

interface SomeInterface { }

class SomeClass : SomeInterface
{
    public override string ToString()
    {
        return "SomeClass";
    }
}

class SomeOtherClass : SomeClass
{
    public override string ToString()
    {
        return "SomeOtherClass";
    }
}

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