yield
คำหลักที่ช่วยให้คุณสามารถสร้างIEnumerable<T>
ในรูปแบบบนบล็อก iterator บล็อกตัววนซ้ำนี้รองรับการประมวลผลที่เลื่อนออกไปและหากคุณไม่คุ้นเคยกับแนวคิดนั้นอาจปรากฏขึ้นเกือบวิเศษ อย่างไรก็ตามในตอนท้ายของวันมันเป็นเพียงรหัสที่รันโดยไม่มีเทคนิคแปลก ๆ
บล็อกตัววนซ้ำสามารถอธิบายได้เป็นน้ำตาลซินแทกติกที่คอมไพเลอร์สร้างเครื่องรัฐที่ติดตามว่าการแจงนับของจำนวนที่ดำเนินไปไกลแค่ไหน เมื่อต้องการระบุจำนวนคุณมักใช้foreach
วนรอบ อย่างไรก็ตามforeach
วนซ้ำก็เป็นน้ำตาลเชิงประโยค ดังนั้นคุณจะถูกลบสอง abstractions ออกจากรหัสจริงซึ่งเป็นสาเหตุที่ในตอนแรกมันอาจจะยากที่จะเข้าใจว่ามันทำงานร่วมกันได้อย่างไร
สมมติว่าคุณมีตัววนซ้ำแบบง่ายมาก:
IEnumerable<int> IteratorBlock()
{
Console.WriteLine("Begin");
yield return 1;
Console.WriteLine("After 1");
yield return 2;
Console.WriteLine("After 2");
yield return 42;
Console.WriteLine("End");
}
ตัววนซ้ำตัวจริงมักจะมีเงื่อนไขและลูป แต่เมื่อคุณตรวจสอบเงื่อนไขและปลดลูปพวกมันจะยังคงเป็น yield
คำสั่งแทรกระหว่างโค้ดอื่น ๆ
ในการระบุการวนซ้ำบล็อกforeach
วนใช้:
foreach (var i in IteratorBlock())
Console.WriteLine(i);
นี่คือผลลัพธ์ (ไม่ประหลาดใจที่นี่):
เริ่ม
1
หลังจาก 1
2
หลังจาก 2
42
ปลาย
ตามที่ระบุไว้ข้างต้นforeach
คือน้ำตาลประโยค:
IEnumerator<int> enumerator = null;
try
{
enumerator = IteratorBlock().GetEnumerator();
while (enumerator.MoveNext())
{
var i = enumerator.Current;
Console.WriteLine(i);
}
}
finally
{
enumerator?.Dispose();
}
ในความพยายามที่จะแก้ให้หายยุ่งนี้ฉันได้สร้างแผนภาพลำดับที่มีการลบ abstractions:
เครื่องสถานะที่สร้างโดยคอมไพเลอร์ยังใช้ตัวแจงนับ แต่เพื่อทำให้ไดอะแกรมชัดเจนยิ่งขึ้นฉันได้แสดงมันเป็นอินสแตนซ์แยกต่างหาก (เมื่อเครื่องสถานะถูกระบุจากเธรดอื่นคุณจะได้รับอินสแตนซ์แยกต่างหาก แต่รายละเอียดนั้นไม่สำคัญที่นี่)
ทุกครั้งที่คุณเรียกใช้ตัววนซ้ำบล็อกอินสแตนซ์ใหม่ของเครื่องสถานะจะถูกสร้างขึ้น อย่างไรก็ตามรหัสของคุณในบล็อกตัววนซ้ำจะไม่ถูกดำเนินการจนกว่าจะenumerator.MoveNext()
ดำเนินการเป็นครั้งแรก นี่คือวิธีการดำเนินการรอการตัดบัญชี นี่คือตัวอย่าง (ค่อนข้างโง่):
var evenNumbers = IteratorBlock().Where(i => i%2 == 0);
ณ จุดนี้ตัววนซ้ำไม่ได้ดำเนินการ ส่วนWhere
คำสั่งสร้างใหม่IEnumerable<T>
ที่ล้อมรอบIEnumerable<T>
กลับโดยIteratorBlock
แต่นับนี้ยังไม่ได้ระบุ สิ่งนี้จะเกิดขึ้นเมื่อคุณเรียกใช้งานforeach
ลูป:
foreach (var evenNumber in evenNumbers)
Console.WriteLine(eventNumber);
หากคุณระบุจำนวนที่นับได้สองครั้งอินสแตนซ์ใหม่ของเครื่องสถานะจะถูกสร้างขึ้นในแต่ละครั้งและบล็อกตัววนซ้ำของคุณจะเรียกใช้รหัสเดียวกันสองครั้ง
แจ้งให้ทราบว่าวิธีการ LINQ ชอบToList()
, ToArray()
, First()
, Count()
ฯลฯ จะใช้foreach
ห่วงระบุนับ ตัวอย่างเช่นToList()
จะระบุองค์ประกอบทั้งหมดของการแจกแจงและเก็บไว้ในรายการ ตอนนี้คุณสามารถเข้าถึงรายการเพื่อรับอิลิเมนต์ทั้งหมดของ enumerable ได้โดยไม่ต้องมีตัววนซ้ำบล็อกดำเนินการอีกครั้ง มีการออกระหว่างการใช้ CPU ToList()
ในการผลิตองค์ประกอบของนับหลายครั้งและหน่วยความจำในการจัดเก็บองค์ประกอบของการแจงนับที่จะเข้าถึงพวกเขาหลายครั้งเมื่อมีการใช้วิธีการเช่นเป็น