คำหลักผลตอบแทนที่ใช้ใน C # คืออะไร?


828

ในวิธีที่ฉันสามารถเปิดเผยเพียงส่วนของ IList <>คำถามหนึ่งในคำตอบที่มีตัวอย่างรหัสต่อไปนี้:

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

คำหลักผลตอบแทนทำอะไรที่นั่น? ฉันเคยเห็นมันถูกอ้างถึงในที่แห่งหนึ่งและอีกคำถามหนึ่ง แต่ฉันไม่ได้คิดออกมาเลยว่ามันทำอะไร ฉันเคยคิดถึงการให้ผลผลิตในแง่ของการที่เธรดหนึ่งให้ผลผลิตต่อด้ายอื่น แต่ดูเหมือนจะไม่เกี่ยวข้องกันที่นี่


เพียงแค่ลิงค์ MSDN เกี่ยวกับเรื่องนี้อยู่ที่นี่msdn.microsoft.com/en-us/library/vstudio/9k7k7cf0.aspx
นักพัฒนา

14
มันไม่น่าแปลกใจ ความสับสนนั้นมาจากข้อเท็จจริงที่ว่าเรามีเงื่อนไขที่จะเห็น "return" เป็นฟังก์ชันเอาต์พุตในขณะที่นำหน้าด้วย "yield" ไม่ใช่
ลาร์รี

4
ฉันอ่านเอกสารแล้ว แต่ฉันเกรงว่าฉันยังไม่เข้าใจ :(
Ortund

คำตอบ:


737

yieldคำหลักที่ไม่จริงค่อนข้างมากที่นี่

ฟังก์ชั่นส่งคืนวัตถุที่ใช้IEnumerable<object>อินเตอร์เฟซ หากฟังก์ชั่นการโทรเริ่มขึ้นforeachเหนือวัตถุนี้ฟังก์ชั่นจะถูกเรียกอีกครั้งจนกว่ามันจะ "ให้ผล" นี้เป็นน้ำตาลประโยคแนะนำในC # 2.0 ในรุ่นก่อนหน้าคุณต้องสร้างของคุณเองIEnumerableและIEnumeratorวัตถุที่จะทำสิ่งนี้

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

public void Consumer()
{
    foreach(int i in Integers())
    {
        Console.WriteLine(i.ToString());
    }
}

public IEnumerable<int> Integers()
{
    yield return 1;
    yield return 2;
    yield return 4;
    yield return 8;
    yield return 16;
    yield return 16777216;
}

เมื่อคุณก้าวผ่านตัวอย่างคุณจะได้พบกับสายแรกที่ผลตอบแทนIntegers() 1การโทรครั้งที่สองจะส่งคืน2และสายyield return 1จะไม่ถูกเรียกใช้อีก

นี่คือตัวอย่างในชีวิตจริง:

public IEnumerable<T> Read<T>(string sql, Func<IDataReader, T> make, params object[] parms)
{
    using (var connection = CreateConnection())
    {
        using (var command = CreateCommand(CommandType.Text, sql, connection, parms))
        {
            command.CommandTimeout = dataBaseSettings.ReadCommandTimeout;
            using (var reader = command.ExecuteReader())
            {
                while (reader.Read())
                {
                    yield return make(reader);
                }
            }
        }
    }
}

113
ในกรณีนี้จะง่ายขึ้นฉันแค่ใช้จำนวนเต็มที่นี่เพื่อแสดงว่าการคืนผลตอบแทนเป็นอย่างไร สิ่งที่ดีเกี่ยวกับการใช้ผลตอบแทนคือมันเป็นวิธีที่รวดเร็วในการนำรูปแบบตัววนซ้ำมาใช้ดังนั้นสิ่งต่างๆจึงถูกประเมินอย่างขี้เกียจ
Mendelt

111
นอกจากนี้ยังมีข้อสังเกตว่าคุณสามารถใช้yield break;เมื่อคุณไม่ต้องการส่งคืนรายการใด ๆ เพิ่มเติม
โรรี่

7
yieldไม่ใช่คำหลัก ถ้าเป็นเช่นนั้นฉันไม่สามารถใช้ผลตอบแทนเป็นตัวบ่งชี้เช่นเดียวกับในint yield = 500;
Brandin

5
@Brandin นั่นเป็นเพราะภาษาการเขียนโปรแกรมทั้งหมดสนับสนุนคำหลักสองประเภทคือสงวนและตามบริบท ผลผลิตตกอยู่ในหมวดหมู่ภายหลังซึ่งเป็นสาเหตุที่โค้ดของคุณไม่ถูกห้ามโดยคอมไพเลอร์ C # รายละเอียดเพิ่มเติมได้ที่นี่: ericlippert.com/2009/05/11/reserved-and-contextual-keywordsคุณจะต้องตื่นเต้นที่จะรู้ว่ามีคำที่สงวนไว้ซึ่งไม่ได้รับการจดจำเป็นคำหลักด้วยภาษา สำหรับเช่น goto ใน java รายละเอียดเพิ่มเติมได้ที่นี่: stackoverflow.com/questions/2545103/…
RBT

7
'If a calling function starts foreach-ing over this object the function is called again until it "yields"'. ไม่ถูกต้องสำหรับฉัน ฉันมักจะนึกถึงคำสำคัญที่ให้ผลตอบแทนในบริบทของ "การเก็บเกี่ยวให้ผลผลิตที่อุดมสมบูรณ์" แทนที่จะเป็น "รถให้ผลผลิตแก่คนเดินเท้า"
แซค

369

การย้ำ มันสร้างเครื่องรัฐ "ภายใต้ฝาครอบ" ที่จดจำตำแหน่งที่คุณอยู่ในแต่ละรอบเพิ่มเติมของฟังก์ชั่นและรับจากที่นั่น


210

ผลผลิตมีประโยชน์สองอย่าง

  1. ช่วยในการกำหนดซ้ำซ้ำโดยไม่สร้างคอลเลกชันชั่วคราว

  2. มันช่วยในการย้ำซ้ำซาก ป้อนคำอธิบายรูปภาพที่นี่

เพื่ออธิบายสองจุดข้างบนให้เห็นได้ชัดขึ้นฉันได้สร้างวิดีโอง่าย ๆ ที่คุณสามารถดูได้ที่นี่


13
yieldวิดีโอที่ช่วยเหลือผมที่จะเข้าใจอย่างชัดเจน @ Project project ของ ShivprasadKoirala การใช้ C # Yield คืออะไร? คำอธิบายเดียวกันนี้ยังเป็นแหล่งข้อมูลที่ดีด้วย
Dush

ฉันจะเพิ่มเป็นจุดที่สามที่yieldเป็นวิธี "เร็ว" เพื่อสร้าง IEnumerator แบบกำหนดเอง (แทนที่จะมีคลาสที่ใช้อินเทอร์เฟซ IEnumerator)
MrTourkos

ฉันดูวิดีโอของคุณ Shivprasad และอธิบายอย่างชัดเจนถึงการใช้คำหลักผลตอบแทน
Tore Aurstad

ขอบคุณสำหรับวิดีโอ! อธิบายได้ดีมาก!
Roblogic

วิดีโอยอดเยี่ยม แต่สงสัยว่า ... การใช้งานโดยใช้ผลตอบแทนนั้นชัดเจนกว่า แต่ก็ต้องสร้างหน่วยความจำชั่วคราวของตนเองหรือ / และรายการภายในเพื่อติดตามสถานะ (หรือสร้างเครื่องรัฐ) ดังนั้น "ผลตอบแทน" คือการทำสิ่งอื่นนอกเหนือจากการทำให้การใช้งานง่ายขึ้นและทำให้สิ่งต่าง ๆ ดูดีขึ้นหรือมีอะไรอีกบ้าง วิธีการเกี่ยวกับประสิทธิภาพการเรียกใช้รหัสโดยใช้ Yield มีประสิทธิภาพมากขึ้น / น้อยลง / เร็วขึ้นกว่าที่ไม่มี?
toughQuestions

135

เมื่อเร็ว ๆ นี้เรย์มอนด์เฉินยังได้จัดทำบทความที่น่าสนใจเกี่ยวกับคำค้นหา

ในขณะที่มันถูกใช้ในนามสำหรับการใช้รูปแบบตัววนซ้ำได้ง่าย แต่สามารถนำไปวางในเครื่องรัฐ ไม่มีประเด็นในการอ้างอิง Raymond ส่วนสุดท้ายยังเชื่อมโยงไปยังการใช้งานอื่น ๆ (แต่ตัวอย่างในบล็อกของ Entin นั้นดีมากโดยแสดงวิธีเขียนรหัสที่ปลอดภัยแบบ async)


สิ่งนี้ต้องได้รับการโหวต น่ายินดีที่เขาอธิบายวัตถุประสงค์ของผู้ปฏิบัติงานและภายใน
sajidnizami

3
ตอนที่ 1 อธิบายถึงวากยสัมพันธ์น้ำตาลของ "การคืนผลผลิต" อธิบายที่ยอดเยี่ยม!
Dror Weiss

99

แรกเห็นผลตอบแทนอัตราผลตอบแทนที่เป็น.NETน้ำตาลกลับIEnumerable

หากไม่มีผลผลิตรายการทั้งหมดของคอลเลกชันจะถูกสร้างขึ้นในครั้งเดียว:

class SomeData
{
    public SomeData() { }

    static public IEnumerable<SomeData> CreateSomeDatas()
    {
        return new List<SomeData> {
            new SomeData(), 
            new SomeData(), 
            new SomeData()
        };
    }
}

รหัสเดียวกันโดยใช้ผลตอบแทนมันส่งกลับรายการโดยรายการ

class SomeData
{
    public SomeData() { }

    static public IEnumerable<SomeData> CreateSomeDatas()
    {
        yield return new SomeData();
        yield return new SomeData();
        yield return new SomeData();
    }
}

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

ตัวดำเนินการผลตอบแทนอนุญาตให้สร้างรายการตามที่ต้องการ นั่นเป็นเหตุผลที่ดีที่จะใช้มัน


40

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

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

static void Main(string[] args)
{
    foreach (int fib in Fibs(6))//1, 5
    {
        Console.WriteLine(fib + " ");//4, 10
    }            
}

static IEnumerable<int> Fibs(int fibCount)
{
    for (int i = 0, prevFib = 0, currFib = 1; i < fibCount; i++)//2
    {
        yield return prevFib;//3, 9
        int newFib = prevFib + currFib;//6
        prevFib = currFib;//7
        currFib = newFib;//8
    }
}

นอกจากนี้สถานะยังคงอยู่สำหรับการแจงนับแต่ละครั้ง สมมติว่าฉันมีFibs()วิธีการโทรอีกวิธีหนึ่งแล้วสถานะจะถูกรีเซ็ต


2
ตั้ง prevFib = 1 - หมายเลข Fibonacci แรกคือ "1" ไม่ใช่ "0"
fubo

31

โดยสังเขปคำสำคัญส่งคืนค่าจากฟังก์ชันโดยไม่ทิ้งไว้เช่นในตัวอย่างรหัสของคุณจะคืนitemค่าปัจจุบันแล้วกลับมาวนซ้ำ อีกอย่างเป็นทางการก็จะถูกใช้โดยคอมไพเลอร์เพื่อสร้างรหัสสำหรับiterator Iterator เป็นฟังก์ชันที่คืนค่าIEnumerableวัตถุ MSDNมีหลายบทความเกี่ยวกับพวกเขา


4
เพื่อความแม่นยำมันไม่กลับมาวนซ้ำมันหยุดมันจนกว่าผู้ปกครองเรียกว่า "iterator.next ()"
อเล็กซ์

8
@jitbit นั่นเป็นเหตุผลที่ฉันใช้คำว่า "สังหรณ์ใจ" และ "เป็นทางการมากขึ้น"
Konrad Rudolph

31

รายการหรือการดำเนินการอาร์เรย์โหลดรายการทั้งหมดทันทีในขณะที่การใช้งานผลผลิตให้โซลูชั่นการดำเนินการรอการตัดบัญชี

ในทางปฏิบัติมักจะต้องการทำงานในปริมาณขั้นต่ำตามที่ต้องการเพื่อลดการใช้ทรัพยากรของแอปพลิเคชัน

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

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

นี่คือการเปรียบเทียบระหว่างสร้างคอลเลกชันก่อนเช่นรายการเปรียบเทียบกับการใช้ผลตอบแทน

ตัวอย่างรายการ

    public class ContactListStore : IStore<ContactModel>
    {
        public IEnumerable<ContactModel> GetEnumerator()
        {
            var contacts = new List<ContactModel>();
            Console.WriteLine("ContactListStore: Creating contact 1");
            contacts.Add(new ContactModel() { FirstName = "Bob", LastName = "Blue" });
            Console.WriteLine("ContactListStore: Creating contact 2");
            contacts.Add(new ContactModel() { FirstName = "Jim", LastName = "Green" });
            Console.WriteLine("ContactListStore: Creating contact 3");
            contacts.Add(new ContactModel() { FirstName = "Susan", LastName = "Orange" });
            return contacts;
        }
    }

    static void Main(string[] args)
    {
        var store = new ContactListStore();
        var contacts = store.GetEnumerator();

        Console.WriteLine("Ready to iterate through the collection.");
        Console.ReadLine();
    }

คอนโซลเอาต์พุต
ContactListStore: การสร้างผู้ติดต่อ 1
ContactListStore: การสร้างผู้ติดต่อ 2
ContactListStore: การสร้างผู้ติดต่อ 3
พร้อมที่จะวนซ้ำผ่านคอลเลกชัน

หมายเหตุ: คอลเลกชันทั้งหมดถูกโหลดเข้าสู่หน่วยความจำโดยไม่ต้องขอรายการเดียวในรายการ

ตัวอย่างผลตอบแทน

public class ContactYieldStore : IStore<ContactModel>
{
    public IEnumerable<ContactModel> GetEnumerator()
    {
        Console.WriteLine("ContactYieldStore: Creating contact 1");
        yield return new ContactModel() { FirstName = "Bob", LastName = "Blue" };
        Console.WriteLine("ContactYieldStore: Creating contact 2");
        yield return new ContactModel() { FirstName = "Jim", LastName = "Green" };
        Console.WriteLine("ContactYieldStore: Creating contact 3");
        yield return new ContactModel() { FirstName = "Susan", LastName = "Orange" };
    }
}

static void Main(string[] args)
{
    var store = new ContactYieldStore();
    var contacts = store.GetEnumerator();

    Console.WriteLine("Ready to iterate through the collection.");
    Console.ReadLine();
}

คอนโซลเอาต์พุต
พร้อมที่จะวนซ้ำในคอลเล็กชัน

หมายเหตุ: คอลเลกชันไม่ได้ดำเนินการเลย นี่เป็นเพราะธรรมชาติของ "การดำเนินการรอการตัดบัญชี" ของ IEnumerable การสร้างไอเท็มจะเกิดขึ้นเมื่อจำเป็นจริงๆเท่านั้น

ลองเรียกคอลเล็กชั่นนี้อีกครั้งและอธิบายพฤติกรรมเมื่อเราดึงผู้ติดต่อรายแรกในคอลเลกชัน

static void Main(string[] args)
{
    var store = new ContactYieldStore();
    var contacts = store.GetEnumerator();
    Console.WriteLine("Ready to iterate through the collection");
    Console.WriteLine("Hello {0}", contacts.First().FirstName);
    Console.ReadLine();
}

เอาต์พุตคอนโซล
พร้อมทำซ้ำผ่านคอลเลกชัน
ContactYieldStore: การสร้างผู้ติดต่อ 1
Hello Bob

ดี! มีการสร้างที่ติดต่อครั้งแรกเฉพาะเมื่อลูกค้า "ดึง" รายการออกจากคอลเลกชัน


1
คำตอบนี้ต้องการความสนใจมากขึ้น! ขอบคุณ
leon22

@ leon22 +2
snr

26

นี่เป็นวิธีง่ายๆในการทำความเข้าใจแนวคิด: แนวคิดพื้นฐานคือถ้าคุณต้องการคอลเล็กชันที่คุณสามารถใช้ " foreach" ใน แต่การรวบรวมรายการลงในคอลเลกชันนั้นมีราคาแพงด้วยเหตุผลบางอย่าง (เช่นการสืบค้นจากฐานข้อมูล) และคุณมักจะไม่ต้องการคอลเลกชันทั้งหมดจากนั้นคุณสร้างฟังก์ชั่นที่สร้างคอลเลกชันทีละรายการและให้ผลตอบแทนแก่ผู้บริโภค (ผู้ที่สามารถยุติความพยายามในการรวบรวมก่อน)

ลองวิธีนี้ดูคุณไปที่เคาน์เตอร์ขายเนื้อและต้องการซื้อแฮมหั่นเป็นปอนด์ คนขายเนื้อใช้แฮม 10 ปอนด์ไปทางด้านหลังวางมันลงบนเครื่องสไลซ์แล้วหั่นเป็นชิ้น ๆ ทั้งหมดจากนั้นนำกองสไลซ์กลับมาให้คุณและวัดเป็นปอนด์ (วิธีเก่า) ด้วยyieldเขียงนำเครื่องหั่นไปที่เคาน์เตอร์และเริ่มหั่นและ "ยอม" แต่ละชิ้นลงบนเครื่องชั่งจนกว่าจะวัดได้ 1 ปอนด์จากนั้นห่อให้คุณและคุณเสร็จแล้ว วิธีการแบบเก่าอาจจะดีกว่าสำหรับคนขายเนื้อ (ช่วยให้เขาจัดระเบียบเครื่องจักรของเขาในแบบที่เขาชอบ) แต่วิธีการใหม่นั้นมีประสิทธิภาพชัดเจนกว่าในกรณีส่วนใหญ่สำหรับผู้บริโภค


18

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:

C # iterator แผนภาพลำดับบล็อก

เครื่องสถานะที่สร้างโดยคอมไพเลอร์ยังใช้ตัวแจงนับ แต่เพื่อทำให้ไดอะแกรมชัดเจนยิ่งขึ้นฉันได้แสดงมันเป็นอินสแตนซ์แยกต่างหาก (เมื่อเครื่องสถานะถูกระบุจากเธรดอื่นคุณจะได้รับอินสแตนซ์แยกต่างหาก แต่รายละเอียดนั้นไม่สำคัญที่นี่)

ทุกครั้งที่คุณเรียกใช้ตัววนซ้ำบล็อกอินสแตนซ์ใหม่ของเครื่องสถานะจะถูกสร้างขึ้น อย่างไรก็ตามรหัสของคุณในบล็อกตัววนซ้ำจะไม่ถูกดำเนินการจนกว่าจะ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()ในการผลิตองค์ประกอบของนับหลายครั้งและหน่วยความจำในการจัดเก็บองค์ประกอบของการแจงนับที่จะเข้าถึงพวกเขาหลายครั้งเมื่อมีการใช้วิธีการเช่นเป็น


18

ถ้าฉันเข้าใจสิ่งนี้อย่างถูกต้องนี่คือวิธีที่ฉันจะพูดสิ่งนี้จากมุมมองของฟังก์ชั่นที่ใช้ IEnumerable กับผลผลิต

  • นี่คือหนึ่ง
  • โทรอีกครั้งถ้าคุณต้องการอีก
  • ฉันจะจำสิ่งที่ฉันให้คุณแล้ว
  • ฉันจะรู้ว่าฉันจะให้คุณอีกเมื่อคุณโทรอีกครั้ง

เรียบง่ายและยอดเยี่ยม
แฮร์รี่

10

คีย์เวิร์ด C # ให้ผลอย่างง่ายอนุญาตให้เรียกหลาย ๆ ตัวของโค้ดเรียกว่า iterator ซึ่งรู้วิธีที่จะกลับมาก่อนที่มันจะเสร็จสิ้นและเมื่อเรียกอีกครั้งจะดำเนินการต่อที่ที่เหลือ - เช่นช่วยให้ผู้ทำซ้ำ กลายเป็นสถานะที่โปร่งใสสำหรับแต่ละรายการในลำดับที่ตัววนซ้ำส่งคืนค่าในการโทรติดต่อกัน

ใน JavaScript แนวคิดเดียวกันนี้เรียกว่า Generators


คำอธิบายที่ดีที่สุด เหล่านี้เป็นตัวกำเนิดเดียวกันในไพ ธ อนด้วยหรือไม่
petrosmm

7

มันเป็นวิธีที่ง่ายและสะดวกในการสร้างจำนวนสำหรับวัตถุของคุณ คอมไพเลอร์สร้างคลาสที่ล้อมรอบเมธอดของคุณและใช้ในกรณีนี้ IEnumerable <object> หากไม่มีคีย์เวิร์ดให้ผลตอบแทนคุณจะต้องสร้างวัตถุที่ใช้ IEnumerable <object>


5

มันสร้างลำดับที่นับได้ สิ่งที่ทำคือการสร้างลำดับ IEnumerable ในเครื่องและส่งคืนเป็นผลลัพธ์ของเมธอด


3

ลิงค์นี้มีตัวอย่างง่ายๆ

แม้แต่ตัวอย่างที่ง่ายกว่าก็อยู่ที่นี่

public static IEnumerable<int> testYieldb()
{
    for(int i=0;i<3;i++) yield return 4;
}

โปรดสังเกตว่าการคืนผลตอบแทนจะไม่ส่งคืนจากวิธีการ คุณยังสามารถใส่WriteLineหลังจากyield return

ข้างต้นสร้าง IEnumerable จาก 4 ints 4,4,4,4

WriteLineที่นี่ด้วย จะเพิ่ม 4 ลงในรายการพิมพ์ abc จากนั้นเพิ่ม 4 ลงในรายการจากนั้นดำเนินการตามวิธีการให้เสร็จสิ้นและส่งคืนจากเมธอด (ทันทีที่วิธีการดังกล่าวเสร็จสิ้น แต่นี่จะมีค่าIEnumerableรายการของints ที่จะส่งคืนเมื่อเสร็จสิ้น

public static IEnumerable<int> testYieldb()
{
    yield return 4;
    console.WriteLine("abc");
    yield return 4;
}

โปรดสังเกตด้วยว่าเมื่อคุณใช้อัตราผลตอบแทนสิ่งที่คุณส่งคืนจะไม่เป็นชนิดเดียวกันกับฟังก์ชัน มันเป็นประเภทขององค์ประกอบภายในIEnumerableรายการ

IEnumerableคุณสามารถใช้ผลผลิตที่มีประเภทผลตอบแทนของวิธีการที่เป็น หากประเภทของวิธีการส่งคืนเป็นintหรือList<int>คุณใช้yieldแล้วมันจะไม่รวบรวม คุณสามารถใช้IEnumerableวิธีคืนค่าโดยไม่มีผลตอบแทน แต่ดูเหมือนว่าคุณไม่สามารถใช้ผลตอบแทนหากไม่มีIEnumerableประเภทคืนวิธี

และเพื่อให้มันทำงานคุณต้องเรียกมันด้วยวิธีพิเศษ

static void Main(string[] args)
{
    testA();
    Console.Write("try again. the above won't execute any of the function!\n");

    foreach (var x in testA()) { }


    Console.ReadLine();
}



// static List<int> testA()
static IEnumerable<int> testA()
{
    Console.WriteLine("asdfa");
    yield return 1;
    Console.WriteLine("asdf");
}

หมายเหตุ - หากพยายามทำความเข้าใจ SelectMany มันจะใช้ผลตอบแทนและข้อมูลทั่วไป .. ตัวอย่างนี้อาจช่วยpublic static IEnumerable<TResult> testYieldc<TResult>(TResult t) { yield return t; }และ public static IEnumerable<TResult> testYieldc<TResult>(TResult t) { return new List<TResult>(); }
barlop

ดูเหมือนคำอธิบายที่ดีมาก! นี่อาจเป็นคำตอบที่ยอมรับได้
pongapundit

@ pongapundit ขอบคุณคำตอบของฉันชัดเจนและเรียบง่าย แต่ฉันไม่ได้ใช้ให้ผลผลิตมากตัวเองผู้ตอบคนอื่น ๆ ก็มีประสบการณ์และความรู้เกี่ยวกับการใช้งานมากกว่าที่ฉันทำ สิ่งที่ฉันเขียนเกี่ยวกับการให้ผลตอบแทนที่นี่อาจเกิดจากการเกาหัวของฉันพยายามที่จะหาคำตอบที่นี่และที่ลิงค์ dotnetperls! แต่เนื่องจากฉันไม่รู้yield returnว่าดี (นอกเหนือจากสิ่งเรียบง่ายที่ฉันพูดถึง) และไม่ได้ใช้มันมากและไม่รู้การใช้งานมากนักฉันไม่คิดว่ามันควรจะเป็นที่ยอมรับ
barlop

3

จุดหนึ่งที่สำคัญเกี่ยวกับคำหลักคืออัตราผลตอบแทนการดำเนินการขี้เกียจ ตอนนี้สิ่งที่ฉันหมายถึงโดยการดำเนินการ Lazy คือการดำเนินการเมื่อจำเป็น วิธีที่ดีกว่าที่จะกล่าวถึงก็คือการยกตัวอย่าง

ตัวอย่าง: ไม่ได้ใช้ Yield เช่น No Lazy Execution

        public static IEnumerable<int> CreateCollectionWithList()
        {
            var list =  new List<int>();
            list.Add(10);
            list.Add(0);
            list.Add(1);
            list.Add(2);
            list.Add(20);

            return list;
        }

ตัวอย่าง: การใช้ Yield เช่น Lazy Execution

    public static IEnumerable<int> CreateCollectionWithYield()
    {
        yield return 10;
        for (int i = 0; i < 3; i++) 
        {
            yield return i;
        }

        yield return 20;
    }

ตอนนี้เมื่อฉันโทรทั้งสองวิธี

var listItems = CreateCollectionWithList();
var yieldedItems = CreateCollectionWithYield();

คุณจะสังเกตเห็น listItems จะมี 5 รายการอยู่ข้างใน (เลื่อนเม้าส์ของคุณไปที่ listItems ขณะทำการดีบั๊ก) ในขณะที่ผลิตรายการก็จะมีการอ้างอิงถึงวิธีการและไม่ใช่รายการ นั่นหมายความว่ามันไม่ได้ดำเนินการกระบวนการรับไอเท็มภายในเมธอด วิธีที่มีประสิทธิภาพมากในการรับข้อมูลเมื่อจำเป็นเท่านั้น การใช้งานจริงของผลผลิตสามารถเห็นได้ใน ORM เช่น Entity Framework และ NHibernate เป็นต้น


-3

กำลังพยายามนำ Ruby Goodness :)
แนวคิด:นี่คือตัวอย่างโค้ด Ruby ที่พิมพ์แต่ละองค์ประกอบของอาร์เรย์

 rubyArray = [1,2,3,4,5,6,7,8,9,10]
    rubyArray.each{|x| 
        puts x   # do whatever with x
    }

การใช้งานแต่ละวิธีของ Array ให้ผลควบคุมการโทร ('Put x') กับแต่ละองค์ประกอบของอาร์เรย์ที่แสดงอย่างประณีตเป็น x ผู้เรียกสามารถทำสิ่งที่ต้องการได้ด้วย x

อย่างไรก็ตาม. Netไม่ได้ไปทุกทางที่นี่ .. C # ดูเหมือนจะให้ผลผลิตคู่กับ IEnumerable ในทางที่บังคับให้คุณเขียนลูป foreach ในผู้โทรตามที่เห็นในการตอบสนองของ Mendelt สง่างามน้อยลงเล็กน้อย

//calling code
foreach(int i in obCustomClass.Each())
{
    Console.WriteLine(i.ToString());
}

// CustomClass implementation
private int[] data = {1,2,3,4,5,6,7,8,9,10};
public IEnumerable<int> Each()
{
   for(int iLooper=0; iLooper<data.Length; ++iLooper)
        yield return data[iLooper]; 
}

7
-1 คำตอบนี้ไม่ถูกต้องสำหรับฉัน ใช่ C # yieldเชื่อมโยงกับIEnumerableและ C # ขาดแนวคิด Ruby ของ "บล็อก" แต่ C # มี lambdas ซึ่งสามารถอนุญาตให้มีการใช้ForEachวิธีการeachได้ สิ่งนี้ไม่ได้หมายความว่าควรทำเช่นนั้น
rsenna

ดีกว่า: สาธารณะ IEnumerable <int> แต่ละ () {int index = 0; ข้อมูลส่งคืนผลตอบแทน [ดัชนี ++]; }
ata
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.