【问题标题】:Point of need of custom Enumerators自定义枚举器的需求点
【发布时间】:2009-10-20 21:01:49
【问题描述】:

在阅读帖子时,没有示例给出了一些分数:

要实现 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 点在这里代表什么?,因为没有例子,我没有得到那个。 这是否意味着,我可以构建自定义枚举器..(对吗?)。我的问题是预构建枚举器/枚举器何时足够(作为初学者,我不应该盲目地确认这一点) 我为什么要照顾自定义的迭代?很好的例子将澄清我的疑问。

感谢您阅读这篇冗长的故事和友好的回应。

【问题讨论】:

    标签: c# collections enumeration


    【解决方案1】:

    (第 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 但实际上从 当前财产)。

    【讨论】:

    • 感谢 Elisha 分享信息
    【解决方案2】:

    这是一个例子:(注意:这是简化的,不是线程安全的例子)

    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 提出的与移动上一个索引值低于 -1 相关的问题。当框架作为 foreach 实现的一部分调用时,MoveNext() 不需要此修复,因为在这种情况下,如果 MoveNext() 返回 false,枚举器实例将终止。但是,如果客户端手动调用 MoveNext(),枚举器不会终止,所以它也需要修复。

    还请注意:如何实现这个东西内部的细节取决于你,并且将取决于它如何在内部管理状态。例如,如果保存引用的内部“桶”是一个链表,而不是一个 ArrayList,那么 MoveNext() 可以通过将 Current 字段更改为指向旧当前的 nextItem 属性来实现......

    【讨论】:

    • 感谢您抽出宝贵时间。 :)
    • 你的 MoveNextMovePrev 很奇怪。如果我创建该枚举器,执行两次MovePrev() 和一次MoveNext()nIndex 变为 -1,但调用返回 true。如果我没记错的话,这两种方法都应该更改nIndex,然后返回0 &lt;= nIndex &amp;&amp; nIndex &lt; pers.alPers.Count
    • @Joren,错误,已更正 MovePrev 必须与零比较,而不是计数。但是您不需要针对范围的“其他”端进行顶级比较索引...它永远不会小于-1或大于计数...一旦它返回false,枚举就会结束...
    • @Joren,再想一想,我看到你明确地调用 Move 方法......是的,在这种情况下,你是对的。但是这种模式被设计为在客户端代码使用 foreach 时由框架使用......在这种情况下,框架生成的 IL 代码正在调用 MoveNext().. 而你所描述的不会发生......但是如果 MovePrev()在那里,它应该被编码来处理你的场景......
    • 我认为枚举器代码应该足够健壮来处理这种情况。 MovePrev 甚至根本不会被 foreach 调用。如果包含它,它应该可以正常工作。
    【解决方案3】:

    你所说的 1 实际上是 2。

    对于 1,它会更像

    public IEnumerator GetEnumerator() {
        return names.GetEnumerator();
    }
    

    对于第 3 点,这意味着让 GetEnumerator 做一些真正的工作:为您定义的集合手动实现一个 Enumerator。

    【讨论】:

      【解决方案4】:

      “如果该类正在“包装”另一个集合,则通过返回被包装的集合的枚举数”意味着:

      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(); }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-04-02
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多