Почему компилятор С# допускает явное литье между IEnumerable <T> и TAlmostAnything?

Следующий код дает вам ошибку компилятора, как и следовало ожидать:

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

Banana justOneBanana = (Banana)aBunchOfBananas;

Однако при использовании IEnumerable<Banana> вы просто получаете ошибку времени выполнения.

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

Banana justOneBanana = (Banana)aBunchOfBananas;

Почему компилятор С# разрешает это?

Ответ 1

Я бы предположил это, потому что IEnumerable<T> - это интерфейс, в котором некоторая реализация может иметь явное приведение к Banana - независимо от того, насколько глупо это будет.

С другой стороны, компилятор знает, что List<T> не может быть явно передан в Banana.

Хороший выбор примеров, кстати!

Добавление примера для пояснения. Может быть, у нас будет "перечислимый", который должен всегда содержать не более один Banana:

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

    // Others omitted...
}

Тогда вы могли сделать это:

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

Как и то, что записывает следующее, что компилятор отлично понимает:

Banana justOneBanana = aBunchOfBananas.SingleOrDefault();

Ответ 2

Когда вы скажете Y y = (Y)x;, этот актер говорит компилятору: "Поверьте мне, что бы ни было x, во время выполнения он может быть отброшен в Y, так что просто сделайте это, хорошо?"

Но когда вы говорите

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

компилятор может посмотреть определения для каждого из этих конкретных классов (Banana и List<Banana>) и увидеть, что нет static explicit operator Banana(List<Banana> bananas), определенных (помните, явный листинг должен быть определен либо в том, или тип, на который наложено, это из спецификации, раздел 17.9.4). Во время компиляции известно, что то, что вы говорите, никогда не может быть правдой. Так он кричит на вас, чтобы перестать лежать.

Но когда вы говорите

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

ну, теперь компилятор не знает. Очень хорошо, что случай, когда aBunchOfBananas случается во время выполнения, его конкретный тип x мог бы определить static explicit operator Banana(X bananas). Поэтому компилятор доверяет вам, как вы его просили.

Ответ 3

Возможно, потому, что компилятор знает, что Banana не расширяет List<T>, но существует вероятность того, что некоторый объект, реализующий IEnumerable<T>, может также расширить Banana и сделать допустимым листинг.

Ответ 4

Согласно языковой спецификации (6.2.4) "Явные ссылочные преобразования:  От любого класса S до любого типа интерфейса T, если S не запечатан и не предоставляется S не реализует T ... Явные ссылочные преобразования - это те преобразования между ссылочными типами, которые требуют проверки во время выполнения, чтобы убедиться, что они верны... "

Поэтому компилятор не проверяет реализацию интерфейса во время компиляции. Это CLR во время выполнения. Он проверяет метаданные, пытаясь найти реализацию в классе или среди своих родителей. Я не знаю, почему так оно ведет. Наверное, это занимает много времени. Итак, этот код правильно компилируется:

public interface IInterface
{}

public class Banana
{
}

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

        IInterface b = (IInterface)banana;
    }
}

В другой руке, если мы попытаемся применить банан к классу, компилятор проверяет свои метаданные и выдает ошибку:

 FileStream fs = (FileStream)banana;