owned this note
owned this note
Published
Linked with GitHub
---
tags: LinQ, LinQ基礎 , C#
---
# LINQ基礎 - Yield
### 閒話543
C#的語法糖真的是多到一個爆炸呀:laughing:
---
### [Iterators](https://docs.microsoft.com/zh-tw/dotnet/csharp/programming-guide/concepts/iterators) & [yield](https://docs.microsoft.com/zh-tw/dotnet/csharp/language-reference/keywords/yield)
> yield 在 .Net 2.0 時提供
> 當編譯器偵測到迭代器時,它會**自動產生**IEnumerator 或 IEnumerator<T> 介面的 Current、MoveNext 和 Dispose 方法。
> 迭代器方法使用 yield return 陳述式,一次傳回一個項目。 當到達 yield return 陳述式時,系統會記住程式碼中的目前位置。下次呼叫迭代器函式時,便會從這個位置重新開始執行。
> 可以使用 yield break 陳述式結束反覆項目
> 每次反覆運算foreach迴圈 (或直接呼叫 IEnumerator.MoveNext),下一個迭代器程式碼主體都會在上一個 yield return 陳述式之後繼續。 然後繼續執行至下一個 yield return 陳述式,直到達到迭代器主體結尾,或遇到 yield break 陳述式為止。
> 迭代器的宣告必須符合下列需求:傳回型別必須是下列其中一種類型:
> - IAsyncEnumerable<T>
> - IEnumerable<T>
> - IEnumerable
> - IEnumerator<T>
> - IEnumerator
>
> 宣告不能有任何 in、 ref或 out 參數。
> 傳回 yield 或 IEnumerable 的 IEnumerator 類型迭代器為 object。 如果反覆運算器傳回 IEnumerable<T> 或 IEnumerator<T> ,則必須從語句中的運算式類型 yield return 到泛型型別參數進行隱含轉換。
您無法在下列項目中包含 yield return 或 yield break 陳述式:
Lambda 運算式與匿名方法。
包含不安全區塊的方法。 如需詳細資訊,請參閱 unsafe。
#### 範例1 - 使用 yield 完成走訪功能
```C#
public class DaysOfTheWeek : IEnumerable
{
readonly string[] m_Days = { "Sun", "Mon", "Tue", "Wed", "Thr", "Fri", "Sat" };
public IEnumerator GetEnumerator()
{
for (int i = 0; i < m_Days.Length; i++)
{
yield return m_Days[i];
}
}
}
```
```C#
static void Main(string[] args)
{
DaysOfTheWeek week = new DaysOfTheWeek();
foreach (string day in week)
{
Console.Write(day + " ");
}
Console.WriteLine();
var emumerator = week.GetEnumerator();
while (emumerator.MoveNext())
{
Console.Write(emumerator.Current + " ");
}
Console.ReadKey();
}
```
##### 輸出結果
![NGActqW.png](https://github.com/s0920832252/LinQ-Note/blob/master/Resources/NGActqW.png?raw=true)
#### 範例2 - 不使用 yield 完成走訪功能
```C#
public class DaysOfTheWeek : IEnumerable
{
readonly string[] m_Days = { "Sun", "Mon", "Tue", "Wed", "Thr", "Fri", "Sat" };
public IEnumerator GetEnumerator()
{
return new DaysOfTheWeekEnumerator(m_Days);
}
}
```
```C#
public class DaysOfTheWeekEnumerator : IEnumerator
{
private int _index = -1;
private string[] _Days;
public DaysOfTheWeekEnumerator(string[] m_Days) => _Days = m_Days;
public object Current => _Days[_index];
public bool MoveNext() => ++_index < _Days.Length;
public void Reset() => _index = -1;
}
```
```C#
static void Main(string[] args)
{
DaysOfTheWeek week = new DaysOfTheWeek();
foreach (string day in week)
{
Console.Write(day + " ");
}
Console.WriteLine();
var emumerator = week.GetEnumerator();
while (emumerator.MoveNext())
{
Console.Write(emumerator.Current + " ");
}
Console.ReadKey();
}
```
##### 輸出結果
![5LnI64S.png](https://github.com/s0920832252/LinQ-Note/blob/master/Resources/5LnI64S.png?raw=true)
由上面兩個範例 我們可以知道
- yield 可幫助自動產生走訪器 , 因此我們**不必自行定義一個實現 IEnumerator 的類別**.
#### 執行順序
##### 範例 - 走訪 list 內成員
```C#
public class CityManager
{
public static IEnumerable<int> GetEnumerable(List<int> _numbers)
{
foreach (var num in _numbers)
{
Console.WriteLine($"執行yield return前, 數值為:{num}");
if (num == 3)
{
Console.WriteLine("數值為3, 呼叫yield break");
yield break;
}
yield return num;
Console.WriteLine($"執行yield return後的下一行, 數值為:{num}");
}
}
}
```
```C#
static void Main(string[] args)
{
var enumerable = CityManger.GetEnumerable(new List<int>(){ 6, 5, 3, 13, 9, 8, 7 });
Console.WriteLine($"執行了foreach之前");
foreach (var num in enumerable)
{
Console.WriteLine($"foreach - 現在的值是{num}");
Console.WriteLine($"數字:{num}處理完畢.跳下一個數字");
Console.WriteLine();
}
Console.WriteLine($"------------分隔線----------------");
var enumerator = enumerable.GetEnumerator();
Console.WriteLine($"執行了while之前");
while (enumerator.MoveNext())
{
Console.WriteLine($"while - 現在的值是{enumerator.Current}");
Console.WriteLine($"數字:{enumerator.Current}處理完畢.跳下一個數字");
Console.WriteLine();
}
Console.ReadKey();
}
```
##### 輸出結果
![Js81Jn4.png](https://github.com/s0920832252/LinQ-Note/blob/master/Resources/Js81Jn4.png?raw=true)
由上面的範例 我們可以知道執行順序是
1. foreach & in 在執行時會呼叫 MoveNext() , 然後取出 Current 的值
1. 取出 Current 的值後 , 執行 foreach 主體.
1. foreach 要取下一個 Item 時 , 會呼叫 MoveNext() , 此時會從剛剛的 yield return 處下一行開始執行.
1. 上述三個動作會重複執行 , 直到走訪完畢或是碰到 yield break 為止.
---
### 總結
- 使用 yield return 可以輕鬆建立一個 IEnumerable<T> 的資料集合.
- 執行 yield return 後 , 下一次被呼叫時 , 會繼續從上一次的 yield return 後開始執行.
- 呼叫 yield break 後 , 會離開 foreach 主體.
- 如果一個區塊(block)中有 yield 陳述式,則此區塊就叫做 Iterator Block
- 一個方法的區塊如果是 Iterator Block , 則它的回傳值會是 IEnumerable 或是IEnumerator. 請參考[C# 語言規格-類別](https://docs.microsoft.com/zh-tw/dotnet/csharp/language-reference/language-specification/classes#iterators)
> Iterator 會產生一系列的值,這些都是相同的型別。 這個型別稱為 iterator 的yield 型別。
> - 傳回IEnumerator object或IEnumerable的反覆運算器產生類型為 object。
> - 傳回IEnumerator<T> T或IEnumerable<T>的反覆運算器產生類型為 T。
> - ![](https://i.imgur.com/fHr22mu.png)
---
### 補充 - 不使用 yield 實作走訪 list 內成員
```C#
public class CityManger
{
public static IEnumerable<T> GetEnumerable<T>(List<T> _numbers)
=> new Enumerable<T>(_numbers);
}
public class Enumerable<T> : IEnumerable<T>
{
private readonly List<T> _list;
public Enumerable(List<T> list) => _list = list;
public IEnumerator<T> GetEnumerator() => new Enumerator<T>(_list);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
public class Enumerator<T> : IEnumerator<T>
{
private List<T> _list;
private int _index = -1;
public Enumerator(List<T> list) => _list = list;
public T Current => _list[_index];
object IEnumerator.Current => Current;
public bool MoveNext()
{
if (_index != -1 && _list[_index].Equals(3))
return false;
return ++_index < _list.Count;
}
public void Reset() => _index = -1;
public void Dispose() => _list = null;
}
```
![F5BCyIJ.png](https://github.com/s0920832252/LinQ-Note/blob/master/Resources/F5BCyIJ.png?raw=true)
---
### Thank you!
You can find me on
- [GitHub](https://github.com/s0920832252)
- [Facebook](https://www.facebook.com/fourtune.chen)
若有謬誤 , 煩請告知 , 新手發帖請多包涵
# :100: :muscle: :tada: :sheep: