Как реализован foreach в C #? [Дубликат]

На этот вопрос уже есть ответ:

Как работают циклы foreach в C #? 7 ответов

Как именноforeach реализовано в C #?

Я думаю, что часть этого выглядит так:

var enumerator = TInput.GetEnumerator();
while(enumerator.MoveNext())
{
  // do some stuff here
}

Однако я не уверен, что на самом деле происходит. Какая методология используется для возвратаenumerator.Current для каждого цикла? Возвращает ли он [для каждого цикла] или требует анонимную функцию или что-то для выполнения телаforeach?

 millimoose24 июн. 2012 г., 18:34
По сути,// do some stuff here заменяется на внутреннюю частьforeach цикл "до" компиляции. (Вернее, компилятор генерирует эквивалентный байт-код.)
 Alexander Ryan Baggett30 мар. 2017 г., 00:11
 Frédéric04 июн. 2017 г., 22:53
Не совсем дубликат связанныхвопро. Только название удаленно совпадает, но тело спрашивает совсем другое.

Ответы на вопрос(2)

точная реализация не затронута. Хотя то, что вы разместили в вопросе, является самой простой формой, полная реализация (включая удаление перечислителя, приведение и т. Д.) Находится в 8.8.4 раздел спецификации.

Теперь есть 2 сценария, гдеforeachикл @ может выполняться по типу:

Если у типа есть открытый / нестатический / неуниверсальный / беспараметрический метод с именемGetEnumerator который возвращает что-то общедоступноеMoveNext метод и публичныйCurrent свойство Как отметил г-н Эрик Липперт в этой статье блога, это было разработано так, чтобы учесть предварительную эру как для безопасности типов, так и для проблем производительности, связанных с боксом в случае типов значений. Обратите внимание, что это случай утка набирает. Например, это работает:

class Test
{
    public SomethingEnumerator GetEnumerator()
    {

    }
}

class SomethingEnumerator
{
    public Something Current //could return anything
    {
        get { return ... }
    }

    public bool MoveNext()
    {

    }
}

//now you can call
foreach (Something thing in new Test()) //type safe
{

}

Это затем переводится компилятором в:

E enumerator = (collection).GetEnumerator();
try {
   ElementType element; //pre C# 5
   while (enumerator.MoveNext()) {
      ElementType element; //post C# 5
      element = (ElementType)enumerator.Current;
      statement;
   }
}
finally {
   IDisposable disposable = enumerator as System.IDisposable;
   if (disposable != null) disposable.Dispose();
}

Если тип реализуетIEnumerable гдGetEnumerator возвращаетIEnumerator у которого есть публикаMoveNext метод и публичныйCurrent свойство Но интересный случай в том, что даже если вы реализуетеIEnumerable явно (т.е. нет публичногоGetEnumerator метод наTest класс), вы можете иметьforeach.

class Test : IEnumerable
{
    IEnumerator IEnumerable.GetEnumerator()
    {

    }
}

Это потому, что в этом случаеforeach реализован как (при условии, что нет других открытыхGetEnumerator метод в классе):

IEnumerator enumerator = ((IEnumerable)(collection)).GetEnumerator();
try {
    ElementType element; //pre C# 5
    while (enumerator.MoveNext()) {
        ElementType element; //post C# 5
        element = (ElementType)enumerator.Current;
        statement;
   }
}
finally {
    IDisposable disposable = enumerator as System.IDisposable;
    if (disposable != null) disposable.Dispose();
}

Если тип реализуетIEnumerable<T> явно тогдаforeach преобразуется в (при условии, что нет других открытыхGetEnumerator метод в классе):

IEnumerator<T> enumerator = ((IEnumerable<T>)(collection)).GetEnumerator();
try {
    ElementType element; //pre C# 5
    while (enumerator.MoveNext()) {
        ElementType element; //post C# 5
        element = (ElementType)enumerator.Current; //Current is `T` which is cast
        statement;
   }
}
finally {
    enumerator.Dispose(); //Enumerator<T> implements IDisposable
}

Несколько интересных вещей, на которые стоит обратить внимание:

В обоих приведенных выше случаяхEnumerator класс должен быть публичнымMoveNext метод и публичныйCurrent свойство. Другими словами, если вы реализуетеIEnumerator интерфейс должен быть реализован неявно. Например,foreach не будет работать для этого перечислителя:

public class MyEnumerator : IEnumerator
{
    void IEnumerator.Reset()
    {
        throw new NotImplementedException();
    }

    object IEnumerator.Current
    {
        get { throw new NotImplementedException(); }
    }

    bool IEnumerator.MoveNext()
    {
        throw new NotImplementedException();
    }
}

(Спасибо, Рой Намир, за то, что указал на это.foreach реализация не так проста, как кажется на первый взгляд)

риоритет @Enumerator - похоже, если у вас естьpublic GetEnumerator метод, тогда это выбор по умолчаниюforeach независимо от того, кто его реализует. Например

class Test : IEnumerable<int>
{
    public SomethingEnumerator GetEnumerator()
    {
        //this one is called
    }

    IEnumerator<int> IEnumerable<int>.GetEnumerator()
    {

    }
}

Если у вас нет публичной реализации (т. Е. Только явной реализации), то приоритет имеет видIEnumerator<T> > IEnumerator.

В реализации @ задействован оператор приведенforeach где элемент коллекции приводится к типу (указанному вforeach цикл сам). Что означает, даже если вы написаSomethingEnumerator так

class SomethingEnumerator
{
    public object Current //returns object this time
    {
        get { return ... }
    }

    public bool MoveNext()
    {

    }
}

Ты можешь написать:

foreach (Something thing in new Test())
{

}

Потому чтоSomething совместим по типу сobject, следуя правилам C #, или другими словами, компилятор позволяет это, если существует явное приведение между двумя типами. В противном случае компилятор предотвращает это. Фактическое приведение выполняется во время выполнения, которое может или не может потерпеть неудачу.

 Eric Lippert02 дек. 2013 г., 17:19
Статья, о которой ты думаешь, здесь: Blogs.msdn.com / б / ericlippert / Архив / 2011/06/30 / ...
 nawfal02 дек. 2013 г., 17:52
@ EricLippert спасибо, я обновлю это ..
 Jeppe Stig Nielsen02 дек. 2013 г., 22:18
Конечно, еслиил IEnumerable ил IEnumerable<X> для одного типаX (IEnumerable<Y> может быть реализовано по-разному в патологических случаях) реализовано неявно, тогда, безусловно, есть «действительный»public GetEnumerator, так что подпадает под случай 1. выше. (Мы игнорируем случай, когдаGetEnumerator из базового класса - это Скрытый идентичной перегрузкой в соответствующем типе.) Но если вы реализуете интерфейсы явно, у вас все равно может быть «плохая» publicGetEnumerator. Например, если у вас естьpublic void/* bad */ GetEnumerator() { },IEnumerable не будет рассматриваться.
 Jeppe Stig Nielsen02 дек. 2013 г., 22:21
Случай 1. выполняется, еслиGetEnumerator является открытым, нестатичным, не универсальным и принимает нулевые параметры, если я правильно помню. ЕслиGetEnumerator не такой или не существует,IEnumerable<> илиIEnumerable Считается
 nawfal04 дек. 2013 г., 19:42
@ JeppeStigNielsen Конечно, это подпадает под случай 1. Если есть публичныйGetEnumerator это все, что имеет значение. Я упоминал об этом в ответе. В качестве примера я привел более удивительный случай, то есть простой автономный publicGetEnumerator, который может переопределить явные, даже если они из интерфейсов IEnumerable. И, конечно, он должен быть не универсальным и не статичным, что я обновлю. Благодарность
Решение Вопроса

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

foreach Не вызов функции - он встроен в сам язык, как иfor петли иwhile петли. Нет необходимости что-либо возвращать или «брать» функцию любого рода.

Обратите внимание, чтоforeach имеет несколько интересных морщин:

При переборе массива (известного во время компиляции) компилятор может использовать счетчик циклов и сравнивать с длиной массива вместо использованияIEnumeratorforeach избавится от итератора в конце; это просто дляIEnumerator<T> который расширяетIDisposable, ноIEnumerator Не, компилятор вставляет проверку для проверки во время выполнения, реализует ли итераторIDisposable Вы можете перебирать типы, которые не поддерживаютIEnumerable илиIEnumerable<T>, пока у вас есть соответствующийGetEnumerator() метод, который возвращает тип с подходящимCurrent а такжеMoveNext() участники. Как отмечено в комментариях, тип можеттакж воплощать в жизньIEnumerable илиIEnumerable<T> явно, но есть публичныйGetEnumerator() метод, который возвращает тип, отличный отIEnumerator/IEnumerator<T>. ВидетьList<T>.GetEnumerator() для примера - во многих случаях это позволяет избежать ненужного создания объекта ссылочного типа.

См. Раздел 8.8.4 спецификации C # 4 для получения дополнительной информации.

 Jamie Dixon24 июн. 2012 г., 19:56
Спасибо @JonSkeet! Из интереса, есть ли тип параметра, который мы можем использовать в C #, который выглядит какType x in collection. Можем ли мы сами использовать этот диалек
 Jon Skeet24 июн. 2012 г., 18:52
@ hvd: True - отредактирую.
 Jon Skeet24 июн. 2012 г., 20:05
@ JamieDixon: Боюсь, я не понимаю, о чем ты спрашиваешь.
 nawfal01 дек. 2013 г., 11:38
@ JonSkeet Я думаю, что слово о вовлеченном кастинге сделает этот ответ завершенным ..
 user74338224 июн. 2012 г., 18:39
Твоя третья пуля не только для типов, которые не реализуютIEnumerable: когда тип реализует его, а также предоставляетGetEnumerator метод, отличный отIEnumerable<T>.GetEnumerator, собственный тGetEnumerator будет использовано. Стандартный классList<T> хороший пример.

Ваш ответ на вопрос