При чтении поста были даны некоторые моменты без примера :
Для реализации IEnumerable/IEnumerable необходимо предоставить перечислитель:
• Если класс "оборачивает" другую коллекцию, возвращая перечислитель обернутой коллекции.
• Через итератор с использованием yield return.
• Путем создания собственной реализации IEnumerator/IEnumerator
(Мой детский разум интерпретирует это как)
(пункт 1)
If the class is "wrapping" another collection, by returning the
wrapped collection's enumerator.
Будет ли это означать..
class StringCollections
{
//A class is wrapping another collection
string[] names=new string {"Jon Skeet","Hamish Smith",
"Marc Gravell","Jrista","Joren"};
//by returning the wrapped collection’s enumerator
public IEnumerator GetEnumerator( )
{
// What should I return here ?
//like the following ?
yield return names[0];
yield return names[1];
yield return names[2];
....
(or)
foreach(string str in names)
{
yield return str;
}
}
}
(пункт 2)
•Via an iterator using yield return.(This point was well explained
by Marc Gravell)
Точка 3
By instantiating your own IEnumerator/IEnumerator<T> implementation*
Что здесь представляет пункт 3? Поскольку нет примера, я не получил его. Означает ли это, что я могу создать собственный счетчик .. (правильно?). Мой вопрос здесь в том, что когда предварительных счетчиков/перечислителей достаточно (как новичок, я не должен слепо подтверждать это) для итераций, почему я должен заботиться о пользовательском? Хорошо пример прояснит мои сомнения.
Спасибо за чтение этой длинной истории и добрый ответ.
(пункт 1) Вы можете связать вызов GetEnumerator с другим перечислителем коллекции:
public class PrimeNumbers : IEnumerable
{
public IEnumerator GetEnumerator()
{
var primes = new List<int> { 2, 3, 5, 7, 11 };
return primes.GetEnumerator();
}
}
(пункт 2) Аналогично примеру в вашем коде
public IEnumerator GetEnumerator()
{
var primes = new List<int> { 2, 3, 5, 7, 11 };
foreach (var number in primes)
{
yield return number;
}
}
Или поместите перечислитель с логикой:
public class PrimeNumbers : IEnumerable
{
public IEnumerator GetEnumerator()
{
for(int i=2; ;i++)
{
if(IsPrime(i))
{
yield return i;
}
}
}
}
(пункт 3) Существует не так много случаев, когда вы хотите реализовать свой собственный Enumerator, но, например, вы можете искать бесконечный набор значений, например. простые числа:
public class PrimeNumbers : IEnumerable
{
public IEnumerator GetEnumerator()
{
return new MyEnumerator();
}
}
public class MyEnumerator : IEnumerator
{
private int lastPrimeNumber = 1;
public bool MoveNext()
{
lastPrimeNumber = /* some logic that find the next prime */;
return true; // There is always next prime
}
public void Reset()
{
lastPrimeNumber = 1;
}
public object Current
{
get { return lastPrimeNumber; }
}
}
Пример использования может быть:
public void PrintAllPrimes()
{
var numbers = new PrimeNumbers();
// This will never end but it'll print all primes until my PC crash
foreach (var number in numbers)
{
Console.WriteLine(number);
}
}
Плюсы и минусы, о которых я могу думать:
Пункт 1. Это самый простой способ перечисления элементов, но он требует знания всех элементов заранее.
Пункт 2: когда в перечислении есть какая-то логика, это читабельно, а также лениво, поэтому нет необходимости вычислять элемент до тех пор, пока он не будет действительно запрошен.
Точка 3: повторное использование проще, но менее читабельно (вычислить элемент в MoveNext, но фактически вернуть его из свойства Current).
Ответ 2 · рейтинг 2 · Charles Bretana
Вот пример: (ПРИМЕЧАНИЕ: это упрощенный, а не потокобезопасный пример)
public class PersonCollection : IEnumerable
{
private ArrayList alPers = new ArrayList();
public IEnumerator GetEnumerator() { return new myTypeEnumerator(this); }
public class myTypeEnumerator : IEnumerator
{
int nIndex;
PersonCollection pers;
private int count { get { return pers.alPers.Count; } }
public myTypeEnumerator(PersonCollection myTypes)
{ pers = myTypes; nIndex = -1; }
public bool MoveNext() { return nIndex <= count && ++nIndex < count; }
// MovePrev() not strictly required
public bool MovePrev() { return (nIndex > -1 && --nIndex > 0 ); }
public object Current { get { return (pers[nIndex]); } }
public void Reset() { nIndex = -1; }
}
}
РЕДАКТИРОВАТЬ: чтобы исправить проблему, поднятую @Joren ниже, связанную с тем, что Move Previous принимает значение индекса ниже -1. При вызове фреймворком как части реализации foreach, MoveNext() не нуждается в этом исправлении, потому что в этом случае, если MoveNext() возвращает false, экземпляр перечислителя завершится. Однако, если клиент вручную вызовет MoveNext(), перечислитель не завершится, поэтому он также нуждается в исправлении.
Также ПРИМЕЧАНИЕ: то, как вы реализуете детали внутри этой вещи, зависит от вас и будет зависеть от того, как она внутренне управляет состоянием. Например, если внутреннее "ведро", содержащее ссылки, было связанным списком, а не ArrayList, то MoveNext() можно было бы реализовать, просто изменив поле Current, чтобы оно указывало на свойство nextItem старого текущего...
Ответ 3 · рейтинг 1 · Tordek
То, что вы сказали для 1, на самом деле будет для 2.
Для 1 это было бы больше похоже на
public IEnumerator GetEnumerator() {
return names.GetEnumerator();
}
Для пункта 3 это означало бы заставить GetEnumerator проделать некоторую реальную работу: реализовать вручную Enumerator для определенной вами коллекции.
Ответ 4 · erikkallen
"Если класс "обертывает" другую коллекцию, возвращая перечислитель обернутой коллекции" означает:
class StringCollections{
//A class is wrapping another collection
string[] names=new string {"Jon Skeet","Hamish Smith",
"Marc Gravell","Jrista","Joren"};
public IEnumerator GetEnumerator() { return names.GetEnumerator(); }
}