Должен ли класс реализовывать IEnumerable для использования Foreach? - PullRequest
15 голосов
/ 24 сентября 2008

Это в C #, у меня есть класс, который я использую из чужой библиотеки DLL. Он не реализует IEnumerable, но имеет 2 метода, которые возвращают IEnumerator. Есть ли способ, которым я могу использовать цикл foreach на них. Класс, который я использую, запечатан.

Ответы [ 11 ]

15 голосов
/ 24 сентября 2008

foreach не не требует IEnumerable, вопреки распространенному мнению. Все, что для этого требуется - это метод GetEnumerator, который возвращает любой объект, имеющий метод MoveNext и свойство get 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())
    …;
7 голосов
/ 24 сентября 2008

Re: Если foreach не требует явного контракта интерфейса, находит ли он GetEnumerator, использующий отражение?

(я не могу комментировать, поскольку у меня недостаточно высокая репутация.)

Если вы подразумеваете время выполнения отражение, то нет. Он выполняет все время компиляции, еще один менее известный факт - он также проверяет, является ли возвращаемый объект, который может Реализует 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);
        }
    }
}
6 голосов
/ 24 сентября 2008

Согласно MSDN :

foreach (type identifier in expression) statement

где выражение:

Коллекция объектов или массив выражений. Тип элемента коллекции должен быть преобразован в идентификатор тип. Не используйте выражение, которое оценивается как ноль. Оценивает тип который реализует IEnumerable или тип который объявляет метод GetEnumerator. В последнем случае GetEnumerator должен либо вернуть тип, который реализует IEnumerator или объявляет все методы, определенные в IEnumerator.

4 голосов
/ 24 сентября 2008

Краткий ответ:

Вам нужен класс с методом с именем 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()))
{
  ...
}

Из спецификации языка C # :

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

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

  • В противном случае определите, имеет ли тип X соответствующий Метод GetEnumerator:

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

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

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

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

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

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

    • Тип коллекции X, тип перечислителя E и элемент тип является типом текущего свойство.

  • В противном случае проверьте перечислимый интерфейс:

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

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

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

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

3 голосов
/ 24 сентября 2008

Не строго. Пока у класса есть обязательные члены GetEnumerator, MoveNext, Reset и Current, он будет работать с foreach

2 голосов
/ 24 сентября 2008

Нет, вам не нужно и вам даже не нужен метод GetEnumerator, например ::10000

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);
}
0 голосов
/ 01 декабря 2013

Для типа требуется только открытый / нестатический / неуниверсальный / беспараметрический метод с именем 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. )

0 голосов
/ 26 сентября 2008

Чтобы класс можно было использовать с 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 (обратите внимание, что обертка не используется, просто экземпляр класса):

    foreach (int item in new Foo())
    {
        Console.Write("{0,2}",item);
    }

печать:

1 2 3 4 5 6

0 голосов
/ 24 сентября 2008

@ Брайан: Не уверен, что вы пытаетесь перебрать значение, возвращаемое из вызова метода или самого класса, Если вам нужен класс, то сделайте его массивом, который вы можете использовать с foreach.

0 голосов
/ 24 сентября 2008

Учитывая класс X с методами A и B, которые оба возвращают IEnumerable, вы можете использовать foreach для класса следующим образом:

foreach (object y in X.A())
{
    //...
}

// or

foreach (object y in X.B())
{
   //...
}

Предположительно, значение для перечислимых значений, возвращаемых A и B., четко определено.

...