不使用foreach循环使用IEnumerable

40

我一定是错过了什么简单的东西。

请看以下代码:

public IEnumerable<int> getInt(){
  for(int i = 0; i < 10; i++){
   yield return i;
  }
}
我可以这样调用它:
foreach (int j in obj.getInt()){
  //do something with j
}

如何在不使用 foreach 循环的情况下使用 getInt 方法:

IEnumerable<int> iter = obj.getInt();
// do something with iter ??

谢谢。

编辑

对于那些想知道我为什么想要这个的人,我正在迭代两件事:

IEnumerator<int> iter = obj.getInt().GetEnumerator();
foreach(object x in xs){
  if (x.someCondition) continue;
  iter.MoveNext();
  int n = iter.current();
  x.someProp = n;
  etc...
}

你到底想做什么?IEnumerable<T>的整个意义在于允许迭代,而foreach循环是最有效的方法。 - Will Vousden
1
@Zakalwe,我想同时迭代两个可枚举对象。foreach正在遍历第一个对象,如果它满足某个条件,我需要下一个int(这只是一个示例,不是真正的int)。 - Mark
这是一个非常好的问题。在Python中,它非常简单,而我正在研究如何在C#中实现它,GetEnumerator就是答案。 - Sith2021
7个回答

71

你可以使用GetEnumerator方法获得对Enumerator的引用,然后使用MoveNext()方法移动,在使用Current属性来访问元素:

var enumerator = getInt().GetEnumerator();
while(enumerator.MoveNext())
{
    int n = enumerator.Current;
    Console.WriteLine(n);
}

2
它允许您对前几个元素进行特殊处理,或在处理完指定数量的元素后停止处理等。基本上,它为您提供了更多的灵活性。 - Anon.
3
可以使用 foreach 完成同样的操作。 - Fitzchak Yitzchaki
2
详细说明如何处理枚举的某些元素,然后离开并完全做其他事情,最后再回来使用 foreach 完成剩余的处理。 - Anon.
9
像这样:int i = 0; foreach(var x in y) { Process(x); i++; if (i == 5) GoOffAndDoSomethingDifferent(); }。你看到发生了什么吗?你处理了前几个,然后去做完全不同的事情,最后再回来完成剩下的。"去做完全不同的事情,等它完成后回到之前的地方"是我们通常认为的“调用方法”的一个描述。 - Eric Lippert
2
@FitzchakYitzchaki,我现在经常使用的一件事情是能够同时枚举多个可枚举对象。我认为你不能使用 foreach 来实现它,而要使用 for 循环,你需要将这些可枚举对象至少转换成 ICollection 而不是 IEnumerable - Dave Cousineau
显示剩余8条评论

34

我的建议是:不要过多涉及枚举器。将您的问题描述为对序列的一系列操作。编写代码以表达这些操作。让序列运算符来管理枚举器。

所以让我们看看我是否理解正确。您有两个序列,例如 { 2, 3, 5, 7, 12 } 和 { "frog", "toad" }。您想执行的逻辑操作是,在第一个序列中遍历。每当找到可被三整除的数字时,获取第二个序列中的下一个项目。使用生成的(数字、两栖动物)对进行某些操作。

很容易实现。首先,筛选出第一个序列:

var filtered = firstSequence.Where(x=>x%3 == 0);

接下来,将过滤后的序列与第二个序列进行压缩:

var zipped = filtered.Zip(
             secondSequence, 
             (y, z)=> new {Number = x, Amphibian = y});

现在,您可以遍历已压缩的序列,并对成对的元素进行任何操作:

foreach(var pair in zipped)
    Console.WriteLine("{0} : {1}", pair.Number, pair.Amphibian);

轻而易举,不需要使用枚举。


5
@Mark: (1)人们常常忘记处理它们。(2)枚举器强调代码的运行方式而不是用途 -- 通过枚举器阅读代码就像看钻头电机的描述一样,而你想要描述的是钻孔的结果。它们确实有时是必需的,但我更愿意在更高的抽象层次上表达我的操作,而不是在一个需要明确跟踪多个序列中的多个“光标”的层次上。 - Eric Lippert
4
@Eric,你的方法有一个问题,如果这是针对大量内存数据进行操作,手动同时迭代两个列表可能会带来性能上的提升... 如果数据量很小或者性能不是问题,那么我会选择优雅的代码(即你的代码)而不是琐碎的代码。 - Jason D
3
@Jason:Zip所做的只是分配两个枚举器并遍历它们。开销应该很小。请记住,序列运算符是惰性的;它们不会一次性实现其结果,而是根据需要从其源序列中获取数据。 - Eric Lippert
@Eric,如果你有两个已排序的IEnumerable对象,并且想要进行无重复项的O(n)复杂度并集操作,那么你不能使用2个foreach循环。实际上,它们将被嵌套,导致O(n^2)的复杂度。这条评论只是为了澄清为什么这个问题是完全合理的。 - Manitra Andriamitondra
#Eric,如果每个单独的项目都是“IDisposable”,怎么办?您想将接收到的项目放入“using”中,因此“HasNext”-“GetNext()” API变得非常有吸引力。(最好不需要包含类本身成为“IDisposable”。) - Timo
显示剩余4条评论

6
这个怎么样?
IEnumerator<int> iter = obj.getInt();
using(iter) {
    while(iter.MoveNext()) {
        DoSomethingWith(iter.Current)
    }
}

@itowlson,那正是我所缺少的! - Mark

2

使用for循环:

for (var enumerator = getInt().GetEnumerator(); enumerator.MoveNext(); )
{
    Console.WriteLine(enumerator.Current);
}

1
你必须在结束时处理枚举器。 - CharlesB

1

需要注意的是,foreach循环的职责是在实例实现IDisposable时处理枚举器的释放。换句话说,foreach应该被替换为类似以下的内容:

var enumerator = enumerable.GetEnumerator();

try
{
    while (enumerator.MoveNext())
    {
        var item = enumerator.Current;
        // do stuff
    }
}
finally
{
    var disposable = enumerator as IDisposable;
    if (disposable != null)
    {
        disposable.Dispose();
    }
}

0
尽管已经有正确的答案了,但请注意在第一次调用MoveNext()之前,IEnumerator.Current是未定义的。
如果您正在遍历一个二次元数组,那么您会需要类似下面这样的东西:
IEnumerable<Foo> Foo() { ... }

int AssignValues(List<TakesFoo> data) {
  var count = 0;
  var foo = Foo().GetEnumerator();

  // Step into the first element of the array;
  // doing this does not discard a value from the IEnumerator
  if (foo.MoveNext()) { 
    foreach (var x in data) {
      x.SetFoo(foo.Current);
      count += 1;
      if (!foo.MoveNext()) { 
        break;
      }
   }

   // Return count of assigned values
   return count;
}

0

示例,

public static void SampleIteratorNext()
{
    var beauty = BeautifulLadies().GetEnumerator();

    Console.WriteLine($"Friday with {Next(beauty)} ...");
    Console.WriteLine($"Saturday with {Next(beauty)} ...");
    Console.WriteLine($"Tusday with {Next(beauty)} ...");
}

public static IEnumerable<string> BeautifulLadies()
{
    yield return "Scarlett";
    yield return "Alexandra";
    yield return "Alisson";
}

// emulate next() in python
public static T Next<T>(IEnumerator<T> iterator)
{
    iterator.MoveNext();
    return iterator.Current;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接