C#深度学习の枚举类型(IEnumerator,IEnumerable)以及Yield
一、关于枚举的含义
.Net提供了可枚举类型的接口IEnumerable和枚举器(迭代器)接口IEnumerator,程序集System.Collections
另:
IQueryable 继承自IEnumerable(System.Core)
枚举,又叫列举,顾名思义,(程序)一个一个列举出来。列举出来以方便查询应用(linq)。常见的,我们遍历一个集合(Collection)的时候,foreach不可少,在C#中有些是可以被foreach(linq查询,foreach本质是linq查询执行方法),这些数据结构
有一个特征:可枚举,像数组,List, ArryList,等,已经隐式实现了接口IEnumerable,可以返回一个IEnumerator(iteror枚举器)。
二、两个接口的定义
1、如果一个类实现了IEnumerable接口,则称为此类为可迭代的,要实现IEnumerable接口就要实现IEnumerable中定义的GetEnumerator()的方法生成一个迭代器(IEnumerator)。
IEnumerable是所有集合类型接口的基接口,如ICollection、IList;IEnumerable只能进行读操作,其具有延迟执行的特性,ICollection、IList的实现类支持增删改查,IList的实现类支持的操作更多、更加灵活如插入元素、移除元素等。
2、如果我们把实现IEnumerable[可迭代的]看做一个类可以进行迭代的标记,那么IEnumerator[迭代器]就相当于迭代时的工作引擎,迭代中的主要工作都需要依靠迭代器进行。
foreach的本质是调用迭代器MoveNext的过程。
public interface IEnumerable { //IEnumerable只有一个方法,返回可循环访问集合的枚举数。 IEnumerator GetEnumerator() ; } public interface IEnumerator { // 方法 //移到集合的下一个元素。如果成功则返回为 true;如果超过集合结尾,则返回false。 bool MoveNext(); // 将集合设置为初始位置,该位置位于集合中第一个元素之前 void Reset(); // 属性:获取集合中的当前元素 object Current { get; } }
三、二者的区别和联系
1、一个集合可查询(可用foreach,where,any等),必须以某种方式返回IEnumerator object)也就是必须实现IEnumerable接口 2、IEnumerator object具体实现了iterator(通过MoveNext(),Reset(),Current)。 3、从这两个接口的用词选择上,也可以看出其不同:IEnumerable是一个声明式的接口,声明实现该接口的class是“可枚举(enumerable)”的 4、IEnumerable和IEnumerator通过IEnumerable的GetEnumerator()方法建立了连接。
四、使用yield语法糖快速生成迭代器
我们可以看到,整个创建自定义可迭代类的时候较为繁琐的过程就是生成工作引擎迭代器,需要手工实现迭代器接口中的MoveNext、Current属性等等,那么为了方便我们快速的生成枚举器,C#提供了一个yield语法糖来帮助我们简化这一过程,请注意,这仅仅是一个语法糖。
- 包含yield的方法返回值只能为IEnumerable和IEnumerator。
- 返回值为IEnumerable的方法,系统会自动生成一个hidden class实现了IEnumerable和一个迭代器IEnumerator
- 返回值为IEnumerator的方法,系统会自动生成一个hidden class仅仅实现了迭代器IEnumerator。
五、Yield的使用场景
在C#中,yield 关键字用于创建迭代器(iterators)。迭代器是一种特殊的对象,可以按需生成一个序列的值。yield 关键字可以用于以下几种常见的场景:
-
惰性计算(Lazy Evaluation):
yield可以用于按需生成大量数据的序列,而不需要一次性将所有数据计算出来并存储在内存中。这在处理大型数据集或无限序列时非常有用。通过使用yield return语句,可以逐个生成序列中的元素,只有在需要时才计算下一个元素。这样可以节省内存,并且可以更高效地处理数据。public IEnumerable<int> GenerateSequence() { for (int i = 0; i < 10; i++) { yield return i; } }
-
延迟加载(Lazy Loading):
yield也可以用于按需加载大型数据集中的元素。当处理大型数据集时,可以使用yield来逐个加载数据,而不需要一次性加载整个数据集到内存中。这对于性能敏感的应用程序非常有用。public IEnumerable<Data> LoadData() { foreach (var item in database) { // 假设从数据库中加载数据 yield return item; } }
-
过滤和转换数据:
yield可以与 LINQ 查询一起使用,用于过滤和转换数据。通过在迭代器中使用yield return语句,可以根据特定的条件选择性地返回序列中的元素。public IEnumerable<int> FilterEvenNumbers(IEnumerable<int> numbers) { foreach (var number in numbers) { if (number % 2 == 0) { yield return number; } } }
总的来说,yield 关键字在需要处理大量数据、需要按需生成数据、需要延迟加载数据或需要过滤转换数据时非常有用。它提供了一种简洁和高效的方式来处理序列数据。
六、迭代接口的具体实例
namespace ConsoleApp8 { public class Person { public string Name { set; get; } public Person(string name) { Name = name; } } public class PeopleEnum : IEnumerator { public Person[] _people; int position = -1; public PeopleEnum(Person[] list) { _people = list; } public bool MoveNext() { position++; return (position < _people.Length); } public void Reset() { position = -1; } public object Current { get { try { return _people[position]; } catch (IndexOutOfRangeException) { throw new InvalidOperationException(); } } } } public class People : IEnumerable { private Person[] people; public People(Person[] pArray) { people = new Person[pArray.Length]; for (int i = 0; i < pArray.Length; i++) { people[i] = pArray[i]; } } public IEnumerator GetEnumerator() { return new PeopleEnum(people); } } class Program { static void Main(string[] args) { Person[] people = new Person[4] { new Person("李磊"), new Person("王刚"), new Person("彤彤"), new Person("丹丹"), }; People listPeople = new People(people); foreach (var p in listPeople) { Console.WriteLine(((Person)p).Name); } Console.ReadLine(); } } }
欢迎qq加群学习:568055323
浙公网安备 33010602011771号