C#之旅(二): IEnumerable IEnumerator
又是两个以rable和rator结尾的两个接口,每次看到这种定义我就会很头疼,很容易搞混掉,所以这次我要将自己的学习记录下来,慢慢的把它们给啃掉,好记性不如烂笔头,也方便今后自己的复习。
通常在编程中我们常常面临这样一个问题就是对一个集合的遍历通常也就是一个数组或是一个链表,比如说一个保存了整个班级名单的变量还是一个Person类型的数组,那我们该怎么去做。当然在C#中我们可以使用foreach语句来轻松的完成遍历:
{
//...do something
}
但这里有个前提条件就是personArray对象类型它必须实现IEnumerable才能够使用foreach语句来进行遍历,看看IEnumerable接口的定义,它只有一个方法:
{
IEnumerator GetEnumerator();
}
而且该方法的返回类型正是IEnumerator接口,为什么要返回这么一个接口呢,同样看下它的定义:
{
bool MoveNext();
object Current { get; }
void Reset();
}
这个接口有三个方法,最后一个方法暂时先不管,主要看前面两个,似乎有点明白了,从方法名字上来看MoveNext移动到下一个元素,如果有下一个元素就是返回true否则就是false。比如当前遍历到了某个同学,要是他不是最后一个说明还可以继续遍历返回true,如果是最后一个了说明不能在往下遍历,再往下可能就是别的班的同学了,此时返回一个false。在看下一个Current,它不是一个方法而是属性,只有一个get方法说明它是只读的,返回值是object可以是任意的对象。那么它的作用就显而易见了就是获取当前遍历位置下的元素了。从以上可以看出真正的遍历实现是由IEnumerator来实现的,IEnumerable规定了该类是可用foreach遍历的,且返回一个遍历的具体实现类IEnumerator。就好像学校过来检查要点名了,任课老师(IEnumerable)懒的自己一个一个点就吩咐记录委员(IEnumerator)来点名,记录委员就不停的MoveNext、MoveNext…点到的同学呢(Current)就喊一声到,一直点到最后一个同学(返回false)才停止。这时假如任课老师觉得人数不对来的人也太少了怎么就全到了呢,就要求重点(这里就相当于Reset了),无奈职责所在只好在foreach一遍…
具体代码如下:
{
class Program
{
static void Main(string[] args)
{
Person[] array = { new Person { Name = "叶华斌", Age = 23 },
new Person { Name = "王昌文", Age = 22 },
new Person { Name = "吴朝剑", Age = 21 }};
PersonArray pa = new PersonArray(array);
foreach (Person item in pa)
{
Console.WriteLine(string.Format("姓名:{0},年龄{1}", item.Name, item.Age));
}
}
}
public class PersonArray : IEnumerable
{
Person[] array;
public PersonArray(Person[] array)
{
this.array = array;
}
public IEnumerator GetEnumerator()
{
return new PersonEnumerator(array);
}
}
public class PersonEnumerator : IEnumerator
{
Person[] array;
int position;
public PersonEnumerator(Person[] array)
{
this.array = array;
this.position = -1;
}
public object Current
{
get
{
if (-1 < position && position < array.Length)
{
return array[position];
}
else
{
throw new IndexOutOfRangeException();
}
}
}
public bool MoveNext()
{
position++;
return position < array.Length;
}
public void Reset()
{
position = -1;
}
}
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
}
输出结果:
值得一看的是反编译之后它的具体实现如何,来看下:
PersonArray pa;
Person item;
Person <>g__initLocal0;
Person <>g__initLocal1;
Person <>g__initLocal2;
Person[] CS$0$0000;
IEnumerator CS$5$0001;
bool CS$4$0002;
IDisposable CS$0$0003;
CS$0$0000 = new Person[3];
<>g__initLocal0 = new Person();
<>g__initLocal0.Name = "叶华斌";
<>g__initLocal0.Age = 0x17;
CS$0$0000[0] = <>g__initLocal0;
<>g__initLocal1 = new Person();
<>g__initLocal1.Name = "王昌文";
<>g__initLocal1.Age = 0x16;
CS$0$0000[1] = <>g__initLocal1;
<>g__initLocal2 = new Person();
<>g__initLocal2.Name = "吴朝剑";
<>g__initLocal2.Age = 0x15;
CS$0$0000[2] = <>g__initLocal2;
array = CS$0$0000;
pa = new PersonArray(array);
//以上做些变量声明的工作
CS$5$0001 = pa.GetEnumerator();//①先调用IEnumerable中得方法返回一个迭代器IEnumerator也就是PersonEnumerator
Label_0084:
try
{
goto Label_00B6;//②使用goto语句跳到下面Label_00B6:处
Label_0086:
item = (Person) CS$5$0001.Current;//④存在就通过Current属性返回当前值
Console.WriteLine(string.Format("姓名:{0},年龄{1}", item.Name, (int) item.Age));
Label_00B6:
if (CS$5$0001.MoveNext() != null)//③移动到下一个元素判断是否存在
{
goto Label_0086;
}
goto Label_00E2;//⑤不存在就跳出foreach语句
}
finally
{
Label_00C5:
CS$0$0003 = CS$5$0001 as IDisposable;
if ((CS$0$0003 == null) != null)
{
goto Label_00E1;
}
CS$0$0003.Dispose();
Label_00E1:;
}
Label_00E2:
return;
大概执行顺序是这样的:1. 执行foreach语句前先调用IEnumerable.GetEnumorator()返回一个IEnumerator类型的枚举器。2. 调用IEnumerator.MoveNext()从前一个元素的位置移动到下一个3. 判断是否是最后一个元素,是跳出循环,否往下执行
4. 调用IEnumerator.Current属性返回当前的元素5. 执行foreach语句块内的内容6. 跳至第2步好了到这一步我们需要的功能也都已经完成了,但总感觉一路走来有点艰辛呀,为什么呢,因为这个PersonEnumerator类让我们的实现变得复杂了许多,接下来再来看一种简洁的形式:
{
class Program
{
static void Main(string[] args)
{
Person[] array = { new Person { Name = "叶华斌", Age = 23 },
new Person { Name = "王昌文", Age = 22 },
new Person { Name = "吴朝剑", Age = 21 }};
PersonArray pa = new PersonArray(array);
foreach (Person item in pa)
{
Console.WriteLine(string.Format("姓名:{0},年龄{1}", item.Name, item.Age));
}
}
}
public class PersonArray : IEnumerable
{
Person[] array;
public PersonArray(Person[] array)
{
this.array = array;
}
public IEnumerator GetEnumerator()
{
for (int i = 0; i < array.Length; i++)
{
yield return array[i];
}
}
}
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
}
通过yield return关键字就可以省去编写PersonEnumerator,通过反编译工具看到PersonArray实现代码:
{
// Fields
private Person[] array;
// Methods
public PersonArray(Person[] array);
public IEnumerator GetEnumerator();
// Nested Types
[CompilerGenerated]
private sealed class <GetEnumerator>d__0 : IEnumerator<object>, IEnumerator, IDisposable
{
// Fields
private int <>1__state;
private object <>2__current;
public PersonArray <>4__this;
public int <i>5__1;
// Methods
[DebuggerHidden]
public <GetEnumerator>d__0(int <>1__state);
private bool MoveNext();
[DebuggerHidden]
void IEnumerator.Reset();
void IDisposable.Dispose();
// Properties
object IEnumerator<object>.Current { [DebuggerHidden] get; }
object IEnumerator.Current { [DebuggerHidden] get; }
}
}
原来编译器通过内嵌类来帮我们实现了PersonEnumerator,只是名字不同叫做<GetEnumerator>d__0罢啦。
最后再来一个泛型版本的:
{
class Program
{
static void Main(string[] args)
{
Person[] array = { new Person { Name = "叶华斌", Age = 23 },
new Person { Name = "王昌文", Age = 22 },
new Person { Name = "吴朝剑", Age = 21 }};
MyList<Person> pa = new MyList<Person>(array);
foreach (Person item in pa)
{
Console.WriteLine(string.Format("姓名:{0},年龄{1}", item.Name, item.Age));
}
}
}
public class MyList<T> :IEnumerable<T>
{
T[] array;
public MyList(T[] array)
{
this.array = array;
}
public IEnumerator<T> GetEnumerator()
{
return new MyListEnumerator<T>(array);
}
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
}
public class MyListEnumerator<T> : IEnumerator<T>
{
T[] array;
int position;
public MyListEnumerator(T[] array)
{
this.array = array;
this.position = -1;
}
object IEnumerator.Current
{
get
{
if (-1 < position && position < array.Length)
{
return array[position];
}
else
{
throw new IndexOutOfRangeException();
}
}
}
public bool MoveNext()
{
position++;
return position < array.Length;
}
public void Reset()
{
position = -1;
}
T IEnumerator<T>.Current
{
get
{
if (-1 < position && position < array.Length)
{
return array[position];
}
else
{
throw new IndexOutOfRangeException();
}
}
}
public void Dispose()
{
}
}
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
}
从上面的代码我们也可以对类库中得List、List<T>类有个大概的了解了,上面的MyList<T>的类相当于一个简略版本List<T>,它仅仅提供对列表的遍历,没有实现对元素操作罢了。