Как foreach вызывает GetEnumerator()? Через ссылку IEnumerable или через...?

    static void Main(string[] args)
    {
        List<int> listArray = new List<int>();
        listArray.Add(100);
        foreach (int item in listArray)
            Console.WriteLine(item);
    }

a) Когда оператор foreach вызывает реализацию listArray IEnumerable<int>.GetEnumerator(), вызывает ли он его через listArray.GetEnumerator() или IEnumerable<int>.GetEnumerator() или IEnumerable.GetEnumerator()?

b) Точно так же, когда foreach ссылается на объект, возвращаемый listArray IEnumerable<int>.GetEnumerator(), ссылается ли этот объект на него через IEnumerator или IEnumerator<int> тип ссылки?

Благодарю вас

EDIT:

Некоторые из моих вопросов цитируют этот текст:

o Выполнить поиск элемента по типу X с идентификатором GetEnumerator и no типа. Если поиск элемента не дает соответствия, или вызывает двусмысленность или создает совпадение, которое не является группой методов, проверить перечислимый интерфейс как описано ниже. Рекомендуется что предупреждение выдается, если член поиск производит что-либо, кроме группа методов или отсутствие соответствия.

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

o Если тип возврата E Метод GetEnumerator не является классом, структуры или типа интерфейса, ошибка и дальнейшие шаги приняты.

o Поиск члена выполняется на E с идентификатором Current и no типа. Если поиск элемента не дает совпадения, результатом является ошибка, или результат - что угодно кроме свойства публичного экземпляра, которое разрешает чтение, возникает ошибка и никаких дальнейших шагов не предпринимается.

o Поиск члена выполняется на E с идентификатор MoveNext и тип нет аргументы. Если поиск элемента не дает совпадения, результатом является ошибка, или результат - что угодно кроме группы методов, ошибка и дальнейшие шаги приняты.

o Разрешение перегрузки выполняется группа методов с пустым список аргументов. Если разрешение перегрузки не приводит к каким-либо применимым методам, приводит к двусмысленности или приводит к единственный лучший метод, но этот метод является либо статическим, либо не публичным, либо его тип возврата не является bool, ошибка и дальнейшие шаги приняты.

o Тип коллекции - X, тип перечислителя - E, а элемент тип - тип тока имущество.

  • В противном случае проверьте перечислимый интерфейс:       o Если существует ровно один тип T такой, что существует неявный преобразование из X в интерфейс System.Collections.Generic.IEnumerable, то тип коллекции - это интерфейса, тип перечислителя является интерфейс System.Collections.Generic.IEnumerator, и тип элемента - T.

  • В противном случае, если существует более одного такого типа T, то ошибка и дальнейшие шаги.

  • В противном случае, если есть неявное преобразование из X в System.Collections.IEnumerable интерфейса, тогда тип коллекции этот интерфейс, тип перечислителя интерфейс System.Collections.IEnumerator и тип элемента - это объект.

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

1)

Цитата из Эрика Липперта:

Опция (1) верна. Обратите внимание, что это означает, что возвращаемый счетчик unboxed mutable struct.

Тот факт, что это изменяемая структура имеет очень реальные последствия, если вы это сделаете что-то глупое, как обход структура, как если бы это была ссылочный тип; он будет скопирован значение, а не по ссылке.

Из http://en.csharp-online.net/ECMA-334:_15.8.4_The_foreach_statement:

foreach (V v in x) embedded-statement

затем расширяется до:

{
   E e = ((C)(x)).GetEnumerator();
   try {
      V v;
      while (e.MoveNext()) {
         v = (V)(T)e.Current;
         embedded-statement
      }
   }
   finally {
      … // Dispose e
   }
}

Переменная e не видна или доступный для выражения x или встроенный оператор или любой другой источник код программы.

В случае listArray возвращаемый перечислитель сохраняется (т.е. его значение сохраняется) в переменной e (таким образом, переменная e является изменяемой структурой). Но в соответствии с вышеприведенной выдержкой e не доступный для моего исходного кода, так как я мог бы передать эту структуру вокруг (если я не пишу код, который делает вручную то, что оператор foreach делает автоматически)?

2)

Поиск элемента выполняется на E с идентификаторами Current и no type. Если поиск элемента не приводит к совпадению, результатом является ошибка, или результат - это что-то за исключением свойства публичного экземпляра, которое разрешает чтение, возникает ошибка и нет предпринимаются дальнейшие шаги.

Кажется, что если мы реализуем GetEnumerator в самом классе (X), то Current также должен быть реализован в самом классе (e) (таким образом, e не должен явно реализовывать Current), поскольку компилятор не потрудится проверять интерфейсы IEnumerator<T> / IEnumerator в случаях, когда поиск элемента (в e с идентификатором Current) не дает соответствия?

3)

Если существует ровно один тип T такой, что существует неявное преобразование из X в интерфейс System.Collections.Generic.IEnumerable, то тип коллекции - это интерфейс, тип перечислителя - это интерфейс System.Collections.Generic.IEnumerator, а тип элемента - T.

В соответствии с вышеизложенным, если foreach должен проверить интерфейс IEnumerable<T>, то foreach всегда будет использовать IEnumerator<T> версию Current? Таким образом, если e явно реализует IEnumerator<T> версию Current, и если она также реализует другую версию Current в самом классе, foreach всегда будет вызывать IEnumerable<T> версию Current?

4)

Метод GetEnumerator документируется как возвращающий один из них:

http://msdn.microsoft.com/en-us/library/x854yt9s.aspx

Что вы подразумеваете под одним из них (как во множественном числе)? Вы указали, что GetEnumerator (как реализовано List<T>) возвращает только struct.

5)

г. Тип коллекции - X, тип перечислителя - E, а тип элемента - тип свойства Current

Возможно, бесполезный вопрос - согласно выше, foreach не проверяет, какие типы элементов хранит определенная пользователем коллекция, но вместо этого предполагает, что тип элементов совпадает с типом, возвращаемым Current имущество?

Ответ 1

(a) Когда оператор foreach вызывает реализацию listArray IEnumerable.GetEnumerator(), вызывает ли он его через (1) listArray.GetEnumerator() или (2) IEnumerable.GetEnumerator() или (3) IEnumerable.GetEnumerator()?

Опция (1) верна. Обратите внимание, что это означает, что возвращаемый счетчик является unboxed mutable struct. Метод GetEnumerator документирован как возвращающий один из них:

http://msdn.microsoft.com/en-us/library/x854yt9s.aspx

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

(1) Но в соответствии с вышеприведенным отрывом e не может быть доступным для моего исходного кода, так как я смогу передать эту структуру вокруг (если только я не пишу код, который делает вручную то, что делает оператор foreach автоматически)?

Вы правы. Я не был ясен. Я хотел сказать, что если вы пишете код, который делает то, что делает foreach, и вы возитесь с объектом перечислителя, тогда вам нужно быть осторожным. Команда CLR осознала, что подавляющее большинство людей будет использовать цикл foreach и, таким образом, не будет подвергаться опасности случайного использования перечислителя неправильно.

(2) Кажется, что если мы реализуем GetEnumerator в самом классе X, то Current также должен быть реализован в самом классе E, так как компилятор не будет пытаться проверить явные члены интерфейса в случаях, когда поиск элемента не выполняется Не получается совпадение?

Правильно.

(3) Если foreach должен проверить интерфейс IEnumerable<T>, то foreach всегда будет использовать версию IEnumerator<T> Current? Таким образом, если E явно реализует версию Current IEnumerator<T> Current, и если она также реализует другую версию Current в самом классе, foreach всегда будет вызывать IEnumerable<T> версию Current?

Правильно. Если вы дойдете до точки, где мы ищем интерфейс, мы будем использовать интерфейс.

(4) Что вы подразумеваете под "одним из этих"

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

(5) в соответствии с вышеизложенным, foreach не проверяет, какие типы элементов хранит определенная пользователем коллекция, но вместо этого предполагает, что тип элементов совпадает с типом, возвращаемым текущим свойством?

Правильно. Он проверяет, что передача выполнена успешно. Например, если вы скажете

foreach(int x in myObjects)

где myObjects дает вам перечислитель, чей Current имеет тип объекта, тогда цикл предполагает, что каждый объект может быть успешно перенесен в int и генерирует исключение во время выполнения, если это неверно. Но если вы так же говорите:

foreach(string x in myInts)

тогда компилятор заметит, что если Current возвращает int, то коллекция никогда не содержит строку и не сможет скомпилировать программу.

(b) Аналогично, когда объект ссылки foreach, возвращаемый listArray IEnumerable.GetEnumerator(), ссылается ли этот объект на этот объект через тип ссылки IEnumerator или IEnumerator?

Вопрос основан на ответе на первый вопрос, являющийся опцией (2). Поскольку вопрос основан на ложности, на него нельзя ответить разумно.

Ответ 2

Поведение foreach указано в спецификации языка, раздел 8.8.4. В двух словах

foreach (T t в выражении)

  • Если выражение представляет собой массив *, используйте интерфейс IEnumerable (* см. комментарий Эрика Липперта ниже).
  • Если выражение имеет метод GetEnumerator, используйте
  • Если выражение конвертируется в IEnumerable<T>, используйте этот интерфейс и IEnumerator<T> (и связанные с ним методы)
  • Если выражение конвертируется в IEnumerable, используйте этот интерфейс и IEnumerator (и связанные с ним методы)

И есть различные условия ошибки и вещи, которые я затушевываю. Но, короче говоря, если ваша коллекция является общей, она будет использоваться для общих параметров интерфейса.

Ответ 3

Из спецификации языка С# 3.0 (раздел 8.8.4):

Обработка времени компиляции оператора foreach сначала определяет тип коллекции, тип перечислителя и тип элемента выражения. Это определение происходит следующим образом:

  • Если тип X выражения является типом массива, тогда существует неявное ссылочное преобразование из X в интерфейс System.Collections.IEnumerable(поскольку System.Array реализует этот интерфейс). Тип коллекции - это интерфейс System.Collections.IEnumerable, тип перечислителя - это интерфейс System.Collections.IEnumerator, а тип элемента - это тип элемента массива типа X.
  • В противном случае определите, имеет ли тип X соответствующий метод GetEnumerator:

    а. Выполните поиск элемента по типу X с идентификатором GetEnumerator и аргументами типа. Если поиск элемента не приводит к совпадению или вызывает неоднозначность или создает совпадение, которое не является группой методов, проверьте перечислимый интерфейс, как описано ниже. Рекомендуется, чтобы предупреждение выдавалось, если поиск элемента производит что-либо, кроме группы методов, или нет совпадения.

    б. Выполните разрешение перегрузки с помощью полученной группы методов и пустого списка аргументов. Если разрешение перегрузки не приводит к каким-либо применимым методам, приводит к двусмысленности или приводит к одному наилучшему методу, но этот метод является либо статическим, либо не общедоступным, проверьте перечислимый интерфейс, как описано ниже. Рекомендуется, чтобы было выдано предупреждение, если разрешение перегрузки создает что-либо, кроме однозначного метода публичных экземпляров или неприменимых методов.

    с. Если возвращаемый тип E метода GetEnumerator не является классом, структурой или интерфейсом, возникает ошибка и дальнейших шагов не предпринимается.

    д. Поиск члена выполняется на E с идентификаторами Current и no type. Если поиск элемента не соответствует совпадению, результатом является ошибка или результат - это что-либо, кроме свойства публичного экземпляра, которое разрешает чтение, возникает ошибка и дальнейших шагов не предпринимается.

    е. Поиск члена выполняется на E с идентификатором MoveNext и аргументами типа. Если поиск элемента не приводит к совпадению, результатом является ошибка или результат - это что-либо, кроме группы методов, возникает ошибка и дальнейших шагов не предпринимается.

    ф. Разрешение перегрузки выполняется в группе методов с пустым списком аргументов. Если разрешение перегрузки не приводит к каким-либо применимым методам, приводит к двусмысленности или приводит к одному наилучшему методу, но этот метод является либо статическим, либо не общедоступным, либо его тип возврата не является bool, возникает ошибка и дальнейших шагов не предпринимается.

    г. Тип коллекции - X, тип перечислителя - E, а тип элемента - тип свойства Current.

Таким образом, компилятор действует так, как будто foreach был следующим кодом, делая полиморфные вызовы и просматривая определённое перечисляемое определение интерфейса (если оно есть) для определения правильных типов и методов:

var iterator = listArray.GetEnumerator();
while(iterator.MoveNext())
{
   var item = iterator.Current;
   Console.WriteLine(item);
}