C#基础知识系列九(对IEnumerable和IEnumerator接口的糊涂认识)

前言

   IEnumerable、IEnumerator到现在为止对这两个接口还是不太理解,不理解但是自己总是想着试着要搞明白,毕竟自己用的少,所以在此先记录一下。以备自己日后可以来翻查,同时也希望园子里的大牛们,来帮我看看理解的怎么样。

查看并使用两个接口

  接下来我们先来看看两个接口的定义。

  先来看一下IEnumerable接口,其实看过这个接口之后,发现它其实是非常的简单,只包含一个方法GetEnumerator(),它返回一个可用于循环访问集合的IEnumerator对象,如下面截图所示:

这里的IEnumerator对象,其实就是另外一个接口,这个接口对象有什么呢?它是一个真正的集合访问器,没有它,就不能使用foreach语句遍历集合或数组,因为只有IEnumerator对象才能访问集合中的项,假如连集合中的项都访问不了,那么进行集合的循环遍历是不可能的事情了。那么让我们看看IEnumerator接口又定义了什么东西。

从上面我们知道IEnumerator接口定义了一个Current属性,MoveNext和Reset两个方法,这是多么的简约。既然IEnumerator对象是一个访问器。那至少应该有一个Current属性,来获取当前集合中的项吧。MoveNext方法只是将游标的内部位置向前移动(就是移到一下个元素而已),要想进行循环遍历,不向前移动一下怎么行呢?

通过注释也可以明确的发现他们的用处。

下面我们来看一个简单的例子:

        static void Main(string[] args)
        {
            int[] iArr = { 1, 3, 4, 6, 7, 9 };
            foreach (int i in iArr)
            {
                Console.WriteLine(i.ToString());
            }
            Console.ReadLine();
        }

F5来运行代码

结果有了,说明简单的数组是可以支持foreach循环的。

下面我们来自己来做一个小例子,先来定义实体类

    /// <summary>
    /// 个人的实体类
    /// </summary>
    public class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }

    /// <summary>
    /// 一群人的实体类
    /// </summary>
    public class People
    {            
        Person[] personList = new Person[4];
        public People()
        {
            personList[0] = new Person() { Name = "aehyok", Age = 25 };
            personList[1] = new Person() { Name = "Kris", Age = 22 };
            personList[2] = new Person() { Name = "Leo", Age = 21 };
            personList[3] = new Person() { Name = "Niki", Age = 23 };
        }
    }

如上面代码所示,一个Person类是个人的实体类,然后People类是一群人的实体类,按照和上面数组类似的格式,下面我们进行调用

        static void Main(string[] args)
        {
       ///直接对一群人实例对象进行foreach
            People people = new People();
            foreach (Person p in people)
            {
                Console.WriteLine("Name:{0}\tAge{1}",p.Name,p.Age);
            }
            Console.ReadLine();
        }

还没来得及编译,错误就来了

所以我们根据上面的讲解我们就让People类实现IEnumerable接口吧。现在先来修改People实体类。

    /// <summary>
    /// 个人的实体类
    /// </summary>
    public class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }

    /// <summary>
    /// 一群人的实体类
    /// </summary>
    public class People:IEnumerable
    {            
        Person[] personList = new Person[4];
        public People()
        {
            personList[0] = new Person() { Name = "aehyok", Age = 25 };
            personList[1] = new Person() { Name = "Kris", Age = 22 };
            personList[2] = new Person() { Name = "Leo", Age = 21 };
            personList[3] = new Person() { Name = "Niki", Age = 23 };
        }

        public IEnumerator GetEnumerator()
        {
            return this.personList.GetEnumerator();
        }
    }

继承实现接口,完成该方法之后,就可以在调用时用foreach了。

注意:其实这里完全不用继承该接口。直接对GetEnumerator()方法进行实现,然后返回IEnumerator即可。

这样还可以有另外一种调用方式

        static void Main(string[] args)
        {
            People people = new People();
            foreach (Person p in people)
            {
                Console.WriteLine("Name:{0}\tAge{1}",p.Name,p.Age);
            }
            Console.WriteLine("");
            ///直接获取迭代器
            IEnumerator i = people.GetEnumerator();
            while (i.MoveNext())
            {
                Person person = (Person)i.Current;
                Console.WriteLine("Name:{0}\tAge{1}", person.Name, person.Age);
            }
            Console.ReadLine();
        }

调用结果

自定义两个接口并进行实现

  上面我们是通过继承微软类库中的接口来实现的实体集合的foreach遍历。下面我们来演示一下完全通过自己创建接口来实现自己定义的实例集合的foreach遍历。首先我们来实现一个简单的迭代器。

第一步:定义一个接口IMyEnumerator,之后所有迭代器都要进行实现

    /// <summary>
    /// 要求所有的迭代器全部实现该接口
    /// </summary>
    interface IMyEnumerator
    {
        bool MoveNext();
        object Current{get;};
    }

第二步:再定义一个接口IMyEnumerable,所有集合要实现该接口

    /// <summary>
    /// 要求所有的集合实现该接口
    /// 这样一来,客户端就可以针对该接口编码
    /// 而无须关注具体的实现
    /// </summary>
    interface IMyEnumerable
    {
        IMyEnumerator GetEnumerator();
        int Count{get;};
    }

第三步:一个简单的集合类MyList,实现IMyEnumerable。

    class MyList:IMyEnumerable
    {
        int[] items = {0,1,2,3,4,5,6,7,8,9};
        IMyEnumerator myEnumerator;

        public int this[int i]
        {
            get { return items[i]; }
            set { this.items[i] = value; }
        }

        public int Count
        {
            get { return items.Length; }
        }

        public IMyEnumerator GetEnumerator()
        {
            if (myEnumerator == null)
            {
                myEnumerator = new MyEnumerator(this);
            }
            return myEnumerator;
        }

    }

第四步:其实集合中也需要进行使用实现了第一步的迭代器,所以在此就是要实现这个迭代器

public class MyEnumerator:IMyEnumerator
    {
        int index = 0;
        MyList myList;
        public MyEnumerator(MyList myList)
        {
            this.myList = myList;
        }

        public bool MoveNext()
        {
            if (index + 1 > =myList.Count)
            {
                index = 1;
                return false;
            }
            else
            {
                index++;
                return true;
            }
        }

        public object Current
        {
            get { return myList[index]; }
        }
    }

第五步:简单调用进行调试

        static void Main(string[] args)
        {
            ///使用接口IMyEnumerable代替MyList
            IMyEnumerable list = new MyList();
            ///得到迭代器,在循环中针对迭代器进行编码,而不是集合MyList
            IMyEnumerator enumerator = list.GetEnumerator();
            for (int i = 0; i < list.Count; i++)
            {
                object current = enumerator.Current;
                Console.WriteLine(current.ToString());
                enumerator.MoveNext();
                
            }
            Console.WriteLine("");
            ///重新创建一个新的对象
            IMyEnumerable list1 = new MyList();
            IMyEnumerator enumerator1 = list1.GetEnumerator();
            while (enumerator1.MoveNext())    //因为此处闲下移了一位,所以从1开始
            {
                object current = enumerator1.Current;
                Console.WriteLine(current.ToString());
            }
            Console.ReadLine();
        }

调用结果

   其实我定义的两个接口使用的是IMyEnumerable和IMyEnumerator,这里你直接可以去掉My那么就是微软类库里面的接口了,我这里只是自定义罢了,然后我自己定义接口的方法属性,没有严格按照微软的接口进行定义,但是差不多,只需要进行简单的修正就可以进行调用。这里有一个版本的。

View Code

其实上面例子中的调用我们就可以使用foreach来调用了,那么现在我们来用foreach来调用看看。

        static void Main(string[] args)
        {
            MyList list=new MyList();
            foreach (object obj in list)
            {
                Console.WriteLine(obj.ToString());
            }
            Console.ReadLine();
        }

总结

通过上面我实现的几个简单的例子可以发现,一个类型支持foreach遍历的条件可以是:

  1、第一个方案是:这个类实现IEnumerable接口

  2、第二个方案是:这个类有一个public的GetEnumerator的实例方法(不用继承IEnumerable实现接口),并且返回类型中有public 的bool MoveNext()实例方法和public的Current实例属性。

实现了IEnmerable<T>接口的集合,是强类型的。它为子对象的迭代提供类型更加安全的方式。

自己实现了下,感觉还是懂了一些,虽然还没有彻底的搞明白,但最起码大概知道怎么回事了。有空再来看看yield关键字的用法。  

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Ryan Miao

Java8学习(3)- Lambda 表达式

猪脚:以下内容参考《Java 8 in Action》 本次学习内容: Lambda 基本模式 环绕执行模式 函数式接口,类型推断 方法引用 Lambda 复...

34090
来自专栏GIS讲堂

面向对象下计算器的编码实现

首先是新建一个运算类(Calcultor.cs),它包含两个属性strNumA和strNumB,用以储存计算时的两个数,此外,还有一个虚方法Getresult(...

30130
来自专栏大内老A

从yield关键字看IEnumerable和Collection的区别

C#的yield关键字由来以久,如果我没有记错的话,应该是在C# 2.0中被引入的。相信大家此关键字的用法已经了然于胸,很多人也了解yield背后的“延迟赋值”...

22770
来自专栏机器学习入门

POJ 刷题系列:2739. Sum of Consecutive Prime Numbers

POJ 刷题系列:2739. Sum of Consecutive Prime Numbers 传送门:POJ 2739. Sum of Consecutive...

21770
来自专栏blackheart的专栏

[C#3] 4-匿名类型

1.DEMO 使用匿名类型: static void Main() { var someType = new { Name = "乱舞春秋", Age ...

22070
来自专栏IT进修之路

原 分分钟看懂java用引用传递与值传递在

19550
来自专栏cs

c#知识点1.0数据类型

以前就说,要开始写c#的博客,最近把linux大约写完了,现在开始c#了,java的博客简书一大堆,我就避免撞车吧,其实我是菜鸟(嘻嘻,写不出更好的了) 数据...

38670
来自专栏我是业余自学C/C++的

字典 原

字典(dictionary)是由一些形如(key,value)的数对所组成的集合,其中key是关键字,value是与关键字key对应的值(另一种说法是,valu...

13510
来自专栏蓝天

rapidjson常见使用示例

Document d; v2.CopyFrom(d, a); // 把整个document复制至v2,d不变 rapidjson为了最大...

1.3K20
来自专栏一枝花算不算浪漫

[读书笔记]C#学习笔记五: C#3.0自动属性,匿名属性及扩展方法

357100

扫码关注云+社区

领取腾讯云代金券