เหตุใดจึงต้องใช้คำหลักผลตอบแทนเมื่อฉันสามารถใช้ IEnumerable ธรรมดาได้


171

รับรหัสนี้:

IEnumerable<object> FilteredList()
{
    foreach( object item in FullList )
    {
        if( IsItemInPartialList( item ) )
            yield return item;
    }
}

ทำไมฉันไม่ควรเขียนโค้ดด้วยวิธีนี้:

IEnumerable<object> FilteredList()
{
    var list = new List<object>(); 
    foreach( object item in FullList )
    {
        if( IsItemInPartialList( item ) )
            list.Add(item);
    }
    return list;
}

ฉันเข้าใจว่าyieldคำหลักทำอะไร มันบอกให้คอมไพเลอร์สร้างสิ่งที่แน่นอน (ตัววนซ้ำ) แต่ทำไมต้องใช้มัน? นอกจากจะเป็นรหัสน้อยกว่าแล้วมันจะทำยังไงให้ฉัน


28
ฉันรู้ว่านี่เป็นเพียงตัวอย่าง แต่จริงๆแล้วรหัสควรเขียนดังนี้: FullList.Where(IsItemInPartialList):)
BlueRaja - Danny Pflughoeft

คำตอบ:


241

การใช้yieldทำให้คอลเลกชันขี้เกียจ

สมมติว่าคุณต้องการห้ารายการแรก วิธีของคุณฉันต้องวนซ้ำทั้งรายการเพื่อรับห้ารายการแรก ด้วยyieldฉันวนซ้ำห้ารายการแรกเท่านั้น


14
โปรดทราบว่าการใช้FullList.Where(IsItemInPartialList)จะขี้เกียจ เพียง แต่มันต้องการคอมไพเลอร์น้อยมากที่สร้างโค้ดแบบกำหนดเอง --- gunk --- และเวลานักพัฒนาน้อยลงในการเขียนและบำรุงรักษา (แน่นอนว่าเป็นเพียงตัวอย่างนี้)
sehe

4
นั่นคือ Linq ใช่ไหม ฉันคิดว่า Linq ทำสิ่งที่คล้ายกันมากภายใต้ฝาครอบ
Robert Harvey

1
ใช่ Linq ใช้การดำเนินการล่าช้า ( yield return) หากเป็นไปได้
ชาด Schouggins

11
อย่าลืมว่าหากคำสั่งการส่งคืนไม่ได้ดำเนินการคุณยังคงได้รับผลการรวบรวมที่ว่างเปล่าดังนั้นคุณไม่จำเป็นต้องกังวลเกี่ยวกับข้อยกเว้นการอ้างอิงแบบ null ให้ผลตอบแทนที่ยอดเยี่ยมด้วยช็อคโกแลตโรย
มีดโกน

127

ประโยชน์ของตัววนซ้ำนั้นก็คือมันทำงานได้อย่างเกียจคร้าน ดังนั้นคุณสามารถเขียนวิธีการกรองเช่นนี้:

public static IEnumerable<T> Where<T>(this IEnumerable<T> source,
                                   Func<T, bool> predicate)
{
    foreach (var item in source)
    {
        if (predicate(item))
        {
            yield return item;
        }
    }
}

ซึ่งจะช่วยให้คุณสามารถกรองสตรีมได้นานเท่าที่คุณต้องการไม่เคยบัฟเฟอร์มากกว่าหนึ่งรายการในแต่ละครั้ง หากคุณต้องการเพียงค่าแรกจากลำดับที่ส่งคืนเช่นทำไมคุณต้องการคัดลอกทุกอย่างลงในรายการใหม่

เป็นอีกตัวอย่างหนึ่งคุณสามารถสร้างกระแสข้อมูลไม่สิ้นสุดได้อย่างง่ายดายโดยใช้ตัววนซ้ำตัวบล็อก ตัวอย่างเช่นนี่คือลำดับของตัวเลขสุ่ม:

public static IEnumerable<int> RandomSequence(int minInclusive, int maxExclusive)
{
    Random rng = new Random();
    while (true)
    {
        yield return rng.Next(minInclusive, maxExclusive);
    }
}

คุณจะจัดเก็บลำดับไม่สิ้นสุดในรายการได้อย่างไร

ฉันชุดบล็อก Edulinqจะช่วยให้การดำเนินงานที่เป็นตัวอย่างของ LINQ กับวัตถุซึ่งจะทำให้หนักใช้บล็อก iterator LINQ เป็นคนขี้เกียจโดยพื้นฐานที่สามารถ - และวางสิ่งต่าง ๆ ลงในรายการก็ไม่ได้ผล


1
ฉันไม่แน่ใจว่าจะชอบคุณRandomSequenceหรือไม่ สำหรับฉันแล้ว IEnumerable หมายถึง - ครั้งแรกและสำคัญที่สุดที่ฉันสามารถทำซ้ำกับ foreach ได้ แต่สิ่งนี้จะนำไปสู่การวนซ้ำไม่สิ้นสุดที่นี่ ฉันคิดว่านี่เป็นแนวคิดในการใช้ IEnumerable ที่เป็นอันตราย แต่ YMMV ในทางที่ผิด
Sebastian Negraszus

5
@SebastianNegraszus: ลำดับของตัวเลขสุ่มไม่มีที่สิ้นสุดเชิงตรรกะ IEnumerable<BigInteger>ตัวอย่างเช่นคุณสามารถสร้างลำดับ Fibonacci ที่เป็นตัวแทนได้อย่างง่ายดาย คุณสามารถใช้foreachกับมัน แต่ไม่มีอะไรเกี่ยวกับIEnumerable<T>การรับประกันว่ามันจะ จำกัด
Jon Skeet

42

ด้วยรหัส "รายการ" คุณจะต้องดำเนินการรายการเต็มก่อนที่จะส่งต่อไปยังขั้นตอนถัดไป เวอร์ชัน "อัตราผลตอบแทน" ผ่านรายการที่ประมวลผลทันทีไปยังขั้นตอนถัดไป หาก "ขั้นตอนต่อไป" มี ".Take (10)" จากนั้นรุ่น "yield" จะประมวลผล 10 รายการแรกเท่านั้นและลืมส่วนที่เหลือ รหัส "รายการ" จะประมวลผลทุกอย่างแล้ว

ซึ่งหมายความว่าคุณเห็นความแตกต่างมากที่สุดเมื่อคุณต้องทำการประมวลผลจำนวนมากและ / หรือมีรายการที่ต้องดำเนินการจำนวนมาก


23

คุณสามารถใช้yieldเพื่อส่งคืนรายการที่ไม่ได้อยู่ในรายการ นี่คือตัวอย่างเล็ก ๆ น้อย ๆ ที่สามารถวนซ้ำในรายการจนถูกยกเลิก

public IEnumerable<int> GetNextNumber()
{
    while (true)
    {
        for (int i = 0; i < 10; i++)
        {
            yield return i;
        }
    }
}

public bool Canceled { get; set; }

public void StartCounting()
{
    foreach (var number in GetNextNumber())
    {
        if (this.Canceled) break;
        Console.WriteLine(number);
    }
}

สิ่งนี้เขียน

0
1
2
3
4
5
6
7
8
9
0
1
2
3
4

... ฯลฯ ไปที่คอนโซลจนกว่าจะถูกยกเลิก


10
object jamesItem = null;
foreach(var item in FilteredList())
{
   if (item.Name == "James")
   {
       jamesItem = item;
       break;
   }
}
return jamesItem;

เมื่อโค้ดด้านบนถูกใช้เพื่อวนลูปผ่าน FilteredList () และสมมติว่า item.Name == "James" จะได้รับความพึงพอใจในรายการที่ 2 ในรายการวิธีการที่ใช้ yieldจะให้ผลสองครั้ง นี่เป็นพฤติกรรมขี้เกียจ

โดยที่รายการที่ใช้วิธีการจะเพิ่มวัตถุ n ทั้งหมดลงในรายการและส่งรายการทั้งหมดไปยังวิธีการโทร

นี่เป็นกรณีการใช้งานที่สามารถเน้นความแตกต่างระหว่าง IEnumerable และ IList


7

ตัวอย่างโลกที่ดีที่สุดที่ฉันเคยเห็นเพื่อการใช้งาน yieldการคำนวณลำดับฟีโบนักชี

พิจารณารหัสต่อไปนี้:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(string.Join(", ", Fibonacci().Take(10)));
        Console.WriteLine(string.Join(", ", Fibonacci().Skip(15).Take(1)));
        Console.WriteLine(string.Join(", ", Fibonacci().Skip(10).Take(5)));
        Console.WriteLine(string.Join(", ", Fibonacci().Skip(100).Take(1)));
        Console.ReadKey();
    }

    private static IEnumerable<long> Fibonacci()
    {
        long a = 0;
        long b = 1;

        while (true)
        {
            long temp = a;
            a = b;

            yield return a;

            b = temp + b;
        }
    }
}

สิ่งนี้จะส่งคืน:

1, 1, 2, 3, 5, 8, 13, 21, 34, 55
987
89, 144, 233, 377, 610
1298777728820984005

นี่เป็นสิ่งที่ดีเพราะมันจะช่วยให้คุณสามารถคำนวณอนุกรมที่ไม่มีที่สิ้นสุดอย่างรวดเร็วและง่ายดายทำให้คุณสามารถใช้ส่วนขยาย Linq และค้นหาเฉพาะสิ่งที่คุณต้องการ


5
ฉันไม่เห็นอะไร "โลกแห่งความจริง" ในการคำนวณลำดับฟีโบนักชี
Nek

ฉันยอมรับว่านี่ไม่ใช่ "โลกแห่งความจริง" แต่เป็นแนวคิดที่ยอดเยี่ยม
Casey

1

เหตุใดจึงใช้ [ผลผลิต] นอกจากจะเป็นรหัสน้อยกว่าแล้วมันจะทำยังไงให้ฉัน

บางครั้งมันมีประโยชน์บางครั้งไม่ หากชุดข้อมูลทั้งหมดต้องถูกตรวจสอบและส่งคืนจะไม่มีประโยชน์ในการใช้ผลตอบแทนเพราะสิ่งนั้นได้ถูกนำมาใช้

เมื่ออัตราผลตอบแทนที่แท้จริงคือเมื่อคืนชุดบางส่วนเท่านั้น ฉันคิดว่าตัวอย่างที่ดีที่สุดคือการเรียงลำดับ สมมติว่าคุณมีรายการของวัตถุที่มีวันที่และจำนวนเงินดอลล่าร์จากปีนี้และคุณต้องการดูเรกคอร์ด (5) รายการแรกของปี

เพื่อให้บรรลุสิ่งนี้รายการจะต้องเรียงลำดับจากน้อยไปมากตามวันที่และจากนั้นจึงนำ 5 อันดับแรกมาใช้ หากสิ่งนี้ทำโดยไม่มีผลตอบแทนทั้งหมดรายการจะต้องถูกจัดเรียงขวาเพื่อให้แน่ใจว่าวันที่สองครั้งล่าสุดอยู่ในลำดับ

อย่างไรก็ตามด้วยผลผลิตเมื่อมีการสร้าง 5 รายการแรกแล้วการหยุดการเรียงลำดับและผลลัพธ์จะพร้อมใช้งาน สิ่งนี้สามารถประหยัดเวลาได้มาก


0

ข้อความสั่งคืนสินค้าให้คุณส่งคืนสินค้าได้ครั้งละหนึ่งรายการเท่านั้น คุณกำลังรวบรวมรายการทั้งหมดในรายการและส่งคืนรายการนั้นอีกครั้งซึ่งเป็นค่าใช้จ่ายหน่วยความจำ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.