Apa yang “hasil istirahat;” lakukan di C #?

494

Saya telah melihat sintaks ini di MSDN:, yield breaktapi saya tidak tahu apa fungsinya. Apakah ada yang tahu?

skb
sumber
26
Namun, dokumentasi MSDN menyebutkannya tetapi tidak menjelaskannya. Itu adalah cara yang buruk untuk menggoda pengembang yang haus pengetahuan.
Phil
3
Hasil pengembalian menghilangkan kebutuhan untuk daftar dukungan, yaitu Anda tidak perlu kode sesuatu seperti MyList.Add(...)lakukan saja yield return .... Jika Anda perlu keluar dari lingkaran sebelum waktunya dan mengembalikan daftar dukungan virtual yang Anda gunakanyield break;
The Muffin Man

Jawaban:

529

Ini menentukan bahwa sebuah iterator telah berakhir. Anda dapat menganggapnya yield breaksebagai returnpernyataan yang tidak mengembalikan nilai.

Misalnya, jika Anda mendefinisikan suatu fungsi sebagai iterator, badan fungsi tersebut mungkin terlihat seperti ini:

for (int i = 0; i < 5; i++)
{
    yield return i;
}

Console.Out.WriteLine("You will see me");

Perhatikan bahwa setelah loop telah menyelesaikan semua siklusnya, baris terakhir dijalankan dan Anda akan melihat pesan di aplikasi konsol Anda.

Atau seperti ini dengan yield break:

int i = 0;
while (true)
{
    if (i < 5)
    {
        yield return i;
    }
    else
    {
        // note that i++ will not be executed after this
        yield break;
    }
    i++;
}

Console.Out.WriteLine("Won't see me");

Dalam hal ini pernyataan terakhir tidak pernah dieksekusi karena kami meninggalkan fungsinya lebih awal.

Damir Zekić
sumber
8
Mungkinkah itu sekadar breakbukan yield breakdalam contoh Anda di atas? Compiler tidak mengeluh tentang itu.
orad
85
@orad, sederhana breakdalam hal ini akan menghentikan loop, tetapi tidak akan membatalkan eksekusi metode, sehingga baris terakhir akan dieksekusi dan "Tidak akan melihat saya teks" akan benar-benar terlihat.
Damir Zekić
5
@Damir Zekić Bisakah Anda juga menambah jawaban Anda mengapa Anda harus lebih suka hasil break over return dan apa perbedaan dari keduanya?
Bruno Costa
11
@ DamirZekić mengembalikan nol dan hasil istirahat tidak sama. Jika Anda mengembalikan nol maka Anda bisa NullReferenceExceptionmendapatkan enumerator dari IEnumerable, sementara dengan hasil istirahat Anda tidak (ada contoh tanpa elemen).
Bruno Costa
6
@BrunoCosta Mencoba untuk memiliki pengembalian reguler dalam metode yang sama memberikan kesalahan pada kompiler. Menggunakan yield return xlansiran kompiler yang Anda inginkan metode ini menjadi gula sintaksis untuk membuat Enumeratorobjek. Enumerator ini memiliki metode MoveNext()dan properti Current. MoveNext () mengeksekusi metode sampai yield returnpernyataan, dan mengubah nilai itu menjadi Current. Waktu berikutnya MoveNext dipanggil, eksekusi berlanjut dari sana. yield breakset Current ke null, menandakan akhir enumerator ini, sehingga foreach (var x in myEnum())akan berakhir.
Wolfzoon
57

Mengakhiri blok iterator (mis. Mengatakan tidak ada lagi elemen dalam IEnumerable).

Brian
sumber
2
dengan [+1] - meskipun secara akademis tidak ada iterator di .NET. hanya enumerator (satu arah, maju.)
Shaun Wilson
@Shaun Wilson, tetapi dengan yieldkata kunci Anda dapat mengulangi koleksi di kedua arah, apalagi Anda dapat mengambil setiap elemen koleksi berikutnya tidak berturut
monstr
@monstr Anda dapat mengulang koleksi dengan cara apa pun dan Anda tidak perlu yieldmelakukannya. Saya tidak mengatakan Anda salah, saya mengerti apa yang Anda sarankan; tetapi, secara akademis tidak ada iterator di .NET, hanya enumerator (satu arah, maju) - tidak seperti stdc ++ tidak ada "kerangka kerja iterator generik" yang didefinisikan dalam CTS / CLR. LINQ membantu menutup kesenjangan dengan metode ekstensi yang memanfaatkan yield return dan juga metode panggilan balik , tetapi mereka adalah metode ekstensi kelas satu, bukan iterator kelas satu. hasilnya IEnumerabletidak dapat dengan sendirinya beralih ke arah lain selain meneruskan panggilan.
Shaun Wilson
29

Memberitahu iterator bahwa itu telah mencapai akhir.

Sebagai contoh:

public interface INode
{
    IEnumerable<Node> GetChildren();
}

public class NodeWithTenChildren : INode
{
    private Node[] m_children = new Node[10];

    public IEnumerable<Node> GetChildren()
    {
        for( int n = 0; n < 10; ++n )
        {
            yield return m_children[ n ];
        }
    }
}

public class NodeWithNoChildren : INode
{
    public IEnumerable<Node> GetChildren()
    {
        yield break;
    }
}
Perangkap
sumber
21

yieldpada dasarnya membuat IEnumerable<T>metode berperilaku mirip dengan utas terjadwal kooperatif (bukan preemptively).

yield returnseperti utas memanggil fungsi "jadwal" atau "tidur" untuk melepaskan kendali CPU. Sama seperti utas, IEnumerable<T>metode ini mendapatkan kembali kontrol pada titik segera sesudahnya, dengan semua variabel lokal memiliki nilai yang sama seperti sebelumnya sebelum kontrol dilepaskan.

yield break seperti sebuah utas yang mencapai akhir fungsinya dan berakhir.

Orang berbicara tentang "mesin negara", tetapi mesin negara adalah semua "utas" sebenarnya. Sebuah thread memiliki beberapa status (yaitu, nilai variabel lokal), dan setiap kali dijadwalkan, diperlukan beberapa tindakan untuk mencapai status baru. Poin utama tentang itu yieldadalah, tidak seperti utas sistem operasi yang biasa kita gunakan, kode yang menggunakannya membeku dalam waktu hingga iterasi secara manual dimajukan atau diakhiri.


sumber
13

Seluruh subjek blok iterator tercakup dengan baik dalam bab sampel gratis ini dari buku Jon Skeet C # in Depth .

Marc Gravell
sumber
Anda baru saja menjual kepada saya salinannya. Terima kasih! :-)
Jon Schneider
9

The yield breakpernyataan menyebabkan pencacahan berhenti. Akibatnya, yield breakmenyelesaikan enumerasi tanpa mengembalikan item tambahan.

Pertimbangkan bahwa sebenarnya ada dua cara agar metode iterator dapat menghentikan iterasi. Dalam satu kasus, logika metode secara alami dapat keluar dari metode setelah mengembalikan semua item. Berikut ini sebuah contoh:

IEnumerable<uint> FindPrimes(uint startAt, uint maxCount)
{
    for (var i = 0UL; i < maxCount; i++)
    {
        startAt = NextPrime(startAt);
        yield return startAt;
    }

    Debug.WriteLine("All the primes were found.");
}

Dalam contoh di atas, metode iterator secara alami akan berhenti mengeksekusi setelah maxCountbilangan prima ditemukan.

The yield breakPernyataan adalah cara lain untuk iterator untuk berhenti pencacahan. Ini adalah cara untuk keluar dari pencacahan lebih awal. Berikut adalah metode yang sama seperti di atas. Kali ini, metode ini memiliki batasan jumlah waktu yang dapat dieksekusi oleh metode tersebut.

IEnumerable<uint> FindPrimes(uint startAt, uint maxCount, int maxMinutes)
{
    var sw = System.Diagnostics.Stopwatch.StartNew();
    for (var i = 0UL; i < maxCount; i++)
    {
        startAt = NextPrime(startAt);
        yield return startAt;

        if (sw.Elapsed.TotalMinutes > maxMinutes)
            yield break;
    }

    Debug.WriteLine("All the primes were found.");
}

Perhatikan panggilan untuk yield break. Akibatnya, ia keluar dari enumerasi awal.

Perhatikan juga bahwa yield breakkerjanya berbeda dari sekadar dataran break. Pada contoh di atas, yield breakkeluar dari metode tanpa membuat panggilan ke Debug.WriteLine(..).

Wallace Kelly
sumber
7

Di sini http://www.alteridem.net/2007/08/22/the-yield-statement-in-c/ adalah contoh yang sangat baik:

public static IEnumerable <int> Range (int min, int max)
{
   sementara (benar)
   {
      jika (min> = maks)
      {
         istirahat hasil;
      }
      hasil pengembalian min ++;
   }
}

dan penjelasan, bahwa jika suatu yield breakpernyataan mengenai suatu metode, eksekusi metode itu berhenti tanpa hasil. Ada beberapa situasi waktu, ketika Anda tidak ingin memberikan hasil apa pun, maka Anda dapat menggunakan hasil istirahat.

Towhid
sumber
5

yield break hanyalah cara untuk mengatakan pengembalian untuk yang terakhir kalinya dan tidak mengembalikan nilai apa pun

misalnya

// returns 1,2,3,4,5
IEnumerable<int> CountToFive()
{
    yield return 1;
    yield return 2;
    yield return 3;
    yield return 4;
    yield return 5;
    yield break;
    yield return 6;
    yield return 7;
    yield return 8;
    yield return 9;
 }
John ClearZ
sumber
4

Jika apa yang Anda maksud dengan "apa yang benar-benar dilakukan penghancuran hasil", adalah "bagaimana cara kerjanya" - Lihat blog Raymond Chen untuk detail https://devblogs.microsoft.com/oldnewthing/20080812-00/?p=21273

C # iterators menghasilkan beberapa kode yang sangat rumit.

Tony Lee
sumber
12
Ya, mereka hanya rumit jika Anda peduli dengan kode yang dikompilasi. Kode yang menggunakannya cukup sederhana.
Robert Rossney
@ Robert, itulah yang saya maksud jadi saya telah memperbarui jawaban untuk memasukkan komentar Anda
Tony Lee
-2

Kata kunci hasil digunakan bersama dengan kata kunci kembali untuk memberikan nilai pada objek enumerator. hasil pengembalian menentukan nilai, atau nilai, dikembalikan. Ketika pernyataan pengembalian hasil tercapai, lokasi saat ini disimpan. Eksekusi dimulai kembali dari lokasi ini pada saat iterator dipanggil.

Untuk menjelaskan artinya menggunakan contoh:

    public IEnumerable<int> SampleNumbers()
    {
        int counter = 0;
        yield return counter;

        counter = counter + 2;

        yield return counter;

        counter = counter + 3;

        yield return counter ;
    }

Nilai yang dikembalikan saat ini diulang adalah: 0, 2, 5.

Penting untuk dicatat bahwa variabel penghitung dalam contoh ini adalah variabel lokal. Setelah iterasi kedua yang mengembalikan nilai 2, iterasi ketiga dimulai dari tempat sebelumnya, sambil mempertahankan nilai sebelumnya dari variabel lokal bernama penghitung yang 2.

Eranga Dissanayaka
sumber
11
Anda tidak menjelaskan apa yang yield breakterjadi
Jay Sullivan
Saya tidak berpikir yield returnsebenarnya mendukung pengembalian beberapa nilai. Mungkin bukan itu yang sebenarnya Anda maksudkan, tapi begitulah cara saya membacanya.
Sam
Sam - metode SampleNumbers dengan pernyataan pengembalian banyak hasil tidak benar-benar berfungsi, nilai iterator segera dikembalikan dan eksekusi dilanjutkan ketika nilai berikutnya diminta. Saya telah melihat orang mengakhiri metode seperti ini dengan "hasil istirahat", tetapi itu tidak perlu. Memukul akhir metode juga mengakhiri iterator
Loren Paulsen
alasan mengapa ini adalah contoh yang buruk yield breakadalah karena ia tidak mengandung enumerator tingkat bahasa seperti a foreach- saat menggunakan enumerator, yield breaknilai sebenarnya yang diberikan. contoh ini terlihat seperti loop yang tidak terbuka. Anda hampir tidak akan pernah melihat kode ini di dunia nyata (kita semua dapat memikirkan beberapa kasus tepi, tentu saja) juga, tidak ada "iterator" di sini. "blok iterator" tidak dapat melampaui metode sebagai masalah spesifikasi bahasa. apa yang sebenarnya dikembalikan adalah "enumerable", lihat juga: stackoverflow.com/questions/742497/…
Shaun Wilson