Ответ 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-тип?
Есть некоторые крайне неясные сценарии взаимодействия, в которые также встроены явные преобразования.