C# IEnumerable,IEnumerator 重置函数未被调用

Posted

技术标签:

【中文标题】C# IEnumerable,IEnumerator 重置函数未被调用【英文标题】:C# IEnumerable, IEnumerator Reset Function Not Get Called 【发布时间】:2012-07-13 14:09:41 【问题描述】:

我基本上是想让我的班级能够使用foreach 进行迭代。我读了这个教程。 MSDN。这似乎很简单。但是,当我想第二次迭代时遇到问题。我调试了它;结果它没有调用Reset()函数。

A 类

class A : IEnumerable, IEnumerator

    int[] data =  0, 1, 2, 3, 4 ;

    int position = -1;

    public object Current
    
        get
        
            return data[position];
        
    

    public bool MoveNext()
    
        position++;
        return (position < data.Length);
    

    public void Reset()
    
        position = -1;
    

    public IEnumerator GetEnumerator()
    
        return (IEnumerator)this;
    

当我运行以下主要功能时;它从不调用Reset() 函数。所以,在一个循环之后,我再也无法迭代我的类了。

主要

static void Main(string[] args)

    A a = new A();

    foreach (var item in a)
    
        Console.WriteLine(item);
    

    Console.WriteLine("--- First foreach finished. ---");

    foreach (var item in a)
    
        Console.WriteLine(item);
    

输出:

0
1
2
3
4
--- First foreach finished. ---
Press any key to continue . . .

有什么想法吗?

【问题讨论】:

看看这个answer 顺便说一句,除非你是为了好玩或一个非常具体的原因,否则你永远不必以这种方式实现迭代器。查看“迭代器块”又名“收益回报”。 我知道它很旧,但我刚刚在谷歌上找到了这篇文章。我遇到了同样的问题,我实现了 MoveNext 方法,当它返回 false 时调用 Reset。 【参考方案1】:

里德和卡洛斯都是正确的。这是您可以做到这一点的一种方法,因为int[] 实现了IEnumerableIEnumerable&lt;int&gt;

class A : IEnumerable
 
    int[] data =  0, 1, 2, 3, 4 ; 

    public IEnumerator GetEnumerator() 
     
        return data.GetEnumerator(); 
     
 

或者,为了更加强类型,你可以使用IEnumerable的泛型形式:

class A : IEnumerable<int>

    int[] data =  0, 1, 2, 3, 4 ;

    public IEnumerator<int> GetEnumerator()
    
        return ((IEnumerable<int>)data).GetEnumerator();
    

    IEnumerator IEnumerable.GetEnumerator()
    
        return data.GetEnumerator();
    

【讨论】:

我的真实情况比较复杂。这只是一个解释我的问题的例子。在真正的问题中,我没有int 数组。 @Zagy 酷。那么,里德和卡洛斯都有很好的答案。 :)【参考方案2】:

每次调用foreach,它都会要求一个新的 IEnumerator。返回你的类实例是个坏主意——你应该创建一个单独的类来实现IEnumerator,然后返回它。

这通常通过使用嵌套(私有)类并返回它的实例来完成。您可以将 A 类实例传递给私有类(授予它对 data 的访问权限),并将 position 字段放在该类中。它将允许同时创建多个枚举器,并且可以在后续的 foreach 调用中正常工作。

例如,要修改您的代码,您可以执行以下操作:

using System;
using System.Collections;

class A : IEnumerable

    int[] data =  0, 1, 2, 3, 4 ;

    public IEnumerator GetEnumerator()
    
        return new AEnumerator(this);
    

    private class AEnumerator : IEnumerator
    
        public AEnumerator(A inst)
        
            this.instance = inst;
        

        private A instance;
        private int position = -1;

        public object Current
        
            get
            
                return instance.data[position];
            
        

        public bool MoveNext()
        
            position++;
            return (position < instance.data.Length);
        

        public void Reset()
        
            position = -1;
        

    

请注意,您也可以直接返回数组的枚举数(尽管我假设您正在尝试学习如何制作干净的枚举数):

class A : IEnumerable

    int[] data =  0, 1, 2, 3, 4 ;

    public IEnumerator GetEnumerator()
    
        return data.GetEnumerator();
    

最后,您可以使用iterators 以更简单的方式实现这一点:

class A : IEnumerable

    int[] data =  0, 1, 2, 3, 4 ;

    public IEnumerator GetEnumerator()
    
        for (int i=0;i<data.Length;++i)
           yield return data[i];
    

话虽如此,我强烈建议在 IEnumerable 之外实现 IEnumerable&lt;int&gt;。泛型在使用方面做得更好。

【讨论】:

是的,你是对的。我试图学习如何制作干净的枚举器。我的真实代码更复杂。 @zagy 看看迭代器——它们通常是实现枚举器最好、最实用的方法。话虽这么说,除了IEnumerable 之外,我也总是实现IEnumerable&lt;T&gt; ...【参考方案3】:

Reset() 基本上是一个错误。如果可能的话,已经有一个已知的方法来获取一个干净的枚举器:GetEnumerator()。

在规范中要求迭代器块实现(对于迭代器)为此方法抛出异常,因此在一般情况下,正式知道不能指望它起作用,而且很简单:没有人调用它。坦率地说,他们也应该在 API 上将其标记为 [已过时]!

此外,许多序列是不可重复的。考虑一下位于 NetworkStream 或随机数生成器上的迭代器。因为迭代器(在一般情况下)不需要是可重复的,所以您应该尽可能地针对它们最多迭代一次。如果不可能的话,也许可以通过 ToList() 进行缓冲。

“foreach”在任何时候都不涉及 Reset()。只需 GetEnumerator()、MoveNext()、Current 和 Dispose()。

【讨论】:

【参考方案4】:

枚举不调用Reset。您需要创建一个枚举器的新实例,这可能意味着为枚举器创建一个单独的类(即,IEnumerable 和 IEnumerator 不使用相同的类型),如下面的代码所示:

public class ***_11475328

    class A : IEnumerable
    
        int[] data =  0, 1, 2, 3, 4 ;

        public IEnumerator GetEnumerator()
        
            return new AEnumerator(this);
        

        class AEnumerator : IEnumerator
        
            private A parent;
            private int position = -1;

            public AEnumerator(A parent)
            
                this.parent = parent;
            

            public object Current
            
                get  return parent.data[position]; 
            

            public bool MoveNext()
            
                position++;
                return (position < parent.data.Length);
            

            public void Reset()
            
                position = -1;
            
        
    
    public static void Test()
    
        A a = new A();

        foreach (var item in a)
        
            Console.WriteLine(item);
        

        Console.WriteLine("--- First foreach finished. ---");

        foreach (var item in a)
        
            Console.WriteLine(item);
        
    

【讨论】:

以上是关于C# IEnumerable,IEnumerator 重置函数未被调用的主要内容,如果未能解决你的问题,请参考以下文章

C#:你会给一个 IEnumerable 类起啥名字?

c# IEnumerable<T>

C# IEnumerator IEnumerable接口

C#内建接口:IEnumerable

在 C# 中的列表上使用 IEnumerable [重复]

c# IEnumerable集合