0%

【C#】Yield Return與迭代器

什麼是 Yield

Yield 就是 .Net 中用來實作 iterator(迭代器) 設計模式的語法糖,雖然很早就知道這東西,但一直都不太知道怎麼用,剛好最近同事在翻寫寄送大量信件的程式有用到就跟他請教了一下。

以前的做法

將收件人從 DB 撈出來後 ,依據每封信的 Template 去置換內容,再批次送去給寄信服務執行

問題

如果這批收件人很多,把整批的信件都置換完 Template 再送出,會導致系統吃掉大量的記憶。當然最簡單的解決方法就是用迴圈組完一筆就送出一次,但其實用 Yield Return 可以更優雅的解決這個問題

範例

同事提供了簡單的範例來理解這件事情

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
static void Main(string[] args)
{
foreach (var mail in GetData())
{
Console.WriteLine($"- Received: {mail.Title}");
}
}

static List<Mail> GetData()
{
var mailList = new List<Mail>();
int buffer_size = 1024 * 1024 * 4; // 4MB
Random _rnd = new Random();

for (int index = 0; index < 1024; index++)
{
var _buffer = new byte[buffer_size];
_rnd.NextBytes(_buffer);

mailList.Add(new Mail()
{
Title = $"buffer[{index}], {buffer_size / 1024 / 1024} MB",
Buffer = _buffer,
});
Task.Delay(50).Wait();
}

return mailList;
}

public class Mail
{
public string Title { get; set; }
public byte[] Buffer { get; set; }
}

GetData() 這是模擬處理信件的地方,每封信件假設 4MB (到底是寄啥…),整批處理完後回傳 mailList

如果把這段 Code 放到 LINQPad 執行,會發現記憶體一直往上飆,因為會全部都做完放到 List 再回傳並寄信

/images/20181221/1.gif

Yield Return 版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
static void Main(string[] args)
{
foreach (var mail in GetData())
{
Console.WriteLine($"- Received: {mail.Title}");
}
}

static IEnumerable<Mail> GetData()
{
int buffer_size = 1024 * 1024 * 4; // 4MB
Random _rnd = new Random();

for (int index = 0; index < 1024; index++)
{
var _buffer = new byte[buffer_size];
_rnd.NextBytes(_buffer);

yield return new Mail()
{
Title = $"buffer[{index}], {buffer_size / 1024 / 1024} MB",
Buffer = _buffer,
};
Task.Delay(50).Wait();
}

yield break;
}

public class Mail
{
public string Title { get; set; }
public byte[] Buffer { get; set; }
}

觀察記憶體會發現非常的平穩,因為逐筆處理完就被消滅掉了

/images/20181221/2.gif

小結

至於如果不用語法糖 Yield 該如何實作迭代器,可參考安德魯的部落格裡面介紹非常詳細,希望這個方法以後可以更融會貫通的套在專案中使用