試煉16 - IEnumerable + yield 的坑踩過了嗎

2022 鐵人賽文 搬回點部落

開始試煉

黑暗執行緒有說明 IEnumerable + yield 的好處
善用 yield return 省時省 CPU 省 RAM,打造高效率程式

但是必須知道IEnumerable的特性,要不然就像雙面刃
一不小心挖坑給自己跳囉
IEnumerable 是
延遲執行(deferred execution)或 惰性求值(lazy evaluation)
簡單的說 就是 每次都會再執行一次
來個一般用List範例

void Main()
{
    var store = new DemoStore();
    var productList = store.GetProducts();
    var total = productList.Count();

    $"Total products {total}".Dump();

    foreach (var product in productList)
    {
        $"{product.Id},{product.Name}".Dump();
    }
}
public class DemoStore
{
    public List<Product> GetProducts()
    {
        List<Product> result = new List<Product>();
        for (int i = 0; i < 5; i++)
        {
            $"{i}產生Product中".Dump();
            result.Add(new Product()
            {
                Id = i,
                Name = $"Pro {i}"
            });
        }
        return result;
    }
}
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
}
// Define other methods and classes here

執行結果是
0產生Product中
1產生Product中
2產生Product中
3產生Product中
4產生Product中
Total products 5
0,Pro 0
1,Pro 1
2,Pro 2
3,Pro 3
4,Pro 4

然後看完 IEnumerable + yield 覺得好棒就這樣改

void Main()
{
	var store = new DemoStore();
	var productList = store.GetProducts();
	var total = productList.Count();

	$"Total products {total}".Dump();

	foreach (var product in productList)
	{
		$"{product.Id},{product.Name}".Dump();
	}
}
public class DemoStore
{
	public IEnumerable<Product> GetProducts()
	{
		int count = 5;
		for (int i = 0; i < count; i++)
		{
			$"{i}產生Product中".Dump();
			yield return new Product()
			{
				Id = i,
				Name = $"Pro {i}"
			};
		}
	}
}
public class Product
{
	public int Id { get; set; }
	public string Name { get; set; }
}

執行結果是
0產生Product中
1產生Product中
2產生Product中
3產生Product中
4產生Product中
Total products 5
0產生Product中
0,Pro 0
1產生Product中
1,Pro 1
2產生Product中
2,Pro 2
3產生Product中
3,Pro 3
4產生Product中
4,Pro 4

有想到會變成這樣嗎 需然每次有用到 GetProducts的時候
就會再跑一次 如果是查資料庫 就會讀取資料庫兩次

這個範例來說 就是要在適合地方加上ToList
var productList = store.GetProducts().ToList();
這樣就可以了

延伸試煉

搞懂延遲執行
[.NET]快快樂樂學LINQ系列前哨戰-延遲執行 (Deferred Execution)
[.NET] LINQ 的延遲執行 (Deferred Execution)

結束試煉

所以IEnumerable + yield 是好東西 但是要知道如何應對這雙面刃
最後這也是 程式是照你寫的跑不是照你想的跑

參考
How IEnumerable can kill your performance in C#

 

如果內容有誤請多鞭策謝謝