Почему компилятор C# допускает явное приведение между IEnumerable и TAlmostAnything?


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

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

Banana justOneBanana = (Banana)aBunchOfBananas;

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

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

Banana justOneBanana = (Banana)aBunchOfBananas;

почему компилятор C# позволяет это?

4 59

4 ответа:

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

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

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

приведу пример для разъяснения. может быть, у нас будет какой-то" перечислимый", который всегда должен содержать at большинство один 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();

когда вы говорите 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) defined (помните, что явное приведение должно быть определено либо в типе, который приводится, либо в типе, который приводится, это из спецификации, раздел 17.9.4). Он знает во время компиляции, что то, что вы говорите, никогда не может быть правдой. Поэтому он кричит на вас, чтобы вы перестали лгать.

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

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

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

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

согласно спецификации языка (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;