• C# 常用接口学习 IEnumerable<T>


    作者:乌龙哈里
    时间:2015-10-24
    平台:Window7 64bit,Visual Studio Community 2015

    本文参考:

    本文章节:

    • 接口IEnumerable实现
    • 接口IEnumerable<T>实现

    正文:

    本文是作者摸索学习.Net的过程,逐步进行,比较繁琐,是作者本人用来帮助记忆的博文。

    我们先去看看公开的.Net4.0的源程序中IEnumerable<T>、IEnumerable、IEnumerator<T>和IEnumerator这四个接口是如何声明的:

        public interface IEnumerable<out T> : IEnumerable
        {
            new IEnumerator<T> GetEnumerator();
        }

        public interface IEnumerator<out T> : IDisposable, IEnumerator
        {
            new T Current {
                get;
            }
        }

        public interface IEnumerable
        {
            IEnumerator GetEnumerator();
        }

        public interface IEnumerator
        {
            bool MoveNext();
            Object Current {
                get;
            }
        
            void Reset();
        }

    一、接口IEnumerable实现

    1、建一个学生数据结构和一个学生集合类:

        //student数据结构
        class Student
        {
            public int id;
            public string name;
        }

        //student 集合
        class StudentCollection
        {
            public List<Student> students = new List<Student>();
            public void Add(Student student)
            {
                students.Add(student);
            }
        }

    公开一个Add()方法以添加数据,我们的集合类建立完毕。下来添加数据:

            static void Main(string[] args)
            {
                StudentCollection sc = new StudentCollection();
                sc.Add(new Student { id=0,name="Tony"});
                sc.Add(new Student { id=1,name="Micheal"});
                sc.Add(new Student { id =2, name = "Amy" });
                foreach(var s in sc) {...}
            }
        }

    当我们想用foreach()遍历的时候,编译器会告诉我们StudentCollection不包含GetEnumerator,不能用foreach遍历。虽然StudentCollection里面有能用遍历的List<T>,但我们不想在属性上迭代,我们想在类上迭代,不能 foreach(var s in sc.students){...}

    现在只有把我们的StudentCollection类改造成能foreach的。

    2、继承接口IEnumerable:

    当我们在类后面加上:IEnumerable后,Visual Studio IDE会冒出来一个小黄灯泡,点进去有提示自动填充接口的约定,我们选第一项实现接口(Visaul Studio是全世界最贴心的IDE!),IDE会帮我们把SudentCollection改造成以下的:

        class StudentCollection:IEnumerable
        {
            public List<Student> students = new List<Student>();
            public void Add(Student student)
            {
                students.Add(student);
            }

            public IEnumerator GetEnumerator()
            {
                throw new NotImplementedException();
            }
        }

    加了一个返回迭代器的方法GetEnumrator。下来按照IEnumetator接口的约定来实现我们的迭代器StudentCollectionEnumerator,用IDE自动补全代码如下:

        //迭代器
        class StudentCollectionEnumerator : IEnumerator
        {
            public object Current
            {
                get
                {
                    throw new NotImplementedException();
                }
            }

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

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

    我的理解是:Current返回当前元素,MoveNext移动到下一个,Reset回到第一个元素。但根据MSDN上面的说法,Reset 方法提供的 COM 互操作性。它不一定需要实现;相反,实施者只需抛出NotSupportedException。但是,如果您选择执行此操作,则应确保没有调用方依赖于Reset功能。

    迭代器工作的原理是:先调用MoveNext()方法,然后读取Current得到元素,直到MoveNext返回false。

    我们需要3个字段分别放置 元素的位置、元素、元素集。改变后的程序如下:

        //迭代器
        class StudentCollectionEnumerator : IEnumerator
        {
            private int _index;
            private List<Student> _collection;
            private Student value;
            public StudentCollectionEnumerator(List<Student> colletion)
            {
                _collection = colletion;
                _index = -1;
            }
             object IEnumerator.Current
            {
                get { return value; }
            }
            public bool MoveNext()
            {
                _index++;
                if (_index >= _collection.Count) { return false; }
                else { value = _collection[_index]; }
                return true;
            }
            public void Reset()
            {
                _index = -1;
            }

        }

    首先,迭代器初始化,引入元素集 _collection,并把索引 _index设置成-1。设置成-1而不是0是因为迭代器首先调用MoveNext,在MoveNext里面我们先把索引+1指向下一个元素,如果索引_index的值初始为0,则第一个元素是元素集[1],第二个元素了。
    其次,我们要把object Current改成 IEnumerator.Current,这个是实现迭代器的关键。返回元素。(好像有装箱的行为)
    第三,在MoveNext方法内累加索引,并从元素集中读取元素。然后让索引值超出元素集返回个false值。
    最后,在Reset方法内让索引值为-1,不过好像直接抛出错误也成。

    迭代器写好了,我们在StudentColletion类里面调用:

    class StudentCollection : IEnumerable
        {
            public List students;
            public StudentCollection()
            {
                students = new List();
            }
            public void Add(Student student)
            {
                students.Add(student);
            }
            public IEnumerator GetEnumerator()
            {
                return new StudentCollectionEnumerator(students);
            }
        }

    测试运行一下,大功告成!我们实现了可枚举的自己的类。

    通过观察,发现迭代器主要就是返回一个元素对象,而StudentColletion里面的students元素集是List的,本身就能枚举,我们能不能利用这个不用专门写迭代器来实现枚举呢?
    答案是肯定的,我们这样写:

        class StudentCollection:IEnumerable
        {
            public List<Student> students = new List<Student>();
            public void Add(Student student)
            {
                students.Add(student);
            }

            public IEnumerator GetEnumerator()
            {
                foreach(var s in students)
                {
                    yield return s;
                }
            }
        }

    这样就能实现枚举了,真简单,充分利用了.Net给出的各种可枚举集合,不用再去写GetEnumerator这种累活了。

    二、接口IEnumerable<T>实现

    如果我们想写一个通用的可foreach的类,用到泛型:

        class MyCollection<T>
        {
            public List<T> mycollection = new List<T>();
            public void Add(T value)
            {
                mycollection.Add(value);
            }
        }

    其实这个MyCollection类只不过是在List<T>外面封装了一层,要实现IEnumable<T>,继承该泛型接口,Visual Studio 的IDE自动帮我们补全后,如下:

        class MyCollection:IEnumerable
        {
            public List mycollection = new List();
            public void Add(T value)
            {
                mycollection.Add(value);
            }
            public IEnumerator GetEnumerator()
            {
                throw new NotImplementedException();
            }
            IEnumerator IEnumerable.GetEnumerator()
            {
                throw new NotImplementedException();
            }
        }

    我们直接用上面第二个简单的写法,改成:

        class MyCollection:IEnumerable
        {
            public List mycollection = new List();
            public void Add(T value)
            {
                mycollection.Add(value);
            }
            public IEnumerator GetEnumerator()
            {
                foreach(var s in mycollection)
                {
                    yield return s;
                }
            }
            IEnumerator IEnumerable.GetEnumerator()
            {
                foreach (var s in mycollection)
                {
                    yield return s;
                }
            }
        }

    测试运行:

         static void Main(string[] args)
            {
                MyCollection mc = new MyCollection();
                mc.Add(0);
                mc.Add(1);
                mc.Add(2);
                foreach(var s in mc) { Console.WriteLine(s); }
                Console.ReadKey();
        }

    大功告成!
    虽然第二种写法比较投机,充分利用了.NET Framework给的各种泛型集合可枚举的特征。不过我们也自己实现了一个GetEnumerator(),了解了枚举器的工作原理。本章学习目的达成。

  • 相关阅读:
    解读基本数据类型和内置方法(1)(要掌握)
    简单循环流程的介绍
    基本数据类型的使用和运算符的介绍
    开辟编程语言的介绍和变量
    HTML5 元素超出部分滚动, 并隐藏滚动条
    数据库多行数据合并一行(sqlserver、Oracle、Mysql)
    Js apply方法与call方法详解 附ES6新写法
    Java实现牛顿迭代法求解平方根、立方根
    为什么在JavaScript中0.1+0.2不等于0.3?
    html5手机web页面底部菜单
  • 原文地址:https://www.cnblogs.com/leemano/p/4908505.html
Copyright © 2020-2023  润新知