#c# #.net #ienumerable #ienumerator
#c# #.net #ienumerable #ienumerator
Вопрос:
У меня есть код, подобный:
class T : IEnumerable, IEnumerator
{
private int position = -1;
public T() { }
public IEnumerator GetEnumerator() { return this; }
public object Current { get { return position; } }
public bool MoveNext()
{
position ;
return (position < 5);
}
public void Reset() { position = -1; }
}
//Using in code:
T t = new T();
foreach (int i in t)
//to do something
В приведенном выше коде все работает нормально, но когда я использую next:
foreach (int i in t)
if (i == 2)
foreach (int p in t)
//print p
else
//print i
Он выводит (в скобках второй цикл): 0 1 (3 4) 2 вместо 0 1 (0 1 2 3 4) 2 3 4
Я протестировал его в списке и коллекции, и они все делают правильно.
Как я могу достичь того, что мне нужно?
Комментарии:
1. Просто в качестве примечания: еще один способ показать, что текущая реализация неверна:
Console.WriteLine(t.Count()); // 5 Console.WriteLine(t.Count()); // 0
т. Е. В настоящее время она будет повторяться только один раз (обратите внимание также, чтоReset()
никогда не следует использовать — он формально устарел и в большинстве случаев намеренно генерирует исключения)
Ответ №1:
Вы не можете, потому что вы сделали свой код с одним перечислителем, что само по себе является ошибкой IMO. Для меня лучшей версией было бы:
class T : IEnumerable<int> {
public IEnumerator<int> GetEnumerator() {
int i = 0;
while(i < 5) {
yield return i;
i ;
}
}
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
}
Компилятор создаст правильные устройства для достижения этого с помощью отдельных счетчиков.
Если вы не пишете для .NET 1.1, то, если вы обнаружите, что вручную пишете enumarator, есть очень хороший шанс, что вы делаете это сложным путем и получаете его неправильно в качестве бонуса.
Если вы действительно должны сделать это сложным способом:
class T : IEnumerable<int>
{
public T() { }
public IEnumerator<int> GetEnumerator() { return new TEnumerator(); }
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
private class TEnumerator : IEnumerator<int>
{
private int position = -1;
public int Current { get { return position; } }
object IEnumerator.Current { get { return Current; } }
void IDisposable.Dispose() {}
public bool MoveNext()
{
position ;
return (position < 5);
}
public void Reset() { position = -1; }
}
}
Значение здесь заключается в том, что разные экземпляры TEnumerator
позволяют одному и тому же T
экземпляру повторяться отдельно.
Комментарии:
1. @Praetor12 да, но вам понадобится второй тип для представления перечислителя. Я смоделирую это … 2 секунды.
2. Кажется, это немного сложнее, чем я ожидал, но это то, что мне нужно. Большое спасибо!
3. @Praetor12 из болезненного любопытства, зачем вам нужно писать перечислитель сложным способом?
4. Мне нравится писать код, который работает так, как я понимаю. Я не знаю, как именно работает yeild: ( Вот и все.
5. @Praetor12 вам может понравиться серия постов в блоге Эрика Липперта о блоках итераторов и, в частности, статьи в блогах Рэймонда Чена и Джона Скита , ссылки на которые приведены в части 2
Ответ №2:
foreach (int i in t)
if (i == 2)
foreach (int p in t)
//print p
else
//print i
Сначала всегда используйте фигурные скобки, пока вы делаете отступы, совпадающие с тем, что произойдет, другой if
там будет путать вещи.
foreach (int i in t) {
if (i == 2) {
foreach (int p in t) {
//print p
}
} else {
//print i
}
}
Но у вас проблема: у вас есть только один счетчик на экземпляр T
, и вы используете один и тот же экземпляр. Поэтому вы делаете через один раз. Если вы хотите разрешить одновременные перечисления, объект enumerator должен быть отдельным GetEnumerator
, каждый раз возвращая новый экземпляр.