Требуется ли классу IEnumerable для использования Foreach
Это в С#, у меня есть класс, который я использую из какой-либо другой DLL. Он не реализует IEnumerable, но имеет 2 метода, которые передают IEnumerator. Есть ли способ использовать для них цикл foreach. Класс, который я использую, запечатан.
Ответы
Ответ 1
foreach
не требует IEnumerable
, вопреки распространенному мнению. Все, что требуется, это метод GetEnumerator
, который возвращает любой объект, который имеет метод MoveNext
и get-property Current
с соответствующими сигнатурами.
/EDIT: В вашем случае, однако, вам не повезло. Однако вы можете тривиально обернуть свой объект, чтобы сделать его перечислимым:
class EnumerableWrapper {
private readonly TheObjectType obj;
public EnumerableWrapper(TheObjectType obj) {
this.obj = obj;
}
public IEnumerator<YourType> GetEnumerator() {
return obj.TheMethodReturningTheIEnumerator();
}
}
// Called like this:
foreach (var xyz in new EnumerableWrapper(yourObj))
…;
/EDIT: следующий метод, предложенный несколькими людьми, не работает, если метод возвращает IEnumerator
:
foreach (var yz in yourObj.MethodA())
…;
Ответ 2
Re: Если foreach не требует явного контракта интерфейса, находит ли он GetEnumerator, используя отражение?
(Я не могу комментировать, так как у меня недостаточно высокая репутация.)
Если вы подразумеваете отражение во время выполнения, то нет. Он делает это все compiletime, еще один известный факт заключается в том, что он также проверяет, доступен ли возвращаемый объект, который мог бы реализовать IEnumerator.
Чтобы увидеть это в действии, рассмотрите этот (исполняемый) фрагмент.
using System;
using System.Collections.Generic;
using System.Text;
namespace ConsoleApplication3
{
class FakeIterator
{
int _count;
public FakeIterator(int count)
{
_count = count;
}
public string Current { get { return "Hello World!"; } }
public bool MoveNext()
{
if(_count-- > 0)
return true;
return false;
}
}
class FakeCollection
{
public FakeIterator GetEnumerator() { return new FakeIterator(3); }
}
class Program
{
static void Main(string[] args)
{
foreach (string value in new FakeCollection())
Console.WriteLine(value);
}
}
}
Ответ 3
Согласно MSDN:
foreach (type identifier in expression) statement
где выражение:
Коллекция объектов или выражение массива. Тип элемента коллекции должен быть конвертирован в идентификатор тип. Не используйте выражение, которое имеет значение null. Вычисляет тип который реализует IEnumerable или тип который объявляет метод GetEnumerator. В последнем случае GetEnumerator должен либо вернуть тип, который реализует IEnumerator или объявляет все методы, определенные в IEnumerator.
Ответ 4
Краткий ответ:
Вам нужен класс с методом GetEnumerator, который возвращает IEnumerator, который у вас уже есть. Достичь этого с помощью простой оболочки:
class ForeachWrapper
{
private IEnumerator _enumerator;
public ForeachWrapper(Func<IEnumerator> enumerator)
{
_enumerator = enumerator;
}
public IEnumerator GetEnumerator()
{
return _enumerator();
}
}
Использование:
foreach (var element in new ForeachWrapper(x => myClass.MyEnumerator()))
{
...
}
Обработка времени компиляции оператор foreach сначала определяет тип коллекции, тип перечислителя и тип элемента выражения. Эта определение происходит следующим образом:
-
Если тип выражения X является типом массива, то существует неявный обращение ссылки от X к System.Collections.IEnumerable интерфейса (поскольку System.Array реализует этот интерфейс). тип коллекции System.Collections.IEnumerable интерфейса, тип перечислителя является System.Collections.IEnumerator интерфейс и тип элемента тип элемента массива X.
-
В противном случае определите, имеет ли тип X соответствующий Метод GetEnumerator:
-
Выполните поиск элемента по типу X с идентификатором GetEnumerator и no типа. Если поиск элемента не дает соответствия, или вызывает двусмысленность или создает совпадение, которое не является группой методов, проверить перечислимый интерфейс как описано ниже. Рекомендуется что предупреждение выдается, если член поиск производит что-либо, кроме группы методов или нет совпадения.
-
Выполните разрешение перегрузки, используя полученную группу методов и пустой список аргументов. Если перегрузка результаты разрешения не применимы методов, приводит к двусмысленности или приводит к одному наилучшему методу, но этот метод является либо статическим, либо не публичный, проверить перечислимый интерфейс, как описано ниже. это рекомендовал выпустить предупреждение если возникает перегрузка ничего, кроме недвусмысленной публики метод экземпляра или не применяется Методы.
-
Если тип возврата E метода GetEnumerator не является классом, структуры или типа интерфейса, ошибка и дальнейшие шаги приняты.
-
Поиск пользователя выполняется на E с идентификатором Current и no типа. Если поиск элемента не дает совпадения, результатом является ошибка, или результат - что угодно кроме свойства публичного экземпляра, которое разрешает чтение, возникает ошибка и никаких дальнейших шагов не предпринимается.
-
Поиск пользователя выполняется на E с идентификатором MoveNext и no типа. Если поиск элемента не дает совпадения, результатом является ошибка, или результат - что угодно кроме группы методов, ошибка и дальнейшие шаги приняты.
-
Разрешение перегрузки выполняется в группе методов с пустым список аргументов. Если разрешение перегрузки не приводит к каким-либо применимым методам, приводит к двусмысленности или приводит к единственный лучший метод, но этот метод является либо статическим, либо не публичным, либо его тип возврата не является bool, ошибка и дальнейшие шаги приняты.
-
Тип коллекции - X, тип перечислителя - E, а элемент тип - тип тока свойство.
-
В противном случае проверьте перечислимый интерфейс:
-
Если существует ровно один тип T такой, что существует неявный преобразование из X в интерфейс System.Collections.Generic.IEnumerable < Т > , то тип коллекции - это интерфейса, тип перечислителя является интерфейс System.Collections.Generic.IEnumerator < Т > , и тип элемента - T.
-
В противном случае, если существует более одного такого типа T, то ошибка и дальнейшие шаги приняты.
-
В противном случае, если есть неявное преобразование из X в System.Collections.IEnumerable интерфейса, тогда тип коллекции этот интерфейс, тип перечислителя интерфейс System.Collections.IEnumerator и тип элемента - это объект.
-
В противном случае возникает ошибка, и дальнейшие шаги не предпринимаются.
Ответ 5
Не строго. Пока у класса есть требуемые элементы GetEnumerator, MoveNext, Reset и Current, он будет работать с foreach
Ответ 6
Нет, нет, и вам даже не нужен метод GetEnumerator, например:
class Counter
{
public IEnumerable<int> Count(int max)
{
int i = 0;
while (i <= max)
{
yield return i;
i++;
}
yield break;
}
}
который называется так:
Counter cnt = new Counter();
foreach (var i in cnt.Count(6))
{
Console.WriteLine(i);
}
Ответ 7
Вы всегда можете его обернуть, и, поскольку в стороне, чтобы быть "недоступным", вам нужен только метод, называемый "GetEnumerator" с соответствующей подписью.
class EnumerableAdapter
{
ExternalSillyClass _target;
public EnumerableAdapter(ExternalSillyClass target)
{
_target = target;
}
public IEnumerable GetEnumerator(){ return _target.SomeMethodThatGivesAnEnumerator(); }
}
Ответ 8
Учитывая класс X с методами A и B, которые возвращают IEnumerable, вы можете использовать foreach в классе следующим образом:
foreach (object y in X.A())
{
//...
}
// or
foreach (object y in X.B())
{
//...
}
Предположительно, значение для перечислений, возвращаемых A и B., определено корректно.
Ответ 9
@Brian: Не уверен, что вы пытаетесь перебрать значение return из вызова метода или самого класса,
Если вам нужен класс, тогда сделайте его массивом, который вы можете использовать с foreach.
Ответ 10
Для того чтобы класс, который можно было бы использовать с foeach, все, что ему нужно сделать, это иметь открытый метод, который возвращает и IEnumerator с именем GetEnumerator(), что он:
Возьмем следующий класс, он не реализует IEnumerable или IEnumerator:
public class Foo
{
private int[] _someInts = { 1, 2, 3, 4, 5, 6 };
public IEnumerator GetEnumerator()
{
foreach (var item in _someInts)
{
yield return item;
}
}
}
альтернативно метод GetEnumerator() может быть записан:
public IEnumerator GetEnumerator()
{
return _someInts.GetEnumerator();
}
При использовании в foreach (обратите внимание, что оболочка no не используется, просто экземпляр класса):
foreach (int item in new Foo())
{
Console.Write("{0,2}",item);
}
печатает:
1 2 3 4 5 6
Ответ 11
Для типа требуется только общедоступный/нестатический/не общий/без параметров метод с именем GetEnumerator
, который должен возвращать то, что имеет общедоступный метод MoveNext
и общедоступное свойство Current
. Когда я когда-то вспоминал г-на Эрика Липперта, , это было разработано таким образом, чтобы соответствовать предыстории эпох как для безопасности типов, так и для связанных с боксом проблем производительности в случае типов значений.
Например, это работает:
class Test
{
public SomethingEnumerator GetEnumerator()
{
}
}
class SomethingEnumerator
{
public Something Current //could return anything
{
get { }
}
public bool MoveNext()
{
}
}
//now you can call
foreach (Something thing in new Test()) //type safe
{
}
Затем это переводется компилятором на:
var enumerator = new Test().GetEnumerator();
try {
Something element; //pre C# 5
while (enumerator.MoveNext()) {
Something element; //post C# 5
element = (Something)enumerator.Current; //the cast!
statement;
}
}
finally {
IDisposable disposable = enumerator as System.IDisposable;
if (disposable != null) disposable.Dispose();
}
Из раздела 8.8.4 спецификации
Что-то, стоит отметить, связано с приоритетом перечислителя - похоже, если у вас есть метод public GetEnumerator
, то это выбор по умолчанию foreach
по умолчанию, независимо от того, кто его реализует. Например:
class Test : IEnumerable<int>
{
public SomethingEnumerator GetEnumerator()
{
//this one is called
}
IEnumerator<int> IEnumerable<int>.GetEnumerator()
{
}
}
(Если у вас нет публичной реализации (т.е. только явная реализация), тогда приоритет будет выглядеть как IEnumerator<T>
> IEnumerator
.)