Вопрос по c#, language-specifications, foreach, language-implementation – Как foreach реализован в C #? [Дубликат]

21

This ques#ion already has an answer here:

How do foreach loops work in C#? 7 answers

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

Я представляю, что часть этого выглядит как:

var enumera#or = TInpu#.Ge#Enumera#or();
while(enumera#or.MoveNex#())
{
  // do some s#uff here
}

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

Вот как это реализованоreferencesource.microsoft.com/#System.Activities/System/… Alexander Ryan Baggett
По существу,// do some stuff here заменяется на внутреннюю частьforeach цикл "перед" сборник. (Вернее, компилятор генерирует эквивалентный байт-код.) millimoose
Не совсем дубликат связанныхquestion, Только название удаленно совпадает, но тело спрашивает совсем другое. Frédéric

Ваш Ответ

2   ответа
10

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

Сейчас есть 2 сценария, гдеforeach цикл может быть запущен по типу:

If the type has a public/non-static/non-generic/parameterless method named GetEnumerator which returns something that has a public MoveNext method and a public Current property. As noted by Mr Eric Lippert in this blog article, this was designed so as to accommodate pre generic era for both type safety and boxing related performance issues in case of value types. Note that this a case of duck typing. For instance this works:

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
{

}

This is then translated by the compiler to:

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();
}

If the type implements IEnumerable where theGetEnumerator returns IEnumerator that has a public MoveNext method and a public Current property. But an interesting sub case is that even if you implement IEnumerable explicitly (ie no public GetEnumerator method on Test class), you can have a foreach.

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

    }
}

This is because in this case foreach is implemented as (provided there is no other public GetEnumerator method in the class):

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();
}

If the type implements IEnumerable<T> explicitly then the foreach is converted to (provided there is no other public GetEnumerator method in the class):

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
}

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

In both the above cases the Enumerator class should have a public MoveNext method and a public Current property. In other words, if you're implementing IEnumerator interface it has to be implemented implicitly. For eg, foreach wont work for this enumerator:

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

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

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

(Thanks Roy Namir for pointing this out. foreach implementation isnt as easy it seems on the surface)

Enumerator precedence - It goes like if you have a public GetEnumerator method, then that is the default choice of foreach irrespective of who is implementing it. For example:

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

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

    }
}

If you don't have a public implementation (ie only explicit implementation), then precedence goes like IEnumerator<T> > IEnumerator.

There is a cast operator involved in the implementation of foreach where the collection element is cast back to the type (specified in the foreach loop itself). Which means even if you had written the SomethingEnumerator like this:

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

    public bool MoveNext()
    {

    }
}

You could write:

foreach (Something thing in new Test())
{

}

Because Something is type compatible with object, going by C# rules ,or in other words, the compiler lets it if there is an explicit cast possible between the two types. Otherwise the compiler prevents it. The actual cast is performed at run time which may or may not fail.

Конечно, еслиeither IEnumerable or IEnumerable<X> для одного типаX (IEnumerable<Y> может быть реализовано по-разному в патологических случаях) неявно, тогда, безусловно, есть «действительный»public GetEnumerator, так что подпадает под случай 1. выше. (Мы игнорируем случай, когдаGetEnumerator из базового классаhidden идентичной перегрузкой в соответствующем типе.) Но если вы реализуете интерфейсы явно, у вас все равно может быть "плохой" общественностиGetEnumerator, Например, если у вас естьpublic void/* bad */ GetEnumerator() { },IEnumerable не будет рассматриваться.
@JeppeStigNielsen Конечно, это подпадает под случай 1. Если есть общественныеGetEnumerator это все, что имеет значение. Я упоминал об этом в ответе. В качестве примера я привел более удивительный случай, т.е.GetEnumerator которые могут переопределить явные, даже если они из интерфейсов IEnumerable. И, конечно, он должен быть не универсальным и не статичным, что я обновлю. Спасибо!
@EricLippert спасибо, я обновлю это ..
Случай 1. предпринимается, еслиGetEnumerator является общедоступным, нестатичным, не универсальным и принимает нулевые параметры, если я правильно помню. ЕслиGetEnumerator не таков или не существует,IEnumerable<> или жеIEnumerable Считается.
Статья, о которой вы думаете, находится здесь:blogs.msdn.com/b/ericlippert/archive/2011/06/30/…
27

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

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

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

When iterating over an array (known at compile-time) the compiler can use a loop counter and compare with the length of the array instead of using an IEnumerator foreach will dispose of the iterator at the end; that's simple for IEnumerator<T> which extends IDisposable, but as IEnumerator doesn't, the compiler inserts a check to test at execution time whether the iterator implements IDisposable You can iterate over types which don't implement IEnumerable or IEnumerable<T>, so long as you have an applicable GetEnumerator() method which returns a type with suitable Current and MoveNext() members. As noted in comments, a type can also implement IEnumerable or IEnumerable<T> explicitly, but have a public GetEnumerator() method which returns a type other than IEnumerator/IEnumerator<T>. See List<T>.GetEnumerator() for an example - this avoids creating a reference type object unnecessarily in many cases.

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

@JonSkeet Я думаю, что слово о вовлеченном кастинге сделает этот ответ завершенным ..
@JamieDixon: я не понимаю, о чем ты спрашиваешь, я боюсь.
Спасибо @JonSkeet! Из интереса, есть ли тип параметра, который мы можем использовать в C #, который выглядит какType x in collection, Можем ли мы использовать этот диалект сами? Jamie Dixon
@hvd: True - отредактирую.
Ваша третья пуля предназначена не только для типов, которые не реализуютсяIEnumerable: когда тип реализует его, а также обеспечиваетGetEnumerator метод, который отличается отIEnumerable<T>.GetEnumeratorтип "собственный"GetEnumerator будет использоваться. Стандартный классList<T> хороший пример

Похожие вопросы