Home » c# » Why does the C# compiler allow an explicit cast between IEnumerable<T> and TAlmostAnything?

Why does the C# compiler allow an explicit cast between IEnumerable<T> and TAlmostAnything?

Posted by: admin November 29, 2017 Leave a comment

Questions:

The following code gives you a compiler error, as you’d expect:

List<Banana> aBunchOfBananas = new List<Banana>();

Banana justOneBanana = (Banana)aBunchOfBananas;

However, when using IEnumerable<Banana>, you merely get a runtime error.

IEnumerable<Banana> aBunchOfBananas = new List<Banana>();

Banana justOneBanana = (Banana)aBunchOfBananas;

Why does the C# compiler allow this?

Answers:

I would suppose it’s because IEnumerable<T> is an interface where some implementation could have an explicit cast to Banana – no matter how silly that would be.

On the other hand, the compiler knows that List<T> can’t be explicitly cast to a Banana.

Nice choice of examples, by the way!

Adding an example to clarify. Maybe we’d have some “enumerable” that should always contain at most a single Banana:

public class SingleItemList<T>:Banana, IEnumerable<T> where T:Banana {
    public static explicit operator T(SingleItemList<T> enumerable) {
        return enumerable.SingleOrDefault();
    }

    // Others omitted...
}

Then you could actually do this:

IEnumerable<Banana> aBunchOfBananas = new SingleItemList<Banana>();
Banana justOneBanana = (Banana)aBunchOfBananas;

As it’s the same as writing the following, which the compiler is perfectly happy with:

Banana justOneBanana = aBunchOfBananas.SingleOrDefault();

Questions:
Answers:

When you say Y y = (Y)x; this cast says to the compiler “trust me, whatever x is, at runtime it can be casted to a Y, so, just do it, okay?”

But when you say

List<Banana> aBunchOfBananas = new List<Banana>();
Banana justOneBanana = (Banana)aBunchOfBananas;

the compiler can look at the definitions for each of these concrete classes (Banana and List<Banana>) and see that there is no static explicit operator Banana(List<Banana> bananas) defined (remember, an explicit cast must be defined in either the type being casted or the type being casted to, this is from the spec, section 17.9.4). It knows at compile time that what you’re saying can not ever be true. So it yells at you to stop lying.

But when you say

IEnumerable<Banana> aBunchOfBananas = new List<Banana>();
Banana justOneBanana = (Banana)aBunchOfBananas;

well, now the compiler doesn’t know. It very well could the case that whatever aBunchOfBananas happens to be at run time, its concrete type X could have defined static explicit operator Banana(X bananas). So the compiler trusts you, like you asked it to.

Questions:
Answers:

It may be because the compiler knows that Banana does not extend List<T>, but that there’s a possibility that some object which implements IEnumerable<T> may also extend Banana and make that a valid cast.

Questions:
Answers:

According to language spec (6.2.4)
“The explicit reference conversions are:
From any class-type S to any interface-type T, provided S is not sealed and provided S does not implement T

The explicit reference conversions are those conversions between reference-types that require run-time checks to ensure they are correct…”

So compiler doesn’t check interface realization during compilation. It does CLR in runtime. It checks metadata trying to find realization in class or among its parents. I dunno why it behaves like this. Probably it takes a lot of time. So this code compiles correctly:

public interface IInterface
{}

public class Banana
{
}

class Program
{
    static void Main( string[] args )
    {
        Banana banana = new Banana();

        IInterface b = (IInterface)banana;
    }
}

In other hand if we try to cast banana to class, compiler checks its metadata and throw an error:

 FileStream fs = (FileStream)banana;