Подтвердить что ты не робот

Преобразование типа С# несовместимо?

В С# я не могу неявно преобразовать long в int.

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

Это вызывает указанную ошибку. И это правильно; если я это сделаю, я рискую сломать свои данные из-за ошибочного усечения. Если я решаю, что я знаю, что делаю, тогда я всегда могу сделать явное приведение и сказать компилятору, что все в порядке, чтобы усечь, я знаю лучше всего.

int i = (int)l;  // OK

Однако такой же механизм не применяется при использовании цикла foreach.

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

Компилятор даже не генерирует предупреждение здесь, хотя это, по сути, одно и то же: непроверенное усечение long на int, что может очень сильно нарушить мои данные.

Итак, мой вопрос просто: почему этот foreach не создает ту же ошибку, что и присвоение переменной?

4b9b3361

Ответ 1

UPDATE: этот вопрос был тема моего блога в июле 2013 года. Спасибо за отличный вопрос!

Почему этот foreach не создает ту же ошибку, что и присвоение переменной?

"Почему" вопросы трудно ответить, потому что я не знаю "настоящего" вопроса, который вы задаете. Поэтому вместо ответа на этот вопрос я отвечу на несколько разных вопросов.

Какой раздел спецификации оправдывает это поведение?

Как правильно отвечает Майкл Лю, это раздел 8.8.4.

Вся суть явного преобразования заключается в том, что преобразование должно быть явным в коде; почему у нас есть оператор литья; он размахивает большим флагом, в котором говорится: "Здесь прямое преобразование прямо здесь". Это один из нескольких раз в С#, где явное преобразование не сохраняется в коде. Какие факторы побуждали команду разработчиков к невидимому введению "явного" преобразования?

Цикл foreach был разработан до дженериков.

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

Вы не хотите говорить:

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

В мире без дженериков вы должны заранее знать, какие типы входят в список, и вы почти всегда имеете это знание. Но эта информация не фиксируется в системе типов. Поэтому вы должны как-то сказать компилятору, и вы это сделаете, сказав

foreach(string item in myList)

Это ваше утверждение компилятору о том, что список полон строк, точно так же, как приведение - это утверждение, что конкретный элемент является строкой.

Вы совершенно правы, что это несчастье в мире с дженериками. Так как это будет ломать, чтобы изменить его сейчас, мы застряли с ним.

Эта функция довольно запутанна; когда я начал программировать С#, я предположил, что у него есть семантика чего-то вроде:

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

То есть, "для каждого объекта строки типа в этом списке выполните следующие действия", когда это действительно так: "для каждого объекта в этом списке утверждают, что элемент является строкой и выполняет следующее..." (if первое, что вы на самом деле хотите, затем используйте метод расширения OfType<T>().)

Мораль истории: языки в конечном итоге со странными "унаследованными" функциями, когда вы массово меняете систему типов в версии 2.

Должен ли компилятор выдавать предупреждение для этого случая в современном коде, где используются дженерики?

Я это рассмотрел. Наши исследования показали, что

foreach(Giraffe in listOfMammals)

настолько распространен, что большую часть времени мы будем давать предупреждение для правильного кода. Это создает проблемы для всех, кто компилируется с включенными "предупреждениями как ошибками", и, вообще говоря, нехорошо иметь предупреждение о коде, что да, возможно, немного вонючий, но на самом деле правильный. Мы решили не преследовать предупреждение.

Существуют ли другие ситуации, когда компилятор С# невидимо вставляет явные преобразования?

Да. На самом деле кто-то задал вопрос об этом через несколько часов после этого:

Компилятор заменяет явное преобразование на мой собственный тип с явным приведением в .NET-тип?

Есть некоторые крайне неясные сценарии взаимодействия, в которые также встроены явные преобразования.

Ответ 2

Как определено в §8.8.4 спецификации С# 4.0, оператор foreach формы

foreach (V v in x) embedded-statement

расширяется до

{
    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
    }
}

где C - это "тип коллекции", а T - "тип элемента", выводимый из x.

Приведение в V (int в вашем случае) позволяет компилировать ваш пример.

Вероятная причина для приведения в V: в С# 1.0, до того, как дженерики были добавлены к языку, явное приведение, как правило, было необходимо в любом случае при перечислении через коллекцию типа ArrayList, поскольку компилятор не мог автоматически введите тип значений в коллекции.

Ответ 3

Простой ответ foreach делает явное приведение за кулисами. Другой пример:

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

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

Это также не сгенерирует ошибку компилятора, но будет запускать InvalidCastException во время выполнения.