1什么时IEnumerable?

什么是IEnumerable?

IEnumerable及IEnumerable的泛型版本IEnumerable是一个接口,它只含有一个方法GetEnumerator。Enumerable这个静态类型含有很多扩展方法,其扩展的目标是IEnumerable。
实现了这个接口的类可以使用Foreach关键字进行迭代(迭代的意思是对于一个集合,可以逐一取出元素并遍历之)。实现这个接口必须实现方法GetEnumerator。

---------IEnumerable其实可以从字面看是一个接口,对于这个泛型版本来说,T代表的时类。所有实现了这个接口的类都具有了foreach关键字进行遍历的功能,同时IEnumerable接口还具备自己的方法。

如何实现一个继承IEnumerable的类型?

实现一个继承IEnumerable的类型等同于实现方法GetEnumerator。想知道如何实现方法GetEnumerator,不妨思考下实现了GetEnumerator之后的类型在Foreach之下的行为:

可以获得第一个或当前成员
可以移动到下一个成员
可以在集合没有下一个成员时退出循环。

假设我们有一个很简单的Person类(例子来自MSDN):

    public class Person
     {
          public Person(string fName, string lName)
          {
               FirstName = fName;
               LastName = lName;
          }
          public string FirstName;
          public string LastName;
     }

然后我们想构造一个没有实现IEnumerable的类型,其储存多个Person,然后再对这个类型实现IEnumerable。这个类型实际上的作用就相当于Person[]或List,但我们不能使用它们,因为它们已经实现了IEnumerable,故我们构造一个People类,模拟很多人(People是Person的复数形式)。这个类型允许我们传入一组Person的数组。所以它应当有一个Person[]类型的成员,和一个构造函数,其可以接受一个Person[],然后将Person[]类型的成员填充进去作为初始化。

 //People类就是Person类的集合
    //但我们不能用List<Person>或者Person[],因为他们都实现了IEnumerable
    //我们要自己实现一个IEnumerable
    //所以请将People类想象成List<Person>或者类似物
    public class People : IEnumerable
    {
        private readonly Person[] _people;
        public People(Person[] pArray)
        {
            //构造一个Person的集合
            _people = new Person[pArray.Length];

            for (var i = 0; i < pArray.Length; i++)
            {
                _people[i] = pArray[i];
            }
        }

        //实现IEnumerable需要实现GetEnumerator方法
        public IEnumerator GetEnumerator()
        {
            throw new NotImplementedException();
        }
    }

我们的主函数应当是:

public static void Main(string[] args)
        {
            //新的Person数组
            Person[] peopleArray = 
            {
                new Person("John", "Smith"),
                new Person("Jim", "Johnson"),
                new Person("Sue", "Rabon"),
            };

            //People类实现了IEnumerable
            var peopleList = new People(peopleArray);

            //枚举时先访问MoveNext方法
            //如果返回真,则获得当前对象,返回假,就退出此次枚举
            foreach (Person p in peopleList)
                Console.WriteLine(p.FirstName + " " + p.LastName);
        }

但现在我们的程序不能运行,因为我们还没实现GetEnumerator方法。

实现方法GetEnumerator

GetEnumerator方法需要一个IEnumerator类型的返回值,这个类型是一个接口,所以我们不能这样写:

return new IEnumerator();

因为我们不能实例化一个接口。我们必须再写一个类PeopleEnumerator,它继承这个接口,实现这个接口所有的成员:Current属性,两个方法MoveNext和Reset。于是我们的代码又变成了这样:

//实现IEnumerable需要实现GetEnumerator方法
        public IEnumerator GetEnumerator()
        {
            return new PeopleEnumerator();
        }

在类型中:

 public class PeopleEnumerator : IEnumerator
    {
        public bool MoveNext()
        {
            throw new NotImplementedException();
        }

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

        public object Current { get; }
    }

现在问题转移为实现两个方法,它们的功能看上去一目了然:一个负责将集合中Current向后移动一位,一个则将Current初始化为0。我们可以查看IEnumerator元数据,其解释十分清楚:

  • Enumerator代表一个类似箭头的东西,它指向这个集合当前迭代指向的成员
  • IEnumerator接口类型对非泛型集合实现迭代
  • Current表示集合当前的元素,我们需要用它仅有的get方法取得当前元素

MoveNext方法根据Enumerator是否可以继续向后移动返回真或假
Reset方法将Enumerator移到集合的开头

通过上面的文字,我们可以理解GetEnumerator方法,就是获得当前Enumerator指向的成员。我们引入一个整型变量position来记录当前的位置,并且先试着写下:

public class PeopleEnumerator : IEnumerator
    {
        public Person[] _peoples;
        public object Current { get; }

        //当前位置
        public int position;

        //构造函数接受外部一个集合并初始化自己内部的属性_peoples
        public PeopleEnumerator(Person[] peoples)
        {
            _peoples = peoples;           
        }
        
        //如果没到集合的尾部就移动position,返回一个bool
        public bool MoveNext()
        {
            if (position < _peoples.Length)
            {
                position++;
                return true;
            }
            return false;
        }

        public void Reset()
        {
            position = 0;
        }
    }

这看上去好像没问题,但一执行之后却发现:

  • 当执行到MoveNext方法时,position会先增加1,这导致第一个元素(在位置0)会被遗漏,故position的初始值应当为-1而不是0
  • 当前位置变量position显然应该是私有的
  • 需要编写Current属性的get方法取出当前位置(position)上的集合成员

通过不断的调试,最后完整的实现应当是:

public class PeopleEnumerator : IEnumerator
{
        public Person[] People;

        //每次运行到MoveNext或Reset时,利用get方法自动更新当前位置指向的对象
        object IEnumerator.Current
        {
            get
            {
                try
                {
                    //当前位置的对象
                    return People[_position];
                }
                catch (IndexOutOfRangeException)
                {
                    throw new InvalidOperationException();
                }
            }
        }

        //当前位置
        private int _position = -1;

        public PeopleEnumerator(Person[] people)
        {
            People = people;           
        }

        //当程序运行到foreach循环中的in时,就调用这个方法获得下一个person对象
        public bool MoveNext()
        {
            _position++;
            //返回一个布尔值,如果为真,则说明枚举没有结束。
            //如果为假,说明已经到集合的结尾,就结束此次枚举
            return (_position < People.Length);
        }

        public void Reset() => _position = -1;
    }

为什么当程序运行到in时,会呼叫方法MoveNext呢?我们并没有直接调用这个方法啊?当你试图查询IL时,就会得到答案。实际上下面两段代码的作用是相同的:

foreach (T item in collection)
{
  ...
}
IEnumerator<T> enumerator = collection.GetEnumerator();
while (enumerator.MoveNext())
{
  T item = enumerator.Current;
  ...
}

注意:第二段代码中,没有呼叫Reset方法,也不需要呼叫。当你呼叫时,你会得到一个异常,这是因为编译器没有实现该方法。

IEnumerable的缺点

  • IEnumerable功能有限,不能插入和删除。
  • 访问IEnumerable只能通过迭代,不能使用索引器。迭代显然是非线程安全的,每次IEnumerable都会生成新的IEnumerator,从而形成多个互相不影响的迭代过程。
  • 在迭代时,只能前进不能后退。新的迭代不会记得之前迭代后值的任何变化。
  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C#中的IEnumerable是一个接口,属于System.Collections命名空间。它定义了一个用于遍历集合的枚举器(enumerator),是集合类的基础接口。IEnumerable接口包含一个方法GetEnumerator(),用于返回一个实现IEnumerator接口的枚举器。枚举器提供了对集合中元素的逐个访问,以便实现对集合的迭代。通过实现IEnumerable接口,可以使你的集合类可以通过foreach循环来进行遍历。在使用foreach循环遍历集合,会自动调用集合的GetEnumerator()方法来获取枚举器,然后使用枚举器逐个访问集合中的元素。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [IEnumerable(C#)](https://blog.csdn.net/qq_64410237/article/details/131695354)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [C#中IEumerable的简单了解](https://blog.csdn.net/qq_39806817/article/details/115024666)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值