C# 学习之路(十八)
Posted Moly的太阳
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C# 学习之路(十八)相关的知识,希望对你有一定的参考价值。
枚举集合
本系列文章主要意在总结笔者在学习过程中学到的有关 C# 特性的知识,分享 C# 中较为重要和突出的部分和有助养成良好编程习惯的提示。
并非旨在系统介绍 C#。
今天主要分享枚举器、迭代器和枚举集合的相关知识,这是我很早就想放在文章中分享的部分。
绿色高亮:IEnumerable
棕色高亮:IEnumerator
蓝色高亮:其他代码
目录:
枚举集合和枚举器
迭代器实现枚举器
枚举集合和枚举器:
小读者们对 foreach 语句肯定不陌生吧!我们可以用 foreach 语句对数组进行正向遍历。我们还可以用 foreach 语句去遍历一个 List<T> 类:
List<int> numbers = new List<int>();
// 初始化集合
foreach (int number in
new int[12]{ 10, 9, 8, 7, 7, 6 ,5 ,10, 4, 3, 2, 1 })
{
numbers.Add(number);
}
// 用 foreach 语句遍历 List<T> 集合
foreach (int number in numbers)
{
Console.WriteLine(number);
}
foreach 语句无疑极大简化了需要编写的代码,但它的使用也是有条件的!这个条件就是:foreach 只可用于遍历可枚举集合。
什么是可枚举集合?简单地说,就是实现了 System.Collections.IEnumerable 接口的集合。(下面简写为 IEnumerable )
需要特别提醒的是,C# 所有数组都是 System.Array 类的实例,而 System.Array 类是实现了 IEnumerable 接口的类。因此所有数组都支持 foreach 语句。
IEnumerable 接口只包含一个名为 GetEnumerator 方法。因此我们只需要实现这个方法,就能完成为接口 IEnumerable 的实现。GetEnumerator 方法的原型如下:
IEnumerator GetEnumerator();
可以发现,GetEnumerator 方法的返回值为 IEnumerator 。好家伙,这是要我返回一个接口,我咋不记得有返回一个接口的方法?读者可能会有这样的疑惑。但其实不然,返回值是 IEnumerator 表示的是我们需要返回一个实现了 IEnumerator 接口的对象!这就好比返回值类型是 object 不一定真的就要返回 object 变量,将返回值类型设置成 object 只是为了囊括所有继承自 object 的类。将返回值类型设置为 IEnumerator 也是同样的道理。
一个实现了 IEnumerator 接口的对象被称为枚举器对象(以下简称枚举器)。因此方法 GetEnumerator 正如其名,返回一个枚举器(Enumerator)。
那么现在,我们显然就要讨论实现 IEnumerator 接口的类又需要实现哪些部分了。(请注意,IEnumerable 和 IEnumerator 是不一样的!虽然它们的拼写很类似)
IEnumerator 接口制定了以下属性和方法:
object Current { get; }
bool MoveNext();
void Reset();
枚举器要实现以上代码段中声明的方法和属性。可以将枚举器想象成指向实现了 IEnumerable 接口的对象中的元素的指针。放在 List<T> 类中,就可以说 List<T> 的枚举器是指向 List<T> 类中的元素的指针。
指针最开始指向第一个元素之前的位置,一般第一个元素的位置是 0 ,那么第一个元素之前的位置可以理解为位置 -1。调用 MoveNext 方法,就可使指针移至列表中的下一项;如果能实际地移到下一项,MoveNext 方法返回 true,否则返回 false。可用 Current 属性访问当前指向的那一项(这就是为啥它是 object 类型的);使用 Reset 方法,则可使指针回到列表第一项之前的位置,也就是位置 -1。
实现了 IEnumerable 接口的类通过集合的 GetEnumerator 方法创建枚举器,然后反复调用枚举器的 MoveNext 方法,并获取 Current 属性的值,就可以每次在该集合中移动一个元素的位置。这正是 foreach 语句做的事情。所以,为了创建自己的可枚举集合类,就必须在自己的集合中实现 IEnumerable 接口,并提供 IEnumerator 接口的一个实现(枚举器),以便由集合类的 GetEnumerator 方法返回。
稍微想一下,就会发现 IEnumerator 接口的 Current 属性具有非类型安全的行为,因为它返回 object 而非具体类型。幸好,.NET Framework 类库还提供了泛型 IEnumerator<T> 接口,该接口同样有 Current 属性,但返回的是一个 T。类似地,还有一个 IEnumerable<T> 接口,其中的 GetEnumerator 方法返回的是一个实现了 IEnumerator<T> 接口的枚举器对象。
应尽可能使用泛型版本的 IEnumerable 和 IEnumerator 接口。
我们来看一个来自微软文档的实例:
using System;
using System.Collections;
// 一个待会要被使用的类
public class Person
{
public Person(string fName, string lName)
{
this.firstName = fName;
this.lastName = lName;
}
public string firstName;
public string lastName;
}
// 实现 IEnumerable 接口的类,也就是支持 foreach 语句的类
public class People : IEnumerable
{
private Person[] _people; // 利用先前定义的 Person 类
public People(Person[] pArray)
{
_people = new Person[pArray.Length];
for (int i = 0; i < pArray.Length; i++)
{
_people[i] = pArray[i];
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return (IEnumerator) GetEnumerator();
// 此 GetEnumerator 非 IEnumerable.GetEnumerator 方法,其定义就在下方
}
// 这个 GetEnumerator 方法用于返回一个 PeopleEnum 枚举器
public PeopleEnum GetEnumerator()
{
return new PeopleEnum(_people);
}
}
// 枚举器本尊
public class PeopleEnum : IEnumerator
{
public Person[] _people;
// 这个 position 变量代表元素位置
int position = -1;
// 初始化枚举器
public PeopleEnum(Person[] list)
{
_people = list;
}
public bool MoveNext()
{
position++;
return (position < _people.Length);
}
public void Reset()
{
position = -1;
}
public Person Current
{
get
{
try
{
return _people[position];
}
catch (IndexOutOfRangeException)
{
throw new InvalidOperationException();
}
}
}
object IEnumerator.Current
{
get
{
return Current;
}
}
}
class App
{
static void Main()
{
Person[] peopleArray = new Person[3]
{
new Person("John", "Smith"),
new Person("Jim", "Johnson"),
new Person("Sue", "Rabon"),
};
People peopleList = new People(peopleArray);
foreach (Person p in peopleList)
Console.WriteLine(p.firstName + " " + p.lastName);
}
}
/* This code produces output similar to the following:
*
* John Smith
* Jim Johnson
* Sue Rabon
*
*/
图片版:
为了大 以上是关于C# 学习之路(十八)的主要内容,如果未能解决你的问题,请参考以下文章