none
关于枚举数和迭代器的问题? RRS feed

  • 问题

  • 产生多个枚举数为啥还要非泛型接口IEnumerable.GetEnumerator()部分呢(见下列代码黑色加粗部分)?

    using System;
    using System.Collections;
    using System.Collections.Generic;

    namespace Ch20Ex20_12
    {
    class MyClass : IEnumerable<string>
    {
    bool ColorFlag = true;
    public MyClass(bool flag)
    {
    ColorFlag = flag;
    }
    IEnumerator<string> BlackAndWhite
    {
    get
    {
    yield return "black";
    yield return "gray";
    yield return "white";
    }
    }
    IEnumerator<string> Colors
    {
    get
    {
    string[] theColors = { "blue", "red", "yellow" };
    for (int i = 0; i < theColors.Length; i++)
    yield return theColors[i];
    }
    }
    public IEnumerator<string> GetEnumerator()
    {
    return ColorFlag
    ? Colors
    : BlackAndWhite;
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
    return ColorFlag
    ? Colors
    : BlackAndWhite;
    }
    }
    class Program
    {
    static void Main(string[] args)
    {
    MyClass mc1 = new MyClass(true);
    foreach (string s in mc1)
    Console.Write("{0} ", s);
    Console.WriteLine();

    MyClass mc2 = new MyClass(false);
    foreach (string s in mc2)
    Console.Write("{0} ", s);
    Console.WriteLine();

    Console.ReadKey();
    }
    }
    }


    万物皆变,规则永恒。
    2012年1月5日 3:14

答案

  • 你还是没有解释清楚其中的原理,为什么yield return关键字所在的迭代器块只需要实现一个泛型接口GetEnumerator()方法即可? yield return关键字所在的迭代器块中继承 IEnumerable<T>接口的非泛型GetEnumerator()方法是如何被省略掉的,微软迭代器这个黑盒子的原理?

    因为yield return的作用相当与实现了一个内部的私有类,该类专门用于实现IEnumerator接口,比如看我的例子:

    class Number
        {
            private int[] numbers = { 12345 };

            private class InnerNumber:IEnumerator<int>
            {
                private int[] numbers = null;

                public InnerNumber(int[]number)
                {
                    numbers = number;
                }
                int pointer = -1;

                public int Current
                {
                    get { return numbers[pointer]; }
                }

                public void Dispose()
                {
                    numbers = null;
                }

                object IEnumerator.Current
                {
                    get { throw new NotImplementedException(); }
                }

                public bool MoveNext()
                {
                    return (++pointer) < numbers.Length;
                }

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

            public IEnumerator<int> GetEnumerator()
            {
                return new InnerNumber(numbers);
            }
        }
    对于Number而言,因为返回了IEnumerable<int>,无需明写实现IEnumerable<T>接口出来;但是
    private class……这一段的本质就是yield return的默认内置实现。你想想,如果去掉private class
    这个一段,不就和你一样了。
       QQ我:讨论(Talk)
    下载MSDN桌面工具(Vista,Win7)
    我的博客园
    慈善点击,点击此处


    2012年1月6日 3:18
    版主
  • 你好wavetekgroup:)

    其实你的问题归纳起来是两个:

    1)yield return的本质是什么?(是一个实现了IEnumerator接口的内部私有类,或者类似类)。

    2)为什么实现了IEnumerable<T>必须要实现IEnumerable?(是语法所致,必须这样做)。

    至于你后来的MyClass尚未实现接口IEnumerable<T>,自然而然是没有必要实现IEnumerable。但是C#中对于这个频繁使用的接口方法规定——只要类中有GetEnumerator以及返回对应的IEnumerable,那么系统默认其“隐式”实现了IEnumerable接口,完全可以用foreach来遍历。所以归根到底是:语法和语义两个方面的问题。


       QQ我:讨论(Talk)
    下载MSDN桌面工具(Vista,Win7)
    我的博客园
    慈善点击,点击此处

    2012年1月6日 5:17
    版主

全部回复

  • 因為 Colors,  BlackAndWhite 都是 IEnumerator<string>呀!

    如果 ColorFlag為true就回傳Colors, 不然就回傳BlackAndWhite!



    亂馬客blog: http://www.dotblogs.com.tw/rainmaker/
    2012年1月5日 5:06
  • 楼主,准确说来理由有2:

    1)IEnumerable<T>自身是继承自接口IEnumerable的,所以必须实现IEnumerable中的GetEnumerator方法。

    参考定义:

    public interface IEnumerable<out T> : IEnumerable

    http://msdn.microsoft.com/zh-cn/library/9eekhta0.aspx

    2)之所以微软要这样做,我估计是为了使得和当初的IEnumerable相融合,意味着你可以直接通过object对迭代器中的元素进行访问,比如:

    foreach(object obj xxx)
    {
       ………………


       QQ我:讨论(Talk)
    下载MSDN桌面工具(Vista,Win7)
    我的博客园
    慈善点击,点击此处
    2012年1月5日 8:45
    版主
  • 那按你的说法,用泛型接口方法

    public IEnumerator<string> GetEnumerator()
    {
    return ColorFlag
    ? Colors
    : BlackAndWhite;
    }

    就足够了,没必要再写一个非泛型的接口方法啊(但是不写上面非泛型的接口方法编译器报错),所以这也是我的问题你还是没解释清楚。


    万物皆变,规则永恒。

    2012年1月5日 11:39
  • 按你的解释应该只有在手动编写枚举数类型泛型接口形式枚举数(继承IEnumerator<T>接口)后,并在编写枚举类型(IEnumerable<T>接口)(实现其中的泛型IEnumerable<T>接口GetEnumerator()方法)时,才需要同时编写泛型方法和非泛型方法GetEnumerator(),这就带来下列问题:

    问题一:如果接口IEnumerable<T>继承自IEnumerator(),并且只有一个方法声明,只需写一个实现代码即可(即基接口的GetEnumerator()方法声明,如果在继承接口IEnumerable中声明的方法,这种继承关系将毫无意义),那么干嘛要有两个实现代码呢?除非它们虽是继承关系声明方法名称相同(都是GetEnumerator())但是却有各自方法声明需要实现不同的方法目标,所以需要同时写出2个方法,因此他们的继承关系不是写两个实现的必然原因。

    问题二:请你注意下面的属性部分

    IEnumerator<string> BlackAndWhite
    {
    get
    {
    yield return "black";
    yield return "gray";
    yield return "white";
    }
    }
    它里面用的是yield return关键字,我们知道编译器应该已经自动(不是手动)生成了迭代器,那么也就说这个迭代器块已经自动创建了枚举数(包含Current属性、MoveNext()方法、IEnumerator.Current、IEnumerator.Reset()、IDisposable.Dispose())和枚举类型(枚举项),我们只需要实现一个泛型接口GetEnumerator()方法即可(下面黑体部分),因此如下示例是可以运行的,为什么上面示例却还要一个非泛型接口GetEnumerator()方法(否则编译器报错)呢??这说明编译器自动生成的迭代器创建的枚举数类型不是泛型接口形式的简单翻版。

    using System;
    using System.Collections.Generic;

    namespace Ch20Ex20_8_2
    {
        class MyClass
        {
            public IEnumerator<string> GetEnumerator()
            {
                return BlackAndWhite();
            }
            public IEnumerator<string> BlackAndWhite()
            {
                yield return "black";
                yield return "gray";
                yield return "white";
            }
        }
        class Program
        {
            static void Main(string[] args)
            {
                MyClass mc = new MyClass();
                foreach (string shade in mc)
                    Console.WriteLine(shade);
                Console.ReadKey();
            }
        }
    }

    唯一的解释是 class MyClass 继承 IEnumerable<string>接口的缘故(我们删除class MyClass继承的IEnumerable<string>接口和非泛型接口IEnumerable.GetEnumerator()部分程序就能正常运行),虽然yield return关键字所在的迭代器块只需要实现一个泛型接口GetEnumerator()方法即可,但是class MyClass 继承的 IEnumerable<string>接口却需要手动实现一个非泛型接口GetEnumerator()方法,但是其中的原理又是什么呢??


    万物皆变,规则永恒。







    2012年1月5日 13:21
  • 那按你的说法,用泛型接口方法

    public IEnumerator<string> GetEnumerator()
    {
    return ColorFlag
    ? Colors
    : BlackAndWhite;
    }

    就足够了,没必要再写一个非泛型的接口方法啊(但是不写上面非泛型的接口方法编译器报错),所以这也是我的问题你还是没解释清楚。

    恐怕楼主误解我的意思了:

    一个接口“继承”一个接口(比如说A接口继承B接口),并不代表着实现了B的方法,只是把B方法“原样拷贝”到A接口自身中,这意味着一旦某个类实现A接口,必须实现两个方法(A接口自身和继承自B接口的方法)。自然不写非泛型方法会产生错误。这是语法层面的问题

    至于说微软为何要这样做,我也觉得有些纳闷——因为在VS2005(Beta2)版本中IEnumerable<T>没有继承IEnumerable,但是正式版直到现在突然继承了这个接口,我想大概是微软的“惯例”——方便直接使用foreach(object obj……)的形式进行访问吧。


       QQ我:讨论(Talk)
    下载MSDN桌面工具(Vista,Win7)
    我的博客园
    慈善点击,点击此处
    2012年1月6日 1:11
    版主
  • 你的说法和我下面问题一的阐述并不矛盾(请看最下面我的回帖),我感觉IEnumerable<T>之所以继承自IEnumerable并且声明方法名称相同都是GetEnumerator()只是为了在foreach迭代中兼容泛型和非泛型的枚举类型数据项罢了。但是回到初始的问题,在yield return关键字生成的自动迭代器中为什么不用在枚举类型中实现非泛型枚举方法(请见最下面我的回帖问题二)。


    万物皆变,规则永恒。
    2012年1月6日 2:29
  • 问题二:请你注意下面的属性部分

    IEnumerator<string> BlackAndWhite
    {
    get
    {
    yield return "black";
    yield return "gray";
    yield return "white";
    }
    }
    它里面用的是yield return关键字,我们知道编译器应该已经自动(不是手动)生成了迭代器,那么也就说这个迭代器块已经自动创建了枚举数(包含Current属性、MoveNext()方法、IEnumerator.Current、IEnumerator.Reset()、IDisposable.Dispose())和枚举类型(枚举项),我们只需要实现一个泛型接口GetEnumerator()方法即可(下面黑体部分),因此如下示例是可以运行的,为什么上面示例却还要一个非泛型接口GetEnumerator()方法(否则编译器报错)呢??这说明编译器自动生成的迭代器创建的枚举数类型不是泛型接口形式的简单翻版。

    using System;
    using System.Collections.Generic;

    namespace Ch20Ex20_8_2
    {
        class MyClass
        {
            public IEnumerator<string> GetEnumerator()
            {
                return BlackAndWhite();
            }
            public IEnumerator<string> BlackAndWhite()
            {
                yield return "black";
                yield return "gray";
                yield return "white";
            }
        }
        class Program
        {
            static void Main(string[] args)
            {
                MyClass mc = new MyClass();
                foreach (string shade in mc)
                    Console.WriteLine(shade);
                Console.ReadKey();
            }
        }
    }

    你说的可以运行的示例是我现在引用的吗?这个当然可以运行,你没有实现IEnumerable<string>啊,一旦实现了,必须严格按照语法进行检测了。因为“迭代器”使用太频繁,因此微软“破例允许”在没有显示实现接口的同时,只要函数体中返回IEnumerable的函数GetEnumerator,默认是实现了对应的某个(带泛型、或者不带泛型)的接口了。

    请比较你的两个示例代码:

    1)class MyClass

    2)class MyClass : IEnumerable<string>


       QQ我:讨论(Talk)
    下载MSDN桌面工具(Vista,Win7)
    我的博客园
    慈善点击,点击此处

    2012年1月6日 2:46
    版主
  • 你还是没有解释清楚其中的原理,回到我问题的本质为什么yield return关键字所在的迭代器块只需要实现一个泛型接口GetEnumerator()方法即可? yield return关键字所在的迭代器块中继承IEnumerable<T>接口的非泛型GetEnumerator()方法是如何被省略掉的,它是如何被默认的?被破例的?微软迭代器这个黑盒子的原理?
    万物皆变,规则永恒。

    2012年1月6日 3:10
  • 你还是没有解释清楚其中的原理,为什么yield return关键字所在的迭代器块只需要实现一个泛型接口GetEnumerator()方法即可? yield return关键字所在的迭代器块中继承 IEnumerable<T>接口的非泛型GetEnumerator()方法是如何被省略掉的,微软迭代器这个黑盒子的原理?

    因为yield return的作用相当与实现了一个内部的私有类,该类专门用于实现IEnumerator接口,比如看我的例子:

    class Number
        {
            private int[] numbers = { 12345 };

            private class InnerNumber:IEnumerator<int>
            {
                private int[] numbers = null;

                public InnerNumber(int[]number)
                {
                    numbers = number;
                }
                int pointer = -1;

                public int Current
                {
                    get { return numbers[pointer]; }
                }

                public void Dispose()
                {
                    numbers = null;
                }

                object IEnumerator.Current
                {
                    get { throw new NotImplementedException(); }
                }

                public bool MoveNext()
                {
                    return (++pointer) < numbers.Length;
                }

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

            public IEnumerator<int> GetEnumerator()
            {
                return new InnerNumber(numbers);
            }
        }
    对于Number而言,因为返回了IEnumerable<int>,无需明写实现IEnumerable<T>接口出来;但是
    private class……这一段的本质就是yield return的默认内置实现。你想想,如果去掉private class
    这个一段,不就和你一样了。
       QQ我:讨论(Talk)
    下载MSDN桌面工具(Vista,Win7)
    我的博客园
    慈善点击,点击此处


    2012年1月6日 3:18
    版主
  • 你好wavetekgroup:)

    其实你的问题归纳起来是两个:

    1)yield return的本质是什么?(是一个实现了IEnumerator接口的内部私有类,或者类似类)。

    2)为什么实现了IEnumerable<T>必须要实现IEnumerable?(是语法所致,必须这样做)。

    至于你后来的MyClass尚未实现接口IEnumerable<T>,自然而然是没有必要实现IEnumerable。但是C#中对于这个频繁使用的接口方法规定——只要类中有GetEnumerator以及返回对应的IEnumerable,那么系统默认其“隐式”实现了IEnumerable接口,完全可以用foreach来遍历。所以归根到底是:语法和语义两个方面的问题。


       QQ我:讨论(Talk)
    下载MSDN桌面工具(Vista,Win7)
    我的博客园
    慈善点击,点击此处

    2012年1月6日 5:17
    版主
  • 谢谢,明白了,其实还是接口方法的实现问题。继承IEnumerable<T>接口的类必须实现基接口IEnumerable和继承接口IEnumerable的GetEnumerator方法。
    万物皆变,规则永恒。

    2012年1月6日 7:32
  • 谢谢,明白了,其实还是接口方法的实现问题。继承IEnumerable<T>接口的类必须实现基接口IEnumerable和继承接口IEnumerable的方法。
    不错!其实你一开始把问题混在一起了。
       QQ我:讨论(Talk)
    下载MSDN桌面工具(Vista,Win7)
    我的博客园
    慈善点击,点击此处
    2012年1月6日 7:33
    版主