【问题标题】:How is the GetEnumerator method get invoked in a foreach loop in C#?如何在 C# 的 foreach 循环中调用 GetEnumerator 方法?
【发布时间】:2017-03-12 17:39:04
【问题描述】:

我正在经历迭代器模式,当我偶然发现示例代码中的逻辑时有点困惑

class MonthCollection : IEnumerable
        {

          public string[] months = {
"January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"};

            public IEnumerator GetEnumerator()
            {
                // Generates values from the collection
                foreach (string element in months)
                    yield return element;
            }
        }

        static void Main()
        {

            MonthCollection collection = new MonthCollection();
            // Consumes values generated from collection's GetEnumerator method
            foreach (string n in collection)
                Console.Write(n + " ");
            Console.WriteLine("\n");
        }

在这段代码中,将数组访问到字符串的正常方法是在 foreach 语句中像这样调用月份变量

MonthCollection 集合 = new MonthCollection();
foreach(collection.months 中的字符串 n){ }

但我不明白 foreach 是如何访问 甚至不直接调用months变量的集合

MonthCollection 集合 = new MonthCollection();
foreach(集合中的字符串 n){}

更新 1

我无法理解的是如何访问变量月份而不在上面的类中这样调用它

static void Main()
        {

            MonthCollection collection = new MonthCollection();
            // Consumes values generated from collection's GetEnumerator method
            foreach (string n in collection.months)
                Console.Write(n + " ");
            Console.WriteLine("\n");
        }

而不是这样

 MonthCollection collection = new MonthCollection();
               method
                foreach (string n in collection)
                    Console.Write(n + " ");
                Console.WriteLine("\n");

【问题讨论】:

    标签: c# collections foreach iterator


    【解决方案1】:

    foreach 循环不需要集合,它只需要一个带有GetEnumerator() 方法的对象,该方法返回IEnumerator,在您的情况下,它带有IEnumerable 实现。

    C# 编译器产生剩余的“魔法”——一个隐藏的临时变量来引用枚举器对象、启动枚举的代码、获取每个项目的代码以及查看枚举何时结束的代码。

    在您的情况下,C# 依赖于您的实现来使用另一个“魔术”——yield return 构造来获取 months 数组的内容。此构造允许您实现GetEnumerator(),而无需为其编写单独的类。该构造被转换为具有状态机的类,让您从循环中间“返回”对象,然后在迭代器请求下一个项目时重新开始。

    yield return 返回的对象不必来自集合。您甚至可以将集合中的对象与其他对象混合,例如

    public IEnumerator GetEnumerator() {
        // Generates values from the collection
        foreach (string element in months) {
            yield return element + " will start soon!";
            yield return element;
            yield return element + " has ended.";
        }
    }
    

    foreach 循环访问变量 months 间接,通过您实现 GetEnumerator() 的代码。

    【讨论】:

    • 您的意思是说,如果在类中找到 GetEnumerator() 方法,c# 编译器会自动调用该方法,而无需用户显式调用该变量。例如,如果我在 A 类中有一个公共财产,并且如果我想在其中调用公共财产,那么我会像这样调用 new A().Name;但是不需要显式调用 GetEnumerator() 方法,都是由 C# 编译器完成的,对吧?
    • @LijinJohn C#编译器要求in右侧的对象是IEnumerable的实现,否则是编译时错误。 foreach 通过GetEnumerator() 获取枚举器,因为它知道类型检查已经成功。这是在代码生成中使用库定义类的 C#“魔法”的一部分。
    • 我的问题是,在这个例子中 MonthCollection collection = new MonthCollection(); foreach(集合中的字符串 n) Console.Write(n + " "); Console.WriteLine("\n");我没有调用 GetEnumerator() 方法,我只是调用了对象,那么 foreach 循环如何访问集合?
    • 根据我的理解应该是这样的 foreach(var d in new MonthCollection().GetEnumerator()) 或者这样的 foreach(var d in new MonthCollection().months)
    • @LijinJohn 你的理解不正确。 month 是一个集合。它允许foreach中使用,你列举的东西不是必须是一个集合。它可以是任何实现IEnumerable 的类,比如你的MonthCollection。按照我在编辑中显示的方式对您的代码进行修改,以查看 yield return 如何将未存储的对象与已存储的对象混合在一起。
    猜你喜欢
    • 2019-02-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-09
    相关资源
    最近更新 更多