Нужно ли классу реализовывать IEnumerable для использования Foreach?

StackOverflow https://stackoverflow.com/questions/127233

  •  02-07-2019
  •  | 
  •  

Вопрос

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

Это было полезно?

Решение

foreach делает нет требовать IEnumerable, вопреки общему мнению.Все, что для этого требуется, — это метод GetEnumerator который возвращает любой объект, имеющий метод MoveNext и свойство get Current с соответствующими подписями.

/РЕДАКТИРОВАТЬ:Однако в вашем случае вам не повезло.Однако вы можете тривиально обернуть свой объект, чтобы сделать его перечислимым:

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))
    …;

/РЕДАКТИРОВАТЬ:Следующий метод, предложенный несколькими людьми, позволяет нет работать, если метод возвращает IEnumerator:

foreach (var yz in yourObj.MethodA())
    …;

Другие советы

Ре:Если 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);
        }
    }
}

В соответствии с MSDN:

foreach (type identifier in expression) statement

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

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

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

Вам нужен класс с методом с именем 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.Array реализует этот интерфейс).Тип сбора - это System.collections.Ienumerable Interface, тип перечисления - это интерфейс System.collections.Ienumerator, а тип элемента - тип элемента типа массива X.

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

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

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

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

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

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

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

    • Тип сбора - x, тип перечисления e e, а тип элемента - тип текущего свойства.

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

    • Если есть ровно один тип T, так что существует неявное преобразование из x в интерфейсную систему. Collections.generic.Ienumerableu003CT> , тогда тип сбора является этим интерфейсом, тип перечислителя является интерфейсной системой. Collections.generic.ienumeratoru003CT> и тип элемента Т.

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

    • В противном случае, если существует неявное преобразование из x в систему. КОЛЛЕКЦИИ. ИНТУРЕНТАЛЬНЫЙ интерфейс, то тип сбора - это интерфейс, тип перечисления является интерфейсной системой.collections.Ienumerator, а тип элемента является объектом.

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

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

Нет, это не так, и вам даже не нужен метод 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);
}

Вы всегда можете обернуть его, и, кроме того, чтобы быть «доступным», вам нужно только иметь метод под названием «GetEnumerator» с правильной сигнатурой.


class EnumerableAdapter
{
  ExternalSillyClass _target;

  public EnumerableAdapter(ExternalSillyClass target)
  {
    _target = target;
  }

  public IEnumerable GetEnumerator(){ return _target.SomeMethodThatGivesAnEnumerator(); }

}

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

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

// or

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

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

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

Чтобы класс можно было использовать с 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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top