【问题标题】:Having trouble understanding IEnumerator无法理解 IEnumerator
【发布时间】:2011-11-02 15:34:16
【问题描述】:

我目前正在学习 C#,目的是有效地使用 Razor 和 Umbraco,我有一点 PHP 背景和 JavaScript 方面的知识。

我遇到了IEnumerator 界面,我很难理解它。我在下面设置了一个代码 sn-p 来演示使用 foreach 循环遍历数组:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace cardEnum
{
    class suitsEnumerator
    {
        private static string[] suits = { "Hearts", "Spades", "Diamonds", "Clubs" };
        public void showAll()
        {
            foreach(string x in suits){
                System.Console.WriteLine(x);
            }
        }
    }
    class run
    {
        static void Main()
        {
            Console.WriteLine("The suits are:");
            var suits = new suitsEnumerator();
            suits.showAll();
            Console.ReadKey();
        }
    }
}

有人可以解释为什么我什至需要使用IEnumerator,否则我可以很容易地循环遍历数组。我知道这是一个如此简单的问题,我就是想不出使用 IEnumerable 的理由。

任何帮助将不胜感激,谢谢!

【问题讨论】:

  • 上面是一个很好的例子,说明了为什么 IEnumerator 很好。 (你知道你在上面使用 IEnumerator 对吧?)
  • 这意味着您可以使用相同的语法 (foreach) 来迭代任何实现 IEnumerable(或具有 GetEnumerator 方法)的类。循环数组很容易,但是如果您有一个不允许索引访问的复杂类怎么办。
  • @JamieF:实际上,使用 Microsoft C# 编译器编译该代码不会使用IEnumerator - 尽管这是一个实现细节。

标签: c# .net ienumerable


【解决方案1】:

这就是在您的示例中实现枚举器模式的方式。我希望这能澄清一些事情,特别是IEnumerableIEnumerator 之间的关系:

namespace cardEnum {
    // simple manual enumerator
    class SuitsEnumerator : IEnumerator {
        private int pos = -1;
        private static string[] suits = { "Hearts", "Spades", "Diamonds", "Clubs" };

        public object Current {
            get { return suits[pos]; }
        }

        public bool MoveNext() {
            pos++;
            return (pos < suits.Length);
        }

        public void Reset() {
            pos = -1;
        }
    }

    class Suits : IEnumerable {
        public IEnumerator GetEnumerator() {
            return new SuitsEnumerator();
        }
    }

    class run {
        static void Main() {
            Console.WriteLine("The suits are:");
            var suits = new Suits();
            foreach (var suit in suits) {
                Console.WriteLine(suit);
            }
            Console.ReadKey();
        }
    }
}

当然,这是一个人为的例子来帮助理解。由于string[] 已经实现了IEnumerable,因此没有理由重新发明轮子。


由于 C# 支持 yields 关键字,最近创建 Enumerator 变得更容易了:

namespace cardEnum {
    // no explicit SuitsEnumerator required
    class Suits : IEnumerable {
        public IEnumerator GetEnumerator() {
            yield return "Hearts";
            yield return "Spades";
            yield return "Diamonds";
            yield return "Clubs";
        }
    }

    class run {
        static void Main() {
            Console.WriteLine("The suits are:");
            var suits = new Suits();
            foreach (var suit in suits) {
                Console.WriteLine(suit);
            }
            Console.ReadKey();
        }
    }
}

【讨论】:

  • 这实际上解决了我的概念问题!只有一个问题,在“class SuitsEnumerator : IEnumerator”中是否可以访问方法“Current、MoveNext、Reset”,因为该类已被 IEnumerator 扩展?
  • @blacktea:我不确定您所说的“可访问”是什么意思。它们对其他类可见,因为它们被声明为public。它们还构成了由IEnumerator (= "如果我知道一个类实现了 IEnumerator,我可以安全地假设它具有称为 Current、MoveNext 和 Reset 的方法,它们的行为符合 IEnumerator 文档中规定的规则。 ")。
【解决方案2】:

首先,如果您要创建自己的枚举器,您应该让类实现 IEnumerator 接口(或其通用等价物)。

public class FooEnumerator : IEnumerator
{
    #region IEnumerator Members

    public object Current
    {
        get { throw new NotImplementedException(); }
    }

    public bool MoveNext()
    {
        throw new NotImplementedException();
    }

    public void Reset()
    {
        throw new NotImplementedException();
    }

    #endregion
}

通常使用枚举器在集合中移动,一次一个项目。这是一个支持移动项目的简单界面,但不代表项目本身。它只有当前项目和移动到下一个项目的概念。

另一方面,IEnumerable 旨在表示项目的集合。 IEnumerable 根据其定义返回一个枚举器,其目的是枚举 IEnumerable 集合中的每个项目。它们不是相互排斥的类,而是依赖类。

您通常不会像示例中那样创建枚举器来遍历您的值。您将创建一个集合并使用枚举器对其进行导航(或在更高级别迭代使用幕后枚举器的集合)。

【讨论】:

    【解决方案3】:

    您可能(并且经常)必须执行一些更复杂的操作,而不仅仅是打印出对象的值。

    您的类可能必须一一返回(产生)对象以进行一些外部处理(通过其他一些类、方法等)。

    然后公开IEnumerator 比返回整个集合要好,因为枚举意味着您一次只传递一个对象!不是一次包含(可能)一百万个元素的数组。

    【讨论】:

      【解决方案4】:

      C# 编译器对此进行了转换:

      foreach(string x in suits)
      

      使用IEnumerator(或IEnumerator&lt;T&gt;)。

      IEnumerableIEnumerable&lt;T&gt; 接口定义了一个GetEnumerator 方法,该方法返回一个IEnumerator(或分别为IEnumerator&lt;T&gt;),这就是您正在使用foreach 进行的迭代。

      例外是对数组进行迭代,编译器会以不同的方式处理(感谢@Jon Skeet)。

      【讨论】:

      • 不一定……Microsoft 实现实际上对数组的处理方式不同,以避免创建迭代器。
      【解决方案5】:

      这很好,因为您的代码依赖于接口 (IEnumerable)

      今天代码中的西装类型是数组

      private static string[] suits = { "Hearts", "Spades", "Diamonds", "Clubs" };
      

      如果您决定使用 for 循环,您的代码或多或少如下所示

      for (int index = 0; index < suits.Length; suits++)
      {
          // code here
      }
      

      明年您可能有理由将其类型从Array 更改为List&lt;T&gt;。如果你使用 for 你需要更新代码块,因为 Array 有 Length 而 List 有 Count 属性。

      【讨论】:

        【解决方案6】:

        如果您查看 System.Array,您会发现 Array 实现了 IEnumerable。 并且对于每个不需要 IEnumerable 对象。它只需要一个名为 GetEnumerator 的方法。

        【讨论】:

        • 但是GetEnumerator方法是通过IEnumerable访问的,不是吗?
        • 是的,但是 foreach 不检查对象是否实现了这个接口。它通过调用 GetEnumerator 方法来替换代码。因此,如果您创建的对象仅包含使用 GetEnumerator 的方法并且不实现 IEnumerable,则可以在对象上使用 foreach。
        • 不,它发生在编译时。它转换代码并检查代码是否有效。
        【解决方案7】:

        假设您要创建一套自定义套装...学习如何实现 IEnumerable&lt;T&gt; 将使您对它为何如此有用有所了解。没有它,我们所知道的 LINQ 就无法存在。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2016-02-10
          • 2014-01-03
          • 2020-04-17
          • 2017-10-05
          • 2020-08-26
          • 2011-04-12
          相关资源
          最近更新 更多