探究C#中的IEnumerable、IEnumerator、Yield
Case1:IEnumerable(可列舉/迭代的)
如果一個類實作了IEnumerable介面,則稱為此類為可迭代的,要實作IEnumerable介面就要實作IEnumerable中定義的GetEnumerator()的方法生成一個迭代器(IEnumerator),
IEnumerable是所有集合型別介面的基介面,如ICollection、IList;IEnumerable只能進行讀操作,其具有延遲執行的特性,ICollection、IList的實作類支持增刪改查,IList的實作類支持的操作更多、更加靈活如插入元素、移除元素等,
Case2:IEnumerator(迭代器)
如果我們把實作IEnumerable[可迭代的]看做一個類可以進行迭代的標記,那么IEnumerator[迭代器]就相當于迭代時的作業引擎,迭代中的主要作業都需要依靠迭代器進行,
Case3:Foreach相關
-
Case3-1 什么樣的類可以進行Foreach迭代?
實作IEnumerable介面的類,換一個更強的定義,實作了IEnumerable介面同時實作了介面中的GetEnumerator方法,
-
Case3-2 Foreach究竟做了哪些事情?
var lst = new List<int> { 1, 2, 3 }; foreach (var item in lst) { Console.WriteLine(item); } Console.WriteLine("----------Equal-----------"); using var enumerator = lst.GetEnumerator();//獲得迭代器,using宣告確保迭代器用完就Dispose while (enumerator.MoveNext()) //迭代器開始作業 { Console.WriteLine(enumerator.Current);//獲取當前項 }
Case4:自定義的可迭代類
// 可迭代物件 標記此類可迭代
public class SpecificEnumerable: IEnumerable
{
private readonly object[] _dataSource;
public SpecificEnumerable(object[] values)
{
_dataSource = values;
}
public IEnumerator GetEnumerator()
{
return new SpecificEnumerator(_dataSource);
}
// 迭代器 作業引擎
internal class SpecificEnumerator: IEnumerator
{
private int _position; // 指標
private readonly object[] _data; // 資料源
public SpecificEnumerator(object[] data)
{
_position = -1;
_data = data;
}
public bool MoveNext()
{
if(_position != _data.Length)
{
_position++;
}
return _position >= 0 && _position < _data.Length;
}
public void Reset()
{
_position = -1;
}
public object Current
{
get
{
if(_position == -1 || _position == _data.Length)
{
throw new IndexOutOfRangeException();
}
return _data[_position];
}
}
}
}
class Program
{
static void Main(string[] args)
{
# region 自定義可列舉類
var myEnumerable = new SpecificEnumerable(new object[]
{
"A", "B", "C", "D"
});
foreach(var item in myEnumerable)
{
Console.WriteLine(item);
}
Console.WriteLine("=========================>");
var myEnumerator = myEnumerable.GetEnumerator();
while(myEnumerator.MoveNext())
{
Console.WriteLine(myEnumerator.Current);
}
#endregion
}
}
Case5:使用yield語法糖快速生成迭代器
通過Case4我們可以看到,整個創建自定義可迭代類的時候較為繁瑣的程序就是生成作業引擎迭代器,需要手工實作迭代器介面中的MoveNext、Current屬性等等,那么為了方便我們快速的生成列舉器,C#提供了一個yield語法糖來幫助我們簡化這一程序,請注意,這僅僅是一個語法糖,
public class SpecificYieldEnumerable: IEnumerable
{
private readonly Random _random = new();
private readonly int _capacity;
public SpecificYieldEnumerable(int count)
{
_capacity = count;
}
public IEnumerator GetEnumerator()
{
for(var i = 0; i < _capacity; i++)
{
yield return _random.Next(100);
}
}
}
class Program
{
static void Main(string[] args)
{
var myEnumerable = new SpecificYieldEnumerable(10);
foreach(var item in myEnumerable)
{
Console.WriteLine(item);
}
Console.WriteLine("=================>");
var myEnumerator = myEnumerable.GetEnumerator();
while(myEnumerator.MoveNext())
{
Console.WriteLine(myEnumerator.Current);
}
}
}
實際包含yield return的回傳值為IEnumerator的方法,系統會幫我們自動生成一個hidden class實作了IEnumerator介面,這就是yield語法糖背后做的作業,
Case6:了解yield背后的故事
- 包含yield的方法回傳值只能為IEnumerable和IEnumerator,
- 回傳值為IEnumerable的方法,系統會自動生成一個hidden class實作了IEnumerable和一個迭代器IEnumerator
- 回傳值為IEnumerator的方法,系統會自動生成一個hidden class僅僅實作了迭代器IEnumerator,
我們來驗證一下上述結論:

我們反編譯了解一下YieldTest1和YieldTest2在應用編譯后的代碼,

可以看出上述兩個包含yield的方法編譯后分別回傳了一個實體,我們再對應看下系統生成的兩個類<YieldTest1>d__0和<YieldTest2>d__1的定義,


進一步分析yield的運行程序,我們以YieldTest2方法為例分析:

整個迭代的主要作業都是在MoveNext中進行,類YieldTest2__1中一共有三個欄位:
- state:迭代的執行狀態,分別維護三個值0[迭代開始]、-1[迭代結束]、1[迭代進行中]
- current:當前迭代項的值
- i:迭代次序
那么此代碼的運行程序大致如下:
var enumerator = YieldTest2(); //并沒有去執行 我們從編譯后的代碼中也可以看到 此處只是得到了一個迭代器物件
while (enumerator.MoveNext()) // 呼叫MoveNext()真正開始執行
{
Console.WriteLine(enumerator.Current);
}
Step1:呼叫MoveNext()迭代開始,state狀態賦值為0,迭代開始,current賦值,state狀態賦值為1,迭代繼續,
Step2:Loop MoveNext()判斷state是否為1,為1,迭代繼續,state置為-1,指標i++,判斷i是否越界,若不越界,state再次置為1,繼續迭代重復Step2;如已越界,執行Step3;
Step3:return false 迭代結束,
Case7: 一個案例闡述包含yield的迭代器在迭代時的運行順序
public static IEnumerable YieldEnumerable()
{
Console.WriteLine("Start");
for (var i = 1; i <= 3; i++)
{
Console.WriteLine($"Item Start:{i}");
yield return i;
Console.WriteLine("Item End");
}
yield return -1;
Console.WriteLine("End");
}
static void Main(string[] args)
{
var enumerable = YieldEnumerable();
foreach (var item in enumerable)
{
Console.WriteLine($"Foreach {item} Start");
Console.WriteLine(item);
Console.WriteLine($"Foreach {item} End");
}
}
運行結果

我們結合前面的分析,實際上就很容易理解上述代碼的運行順序,包含yield的函式內部的執行邏輯就像被分割了幾個部分一樣,當呼叫MoveNext()時,開始執行,遇到yield時函式內部就像按下了暫停鍵,呼叫邏輯繼續向后執行,當再次呼叫MoveNext()時,函式內部按下開始鍵,繼續向后執行,當MoveNext()回傳false,迭代停止,
Case8:yield的實際應用
yield目前應用十分廣泛,不僅僅是C#,包括Python、JavaScript等語言中均有它的身影出現,
比如C#中TAP異步編程模型中的async、await語法糖的背后原理中也有yield參與作業、Dapper的原始碼中也出現了很多yield的身影;Python中也支持yield,可以使用yield+Event Loop實作協程以便進行協同式多任務處理;JavaScript中ES6中的Generator生成器中使用yield,內部模擬一個狀態機式的管理機制實作更加優雅的異步編程,
最后我們給出一個JavaScript Generator 的一個小案例:
const funcAsync = (i) =>
new Promise((resovle, reject) => {
if (i === 3) {
reject('Wow!')
}
setTimeout(() => {
resovle('Hello World' + i)
}, 2000)
})
function* foo() {
console.log('Func1 Start')
yield funcAsync(1)
console.log('Func2 Start')
yield funcAsync(2)
console.log('Func3 Start')
return funcAsync(3)
}
const generator = foo()
generator
.next()
.value.then((res) => {
console.log(res)
return generator.next().value
})
.then((res) => {
console.log(res)
return generator.next().value
})
.then((res) => {
console.log(res)
})
.catch((err) => {
console.error(err)
})

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/296971.html
標籤:其他
