С# `foreach` поведение - Уточнение?
Я прочитал статью Eric здесь о перечислении foreach и о различных сценариях, в которых foreach может работать
Чтобы предотвратить старую версию С# для бокса, команда С# включила утиную печать для foreach для запуска в коллекции, отличной от Ienumerable. (Публичный GetEnumerator
, который возвращает то, что имеет общедоступные MoveNext
и Current
достаточно (.
Итак, Эрик написал образец:
class MyIntegers : IEnumerable
{
public class MyEnumerator : IEnumerator
{
private int index = 0;
object IEnumerator.Current { return this.Current; }
int Current { return index * index; }
public bool MoveNext()
{
if (index > 10) return false;
++index;
return true;
}
}
public MyEnumerator GetEnumerator() { return new MyEnumerator(); }
IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); }
}
Но я считаю, что у него есть некоторые опечатки (отсутствует get accessor при реализации свойства Current
), которые мешают ему компилировать (я уже отправил его по электронной почте).
В любом случае это рабочая версия:
class MyIntegers : IEnumerable
{
public class MyEnumerator : IEnumerator
{
private int index = 0;
public void Reset()
{
throw new NotImplementedException();
}
object IEnumerator.Current {
get { return this.Current; }
}
int Current {
get { return index*index; }
}
public bool MoveNext()
{
if (index > 10) return false;
++index;
return true;
}
}
public MyEnumerator GetEnumerator() { return new MyEnumerator(); }
IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); }
}
Ok.
Согласно MSDN:
Тип C
называется collection type
, если он реализует System.Collections.IEnumerable
интерфейс или реализует collection pattern
, встретив все следующие критерии:
-
C содержит метод публичного экземпляра с подписями GetEnumerator(), который возвращает тип struct-type, class-type или interface-type, который называется E в следующем тексте.
-
E содержит метод общедоступного экземпляра с сигнатурой MoveNext() и возвращаемым типом bool.
-
E содержит свойство публичного экземпляра с именем Current, которое позволяет считывать текущее значение. Тип этого свойства называется типом элемента типа коллекции.
OK. Пусть сопоставление документов с образцом Эрика
Образец Eric называется collection type
, потому что он реализует интерфейс System.Collections.IEnumerable
(явно, хотя). Но это не (!) a collection pattern
из-за пули 3: MyEnumerator
не имеет свойства публичного экземпляра с именем Current.
MSDN говорит:
Если выражение коллекции имеет тип, который реализует (как определено выше), расширение foreach утверждение:
E enumerator = (collection).GetEnumerator();
try {
while (enumerator.MoveNext()) {
ElementType element = (ElementType)enumerator.Current;
statement;
}
}
finally {
IDisposable disposable = enumerator as System.IDisposable;
if (disposable != null) disposable.Dispose();
}
В противном случае, выражение коллекции имеет тип, который реализует System.IEnumerable(!), а расширение оператора foreach:
IEnumerator enumerator =
((System.Collections.IEnumerable)(collection)).GetEnumerator();
try {
while (enumerator.MoveNext()) {
ElementType element = (ElementType)enumerator.Current;
statement;
}
}
finally {
IDisposable disposable = enumerator as System.IDisposable;
if (disposable != null) disposable.Dispose();
}
Вопрос № 1
Кажется, что образец Эрика не реализует
collection pattern
и System.IEnumerable
- поэтому он не должен соответствовать ни одному из указанных выше условий. Итак, почему я все еще могу перебрать его через:
foreach (var element in (new MyIntegers() as IEnumerable ))
{
Console.WriteLine(element);
}
Вопрос № 2
Почему я должен упомянуть new MyIntegers() as IEnumerable
? он уже Ienumerable (!!) и даже после этого, разве компилятор уже выполняет эту работу самостоятельно посредством кастинга:
((System.Collections.IEnumerable)(collection)).GetEnumerator() ?
Это прямо здесь:
IEnumerator enumerator =
((System.Collections.IEnumerable)(collection)).GetEnumerator();
try {
while (enumerator.MoveNext()) {
...
Итак, почему он все еще хочет, чтобы я упоминал как Ienumerable?
Ответы
Ответ 1
MyEnumerator не имеет необходимых общедоступных методов
Да, это так - вернее, если бы Current
были общедоступными. Все, что требуется, это то, что он имеет:
- Публичное, читаемое свойство
Current
- Открытый
MoveNext()
метод без аргументов типа, возвращающих bool
Отсутствие public
здесь было просто еще одной опечаткой, в основном. Как бы то ни было, пример не делает то, что он подразумевал (предотвращает бокс). Он использует реализацию IEnumerable
, потому что вы используете new MyIntegers() as IEnumerable
- поэтому тип выражения IEnumerable
, и он просто использует интерфейс повсюду.
Вы утверждаете, что он не реализует IEnumerable
, (который является System.Collections.IEnumerable
, btw), но он делает это, используя явную реализацию интерфейса.
Проще всего протестировать такие вещи без реализации IEnumerable
:
using System;
class BizarreCollection
{
public Enumerator GetEnumerator()
{
return new Enumerator();
}
public class Enumerator
{
private int index = 0;
public bool MoveNext()
{
if (index == 10)
{
return false;
}
index++;
return true;
}
public int Current { get { return index; } }
}
}
class Test
{
static void Main(string[] args)
{
foreach (var item in new BizarreCollection())
{
Console.WriteLine(item);
}
}
}
Теперь, если вы сделаете Current
private, он не будет компилироваться.
Ответ 2
Ссылка на System.IEnumerable
на MSDN - это не что иное, как опечатка в спецификации старого языка, такой интерфейс не существует, я считаю, что он должен ссылаться на System.Collections.IEnumerable
.
Вы действительно должны прочитать спецификацию языка версии С#, которую вы используете, доступна языковая спецификация С# 5.0 здесь.
Некоторая дополнительная информация о том, почему не удается выполнить этот пример, приводит к ошибке, а не к возврату к использованию System.Collections.IEnumerable
:
В спецификации для foreach
(раздел 8.8.4) вы увидите, что правила немного изменились (некоторые сокращения были сокращены для краткости):
- Выполните поиск элемента по типу X с идентификатором GetEnumerator и без аргументов типа. Если поиск элемента не приводит к совпадению или вызывает неоднозначность или создает совпадение, которое не является группой методов, проверьте перечислимый интерфейс, как описано ниже. Рекомендуется, чтобы предупреждение выдавалось, если поиск элемента производит что-либо, кроме группы методов, или нет совпадения.
- Выполните разрешение перегрузки с помощью полученной группы методов и пустого списка аргументов. Если разрешение перегрузки не приводит к каким-либо применимым методам, приводит к двусмысленности или приводит к одному наилучшему методу, но этот метод является либо статическим, либо не общедоступным, проверьте перечислимый интерфейс, как описано ниже. Рекомендуется выдавать предупреждение, если разрешение перегрузки создает что-либо, кроме однозначного метода публичного экземпляра или не применимых методов.
- Если тип возврата E метода GetEnumerator не является типом класса, структуры или интерфейса, возникает ошибка и дальнейших шагов не предпринимается.
- Поиск элемента выполняется на E с идентификатором Current и no type. Если поиск элемента не дает совпадения, результатом является ошибка, или результат - это что-либо, кроме свойства публичного экземпляра, которое разрешает чтение, возникает ошибка и дальнейших шагов не предпринимается.
- Тип коллекции - X, тип перечислителя - E, а тип элемента - тип свойства Current.
Итак, с первой точки пули мы найдем public MyEnumerator GetEnumerator()
, вторая и третья пули пройдут без ошибок. Когда мы добираемся до четвертого пункта, никакой публичный член с именем Current
не доступен, что приводит к ошибке, которую вы видите без приведения, эта точная ситуация никогда не дает компилятору возможность поиска перечислимого интерфейса.
При явной передаче экземпляра в IEnumerable
все требования удовлетворяются, поскольку тип IEnumerable
и связанный с ним IEnumerator
удовлетворяют всем требованиям.
Также из документации:
Вышеупомянутые шаги, если они успешны, однозначно создают тип коллекции C, тип перечислителя E и тип элемента T. Оператор foreach формы
foreach (V v in x) embedded-statement
затем расширяется до:
{
E e = ((C)(x)).GetEnumerator();
try {
while (e.MoveNext()) {
V v = (V)(T)e.Current;
embedded-statement
}
}
finally {
… // Dispose e
}
}
Таким образом, при явном приведении в IEnumerable
вы получите следующее:
- C =
System.Collections.IEnumerable
- x =
new MyIntegers() as System.Collections.IEnumerable
- E =
System.Collections.IEnumerator
- T =
System.Object
- V =
System.Object
{
System.Collections.IEnumerator e = ((System.Collections.IEnumerable)(new MyIntegers() as System.Collections.IEnumerable)).GetEnumerator();
try {
while (e.MoveNext()) {
System.Object element = (System.Object)(System.Object)e.Current;
Console.WriteLine(element);
}
}
finally {
System.IDisposable d = e as System.IDisposable;
if (d != null) d.Dispose();
}
}
Это объясняет, почему использование литых работ.