假設有兩個類,一個學生類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();
}
}
}