假设有两个类,一个学生类Student,它用来存储一个学生的信息,如名字和年龄。第二个是学校类School,它是学生类的集合。接下来我们一点点的看可迭代对象是如何进化的
一、石器时代的写法
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApp1
{
internal class Student
{
public string Name { get; set; }
public int Age { get; set; }
public Student(string name, int age)
{
Name = name;
Age = age;
}
}
internal class School
{
private readonly List<Student> _list = new List<Student>();
public int Count => _list.Count();
public Student this[int index] => _list[index];
public void Add(Student item)
{
_list.Add(item);
}
}
internal class Program
{
private static void Main(string[] args)
{
var school = new School();
// 添加3个学生
school.Add(new Student("ahri", 15));
school.Add(new Student("ashe", 20));
school.Add(new Student("annie", 25));
// 遍历学校输出所有学生信息
for (var i = 0; i < school.Count; i++)
{
var item = school[i];
Console.WriteLine($"{item.Name}\t{item.Age}");
}
Console.ReadLine();
}
}
}
通过for循环遍历是在任何编程语言的入门书籍中都会提到的方法,因为它最基础。
二、使用IEnumerable
现代编程语言中基本上都会提供类似foreach这样的关键字,它可以说是for的升级版本,它不需要你提供集合对象的元素数量就可以遍历。而第一种遍历方法的缺点是无法使用foreach关键字
看到提示,一个集合类想通过foreach来遍历就得提供GetEnumerator方法,而这个方法正是IEnumerable接口唯一的一个方法,我们只需要继承这个接口
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApp1
{
internal class Student
{
public string Name { get; set; }
public int Age { get; set; }
public Student(string name, int age)
{
Name = name;
Age = age;
}
}
internal class School : IEnumerable
{
private readonly List<Student> _list = new List<Student>();
IEnumerator IEnumerable.GetEnumerator()
{
return new StudentEnumerator(_list);
}
public void Add(Student item)
{
_list.Add(item);
}
}
internal class StudentEnumerator : IEnumerator
{
private readonly List<Student> _list;
private int _index = -1;
public StudentEnumerator(List<Student> items)
{
_list = items;
}
public bool MoveNext()
{
var count = _list.Count();
if (_index < count)
{
_index++;
}
return _index < _list.Count();
}
public void Reset()
{
_index = -1;
}
public object Current => _list[_index];
}
internal class Program
{
private static void Main(string[] args)
{
var school = new School { new Student("ahri", 15), new Student("ashe", 20), new Student("annie", 25) };
foreach (var item in school)
{
var student = (Student)item;
Console.WriteLine($"{student.Name}\t{student.Age}");
}
Console.ReadLine();
}
}
}
怎么看上去代码越来越多了呢?是的,为了能用上foreach,结果还得多写一个StudentEnumerator类,它继承自IEnumerator,必须实现2个方法和1个属性,在foreach遍历时会调用MoveNext来得知遍历是否要结束了(内部通过索引来判断是否到列表末尾了),没遍历完的话则通过Current属性来获取元素。可以看出,遍历的核心实质上都是在StudentEnumerator这个类里面,反而School显得多余了,C#支持继承多接口,所以对于这个例子我们是可以将它们合并的
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApp1
{
internal class Student
{
public string Name { get; set; }
public int Age { get; set; }
public Student(string name, int age)
{
Name = name;
Age = age;
}
}
internal class School : IEnumerable, IEnumerator
{
private readonly List<Student> _list = new List<Student>();
private int _index = -1;
IEnumerator IEnumerable.GetEnumerator()
{
return this;
}
public void Add(Student item)
{
_list.Add(item);
}
public bool MoveNext()
{
var count = _list.Count();
if (_index < count)
{
_index++;
}
return _index < _list.Count();
}
public void Reset()
{
_index = -1;
}
public object Current => _list[_index];
}
internal class Program
{
private static void Main(string[] args)
{
var school = new School { new Student("ahri", 15), new Student("ashe", 20), new Student("annie", 25) };
foreach (var item in school)
{
var student = (Student)item;
Console.WriteLine($"{student.Name}\t{student.Age}");
}
Console.ReadLine();
}
}
}
一般不推荐这样写,因为在GetEnumerator这里返回的是自身,意味着迭代器不是独立的,如果在foreach嵌套的情况下可能不是你想要的
foreach (var item in school)
{
var student = (Student)item;
Console.WriteLine($"Dep1:{student.Name}\t{student.Age}");
foreach (var item2 in school)
{
var student2 = (Student)item2;
Console.WriteLine($"Dep2:{student2.Name}\t{student2.Age}");
}
}
因为共享的是一个迭代器,所以外面的foreach只执行了一次
三、引入yield,抛弃IEnumerator
为了循环时方便一点,似乎在集合类内部需要更多处理,有些得不偿失的感觉。还好,C#没让我们失望,它提供了一个更现代的武器:yield
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApp1
{
internal class Student
{
public string Name { get; set; }
public int Age { get; set; }
public Student(string name, int age)
{
Name = name;
Age = age;
}
}
internal class School : IEnumerable
{
private readonly List<Student> _list = new List<Student>();
IEnumerator IEnumerable.GetEnumerator()
{
foreach (var item in _list)
{
yield return item;
}
}
public void Add(Student item)
{
_list.Add(item);
}
}
internal class Program
{
private static void Main(string[] args)
{
var school = new School { new Student("ahri", 15), new Student("ashe", 20), new Student("annie", 25) };
foreach (var item in school)
{
var student = (Student)item;
Console.WriteLine($"{student.Name}\t{student.Age}");
}
Console.ReadLine();
}
}
}
通过yield return来一个个返回元素,就可以完全抛弃了StudentEnumerator类了。但请明白,yield只是语法糖,实际上在程序编译时C#会自动实现一个对应的Enumerator类来完成迭代工作,只不过我们看不到而已。
最后,在foreach循环中每次都要做一次强制转换也让人受不了!还好,IEnumerable支持泛型
using System;
using System.Collections;
using System.Collections.Generic;
namespace ConsoleApp1
{
internal class Student
{
public string Name { get; set; }
public int Age { get; set; }
public Student(string name, int age)
{
Name = name;
Age = age;
}
}
internal class School : IEnumerable<Student>
{
private readonly List<Student> _list = new List<Student>();
public void Add(Student item)
{
_list.Add(item);
}
public IEnumerator<Student> GetEnumerator()
{
foreach (var item in _list)
{
yield return item;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
internal class Program
{
private static void Main(string[] args)
{
var school = new School { new Student("ahri", 15), new Student("ashe", 20), new Student("annie", 25) };
foreach (var student in school)
{
Console.WriteLine($"{student.Name}\t{student.Age}");
}
Console.ReadLine();
}
}
}