Dalam pertanyaan Bagaimana Saya Hanya Mengekspos Bagian dari IList <> salah satu jawaban memiliki potongan kode berikut:
IEnumerable<object> FilteredList()
{
foreach(object item in FullList)
{
if(IsItemInPartialList(item))
yield return item;
}
}
Apa yang dilakukan kata kunci hasil di sana? Saya telah melihatnya direferensikan di beberapa tempat, dan satu pertanyaan lain, tapi saya belum tahu apa yang sebenarnya dilakukannya. Saya terbiasa berpikir tentang hasil dalam arti satu thread menghasilkan yang lain, tetapi itu tampaknya tidak relevan di sini.
Jawaban:
Kata
yield
kunci sebenarnya cukup banyak di sini.Fungsi mengembalikan objek yang mengimplementasikan
IEnumerable<object>
antarmuka. Jika fungsi panggilan mulai diforeach
atas objek ini, fungsi dipanggil lagi sampai "menghasilkan". Ini adalah gula sintaksis yang diperkenalkan dalam C # 2.0 . Di versi sebelumnya Anda harus membuat objek Anda sendiriIEnumerable
danIEnumerator
melakukan hal-hal seperti ini.Cara termudah untuk memahami kode seperti ini adalah mengetikkan contoh, mengatur beberapa breakpoint, dan melihat apa yang terjadi. Cobalah melangkah melalui contoh ini:
Ketika Anda melangkah melalui contoh, Anda akan menemukan panggilan pertama untuk
Integers()
kembali1
. Panggilan kedua kembali2
dan saluranyield return 1
tidak dieksekusi lagi.Ini adalah contoh kehidupan nyata:
sumber
yield break;
ketika Anda tidak ingin mengembalikan barang lagi.yield
bukan kata kunci. Jika demikian maka saya tidak dapat menggunakan hasil sebagai pengidentifikasi seperti dalamint yield = 500;
'If a calling function starts foreach-ing over this object the function is called again until it "yields"'
. tidak terdengar benar bagiku. Saya selalu memikirkan kata kunci c # yield dalam konteks "hasil panen panen berlimpah", bukannya "hasil mobil ke pejalan kaki".Pengulangan. Ini menciptakan mesin negara "di bawah penutup" yang mengingat di mana Anda berada pada setiap siklus fungsi tambahan dan mengambil dari sana.
sumber
Yield memiliki dua kegunaan besar,
Ini membantu untuk menyediakan iterasi khusus tanpa membuat koleksi temp.
Ini membantu untuk melakukan iterasi stateful.
Untuk menjelaskan dua poin di atas secara lebih demonstratif, saya telah membuat video sederhana yang dapat Anda tonton di sini
sumber
yield
. @ Artikel proyek kode kode ShivprasadKoirala Apa gunanya C # Yield? dari penjelasan yang sama juga merupakan sumber yang bagusyield
"cepat" cara untuk membuat IEnumerator kustom (bukan kelas yang mengimplementasikan antarmuka IEnumerator).Baru-baru ini Raymond Chen juga menjalankan serangkaian artikel menarik tentang kata kunci hasil.
Meskipun secara nominal digunakan untuk dengan mudah menerapkan pola iterator, tetapi dapat digeneralisasi menjadi mesin negara. Tidak ada gunanya mengutip Raymond, bagian terakhir juga menghubungkan ke penggunaan lain (tetapi contoh di blog Entin sangat bagus, menunjukkan cara menulis kode aman async).
sumber
Pada pandangan pertama, imbal hasil adalah gula NET untuk mengembalikan IEnumerable .
Tanpa hasil, semua item koleksi dibuat sekaligus:
Kode yang sama menggunakan hasil, itu mengembalikan item demi item:
Keuntungan menggunakan hasil adalah bahwa jika fungsi yang memakan data Anda hanya membutuhkan item pertama dari koleksi, sisa item tidak akan dibuat.
Operator hasil memungkinkan pembuatan item seperti yang diminta. Itu alasan yang bagus untuk menggunakannya.
sumber
yield return
digunakan dengan enumerator. Pada setiap panggilan pernyataan hasil, kontrol dikembalikan ke penelepon tetapi memastikan bahwa kondisi callee dipertahankan. Karena itu, ketika penelepon menyebutkan elemen berikutnya, ia melanjutkan eksekusi dalam metode callee dari pernyataan segera setelahyield
pernyataan.Mari kita coba pahami ini dengan sebuah contoh. Dalam contoh ini, sesuai dengan setiap baris yang saya sebutkan urutan di mana eksekusi mengalir.
Juga, status dipertahankan untuk setiap enumerasi. Misalkan, saya memiliki panggilan lain ke
Fibs()
metode maka negara akan diatur ulang untuk itu.sumber
Secara intuitif, kata kunci mengembalikan nilai dari fungsi tanpa meninggalkannya, yaitu dalam contoh kode Anda, mengembalikan nilai saat ini
item
dan kemudian melanjutkan loop. Lebih formal, ini digunakan oleh kompiler untuk menghasilkan kode untuk iterator . Iterator adalah fungsi yang mengembalikanIEnumerable
objek. The MSDN memiliki beberapa artikel tentang mereka.sumber
Daftar atau implementasi array memuat semua item segera sedangkan implementasi hasil menyediakan solusi eksekusi yang ditangguhkan.
Dalam praktiknya, seringkali diinginkan untuk melakukan jumlah pekerjaan minimum yang diperlukan untuk mengurangi konsumsi sumber daya suatu aplikasi.
Misalnya, kami mungkin memiliki aplikasi yang memproses jutaan catatan dari basis data. Manfaat berikut dapat dicapai ketika kami menggunakan IEnumerable dalam model berbasis tarik eksekusi yang ditangguhkan:
Berikut adalah perbandingan antara membangun koleksi terlebih dahulu seperti daftar dibandingkan dengan menggunakan hasil.
Daftar Contoh
Output Output
ContactListStore: Membuat kontak 1
ContactListStore: Membuat kontak 2
ContactListStore: Membuat kontak 3
Siap untuk beralih melalui koleksi.
Catatan: Seluruh koleksi dimuat ke dalam memori tanpa meminta satu pun item dalam daftar
Contoh Hasil
Output Konsol
Siap untuk beralih melalui koleksi.
Catatan: Koleksi tidak dieksekusi sama sekali. Ini karena sifat "eksekusi yang ditangguhkan" dari IEnumerable. Membangun item hanya akan terjadi ketika itu benar-benar diperlukan.
Mari panggil koleksi lagi dan pantau perilaku ketika kami mengambil kontak pertama dalam koleksi.
Output Konsol
Siap untuk beralih melalui koleksi
ContactYieldStore: Membuat kontak 1
Halo Bob
Bagus! Hanya kontak pertama yang dibangun ketika klien "menarik" item keluar dari koleksi.
sumber
Berikut ini adalah cara sederhana untuk memahami konsep: Gagasan dasarnya adalah, jika Anda menginginkan koleksi yang dapat Anda gunakan "
foreach
", tetapi mengumpulkan barang-barang ke dalam koleksi itu mahal karena beberapa alasan (seperti meminta mereka keluar dari database), DAN Anda sering kali tidak perlu seluruh koleksi, maka Anda membuat fungsi yang membangun koleksi satu item pada suatu waktu dan mengembalikannya ke konsumen (yang kemudian dapat menghentikan upaya pengumpulan awal).Pikirkan seperti ini: Anda pergi ke meja daging dan ingin membeli satu pon ham yang diiris. Tukang daging mengambil ham 10 pon ke belakang, meletakkannya di mesin pengiris, mengiris semuanya, lalu membawa tumpukan irisan kembali ke Anda dan mengukur satu pon itu. (LAMA). Dengan
yield
, tukang daging membawa mesin pengiris ke meja, dan mulai mengiris dan "menghasilkan" masing-masing irisan ke skala sampai ukuran 1 pon, kemudian membungkusnya untuk Anda dan Anda selesai. Jalan Lama mungkin lebih baik bagi tukang daging (memungkinkannya mengatur mesinnya sesuai keinginannya), tetapi Jalan Baru jelas lebih efisien dalam banyak kasus bagi konsumen.sumber
Kata
yield
kunci memungkinkan Anda untuk membuatIEnumerable<T>
formulir di blok iterator . Blok iterator ini mendukung eksekusi yang ditangguhkan dan jika Anda tidak terbiasa dengan konsep itu mungkin tampak hampir ajaib. Namun, pada akhirnya hanya kode yang dijalankan tanpa trik aneh.Blok iterator dapat digambarkan sebagai gula sintaksis di mana kompiler menghasilkan mesin negara yang melacak sejauh mana enumerasi enumerable telah berkembang. Untuk menghitung enumerable, Anda sering menggunakan
foreach
loop. Namun, satuforeach
lingkaran juga merupakan gula sintaksis. Jadi, Anda adalah dua abstraksi yang dihapus dari kode asli yang mengapa awalnya mungkin sulit untuk memahami bagaimana semuanya bekerja bersama.Asumsikan bahwa Anda memiliki blok iterator yang sangat sederhana:
Blok iterator nyata sering memiliki kondisi dan loop tetapi ketika Anda memeriksa kondisi dan membuka gulungannya, mereka masih berakhir sebagai
yield
pernyataan yang disatukan dengan kode lain.Untuk menghitung blok iterator, sebuah
foreach
loop digunakan:Berikut ini hasilnya (tidak ada kejutan di sini):
Sebagaimana dinyatakan di atas
foreach
adalah gula sintaksis:Dalam upaya untuk mengurai ini saya telah membuat diagram urutan dengan abstraksi dihapus:
Mesin negara yang dihasilkan oleh kompiler juga mengimplementasikan enumerator tetapi untuk membuat diagram lebih jelas, saya telah menunjukkannya sebagai instance yang terpisah. (Ketika mesin negara disebutkan dari utas lain, Anda benar-benar mendapatkan instance terpisah tetapi detail itu tidak penting di sini.)
Setiap kali Anda menyebut blok iterator Anda, instance baru dari mesin state dibuat. Namun, tidak ada kode Anda di blok iterator yang dieksekusi sampai
enumerator.MoveNext()
dijalankan untuk pertama kalinya. Inilah cara kerja eksekusi yang ditangguhkan. Berikut adalah contoh (agak konyol):Pada titik ini iterator belum dieksekusi. The
Where
klausul menciptakan baruIEnumerable<T>
yang membungkus yangIEnumerable<T>
dikembalikan olehIteratorBlock
tetapi enumerable ini belum disebutkan. Ini terjadi ketika Anda menjalankanforeach
loop:Jika Anda menghitung enumerable dua kali maka instance baru dari mesin state dibuat setiap kali dan blok iterator Anda akan mengeksekusi kode yang sama dua kali.
Perhatikan bahwa metode LINQ suka
ToList()
,ToArray()
,First()
,Count()
dll akan menggunakanforeach
loop untuk menghitung enumerable tersebut. MisalnyaToList()
akan menghitung semua elemen yang dapat dihitung dan menyimpannya dalam daftar. Anda sekarang dapat mengakses daftar untuk mendapatkan semua elemen enumerable tanpa blok iterator mengeksekusi lagi. Ada trade-off antara menggunakan CPU untuk menghasilkan elemen-elemen enumerable berulang kali dan memori untuk menyimpan elemen-elemen enumerasi untuk mengaksesnya berkali-kali saat menggunakan metode sepertiToList()
.sumber
Jika saya memahami ini dengan benar, inilah cara saya akan mengatakan ini dari perspektif fungsi mengimplementasikan IEnumerable dengan hasil.
sumber
Kata kunci C # yield, sederhananya, memungkinkan banyak panggilan ke badan kode, yang disebut sebagai iterator, yang tahu cara mengembalikan sebelum dilakukan dan, ketika dipanggil lagi, melanjutkan dari mana ia tinggalkan - artinya membantu iterator menjadi transparan secara transparan per setiap item dalam urutan yang dikembalikan oleh iterator dalam panggilan berurutan.
Dalam JavaScript, konsep yang sama disebut Generator.
sumber
Ini adalah cara yang sangat sederhana dan mudah untuk membuat enumerable untuk objek Anda. Kompiler membuat kelas yang membungkus metode Anda dan yang mengimplementasikan, dalam hal ini, IEnumerable <object>. Tanpa kata kunci hasil, Anda harus membuat objek yang mengimplementasikan IEnumerable <object>.
sumber
Itu menghasilkan urutan enumerable. Apa yang dilakukannya sebenarnya menciptakan urutan IEnumerable lokal dan mengembalikannya sebagai hasil metode
sumber
Tautan ini memiliki contoh sederhana
Contoh-contoh yang lebih sederhana ada di sini
Perhatikan bahwa pengembalian hasil tidak akan kembali dari metode. Anda bahkan dapat menempatkan
WriteLine
setelahyield return
Di atas menghasilkan IEnumerable dari 4 int 4,4,4,4
Sini dengan
WriteLine
. Akan menambahkan 4 ke daftar, mencetak abc, lalu menambahkan 4 ke daftar, lalu menyelesaikan metode dan benar-benar kembali dari metode (setelah metode selesai, seperti yang akan terjadi dengan prosedur tanpa pengembalian). Tapi ini akan memiliki nilai,IEnumerable
daftarint
s, yang dikembalikan setelah selesai.Perhatikan juga bahwa ketika Anda menggunakan hasil, apa yang Anda kembalikan bukan dari jenis yang sama dengan fungsi. Itu dari jenis elemen dalam
IEnumerable
daftar.Anda menggunakan hasil dengan tipe pengembalian metode sebagai
IEnumerable
. Jika tipe pengembalian metode adalahint
atauList<int>
dan Anda gunakanyield
, maka itu tidak akan dikompilasi. Anda dapat menggunakanIEnumerable
jenis metode pengembalian tanpa hasil tetapi tampaknya Anda mungkin tidak dapat menggunakan hasil tanpaIEnumerable
jenis metode kembali.Dan untuk mengeksekusinya Anda harus menyebutnya dengan cara khusus.
sumber
public static IEnumerable<TResult> testYieldc<TResult>(TResult t) { yield return t; }
danpublic static IEnumerable<TResult> testYieldc<TResult>(TResult t) { return new List<TResult>(); }
yield return
itu dengan baik (selain hal sederhana yang saya sebutkan), dan belum banyak menggunakannya dan tidak tahu banyak tentang penggunaannya, saya tidak berpikir ini yang seharusnya diterima.Satu poin utama tentang kata kunci hasil adalah Eksekusi Malas . Sekarang yang saya maksud dengan Lazy Execution adalah mengeksekusi ketika dibutuhkan. Cara yang lebih baik untuk menjelaskannya adalah dengan memberi contoh
Contoh: Tidak menggunakan Yield yaitu Eksekusi Tanpa Malas.
Contoh: menggunakan Yield yaitu Eksekusi Malas.
Sekarang ketika saya memanggil kedua metode.
Anda akan melihat listItems akan memiliki 5 item di dalamnya (arahkan mouse Anda ke listItems saat debugging). Sedangkan yieldItems hanya akan memiliki referensi ke metode dan bukan item. Itu berarti belum menjalankan proses mendapatkan item di dalam metode. Cara yang sangat efisien untuk mendapatkan data hanya saat dibutuhkan. Implementasi hasil aktual dapat dilihat di ORM seperti Entity Framework dan NHibernate dll.
sumber
Itu mencoba untuk membawa beberapa Kebaikan Ruby :)
Konsep: Ini adalah beberapa contoh Kode Ruby yang mencetak setiap elemen array
Setiap implementasi metode Array menghasilkan kontrol ke pemanggil ('menempatkan x') dengan masing - masing elemen array yang disajikan dengan rapi sebagai x. Penelepon kemudian dapat melakukan apa pun yang perlu dilakukan dengan x.
Namun. Net tidak pergi jauh-jauh ke sini .. C # tampaknya telah digabungkan hasil dengan IEnumerable, dengan cara memaksa Anda untuk menulis loop foreach di pemanggil seperti yang terlihat dalam respons Mendelt. Agak kurang elegan.
sumber
yield
digabungkan denganIEnumerable
, dan C # tidak memiliki konsep Ruby dari "blok". Tetapi C # memiliki lambdas, yang dapat memungkinkan implementasi suatuForEach
metode, sama seperti Rubyeach
. Namun ini tidak berarti akan menjadi ide yang baik untuk melakukannya .