Sementara memahami cara yield
kerja kata kunci, saya menemukan link1 dan link2 di StackOverflow yang menganjurkan penggunaan yield return
sementara iterasi DataReader dan sesuai dengan kebutuhan saya juga. Tapi itu membuat saya bertanya-tanya seperti apa yang terjadi, jika saya menggunakan yield return
seperti yang ditunjukkan di bawah ini dan jika saya tidak mengulangi seluruh DataReader, akankah koneksi DB tetap terbuka selamanya?
IEnumerable<IDataRecord> GetRecords()
{
SqlConnection myConnection = new SqlConnection(@"...");
SqlCommand myCommand = new SqlCommand(@"...", myConnection);
myCommand.CommandType = System.Data.CommandType.Text;
myConnection.Open();
myReader = myCommand.ExecuteReader(CommandBehavior.CloseConnection);
try
{
while (myReader.Read())
{
yield return myReader;
}
}
finally
{
myReader.Close();
}
}
void AnotherMethod()
{
foreach(var rec in GetRecords())
{
i++;
System.Console.WriteLine(rec.GetString(1));
if (i == 5)
break;
}
}
Saya mencoba contoh yang sama dalam contoh Aplikasi Konsol dan memperhatikan saat debugging bahwa blok akhirnya GetRecords()
tidak dijalankan. Bagaimana saya memastikan penutupan Koneksi DB? Apakah ada cara yang lebih baik daripada menggunakan yield
kata kunci? Saya mencoba merancang kelas khusus yang akan bertanggung jawab untuk mengeksekusi SQL pilihan dan prosedur tersimpan pada DB dan akan mengembalikan hasilnya. Tapi saya tidak ingin mengembalikan DataReader ke pemanggil. Saya juga ingin memastikan bahwa koneksi akan ditutup di semua skenario.
Sunting Mengubah jawaban atas jawaban Ben karena tidak tepat mengharapkan penelepon metode menggunakan metode ini dengan benar dan sehubungan dengan koneksi DB akan lebih mahal jika metode ini dipanggil beberapa kali tanpa alasan.
Terima kasih Jakob dan Ben untuk penjelasan terperinci.
Jawaban:
Ya, Anda akan menghadapi masalah yang Anda uraikan: sampai Anda selesai mengulangi hasilnya, Anda akan membuat koneksi tetap terbuka. Ada dua pendekatan umum yang bisa saya pikirkan untuk menghadapi ini:
Dorong, jangan tarik
Saat Anda kembali sebuah
IEnumerable<IDataRecord>
, struktur data Anda dapat menarik dari. Sebagai gantinya, Anda dapat mengganti metode Anda untuk mendorong hasilnya. Cara paling sederhana adalah dengan memasukkan sebuahAction<IDataRecord>
yang dipanggil pada setiap iterasi:Perhatikan bahwa mengingat Anda berurusan dengan koleksi item,
IObservable
/IObserver
mungkin struktur data sedikit lebih tepat, tetapi kecuali jika Anda membutuhkannya, yang sederhanaAction
jauh lebih mudah.Mengevaluasi dengan bersemangat
Alternatif lain adalah memastikan iterasi sepenuhnya selesai sebelum kembali.
Anda biasanya dapat melakukan ini dengan hanya memasukkan hasilnya dalam daftar lalu mengembalikannya, tetapi dalam hal ini ada komplikasi tambahan dari setiap item yang menjadi referensi yang sama kepada pembaca. Jadi Anda perlu sesuatu untuk mengekstrak hasil yang Anda butuhkan dari pembaca:
sumber
Anda
finally
Blok akan selalu dieksekusi.Saat Anda menggunakan
yield return
kompiler akan membuat kelas bersarang baru untuk mengimplementasikan mesin negara.Kelas ini akan berisi semua kode dari
finally
blok sebagai metode terpisah. Ini akan melacakfinally
blok yang perlu dijalankan tergantung pada negara. Semuafinally
blok yang diperlukan akan dieksekusi diDispose
metode.Menurut spesifikasi bahasa C #,
foreach (V v in x) embedded-statement
diperluas kesehingga enumerator akan dibuang meskipun Anda keluar dari loop dengan
break
ataureturn
.Untuk mendapatkan informasi lebih lanjut tentang implementasi iterator Anda dapat membaca artikel ini oleh Jon Skeet
Edit
Masalah dengan pendekatan tersebut adalah bahwa Anda mengandalkan klien kelas Anda untuk menggunakan metode ini dengan benar. Misalnya mereka bisa mendapatkan enumerator secara langsung dan mengulanginya melalui a
while
lingkaran tanpa membuangnya.Anda harus mempertimbangkan salah satu solusi yang disarankan oleh @BenAaronson.
sumber
var records = GetRecords(); var first = records.First(); var count = records.Count()
sebenarnya akan menjalankan metode itu dua kali, membuka dan menutup koneksi dan pembaca setiap kali. Iterasi dua kali melakukan hal yang sama. Anda juga dapat melewati hasilnya untuk waktu yang lama sebelum benar-benar mengulanginya, dll. Tidak ada dalam tanda tangan metode menyiratkan perilaku semacam ituGetRecords
returnIEnumerable
, saya berharap itu memuat catatan ke memori. Di sisi lain, jika itu adalah propertiRecords
, saya lebih suka menganggap itu semacam abstraksi atas database.Terlepas dari
yield
perilaku tertentu , kode Anda berisi jalur eksekusi yang tidak akan membuang sumber daya Anda dengan benar. Bagaimana jika baris kedua Anda melempar pengecualian atau yang ketiga? Atau bahkan keempat Anda? Anda membutuhkan rantai coba / akhirnya yang sangat kompleks, atau Anda dapat menggunakanusing
blok.Orang-orang mengatakan bahwa ini tidak intuitif, bahwa orang yang memanggil metode Anda mungkin tidak tahu bahwa menghitung hasilnya dua kali akan memanggil metode dua kali. Yah, keberuntungan yang sulit. Begitulah cara kerjanya bahasa. Itu sinyal yang
IEnumerable<T>
mengirim. Ada alasan mengapa ini tidak kembaliList<T>
atauT[]
. Orang yang tidak tahu ini perlu dididik, tidak bekerja.Visual Studio memiliki fitur yang disebut analisis kode statis. Anda dapat menggunakannya untuk mengetahui apakah Anda telah membuang sumber daya dengan benar.
sumber
IEnumerable
untuk melakukan segala macam hal, seperti melempar pengecualian yang sama sekali tidak terkait atau menulis file 5GB ke disk. Hanya karena bahasa memungkinkan, bukan berarti dapat diterima untuk mengembalikanIEnumerable
yang melakukannya dari metode yang tidak memberikan indikasi bahwa itu akan terjadi.IEnumerable<T>
adalah indikasi bahwa penghitungan dua kali akan menghasilkan dua panggilan. Anda bahkan akan mendapatkan peringatan yang berguna di alat Anda jika Anda menghitung berulang kali. Poin tentang melempar pengecualian sama-sama valid terlepas dari tipe pengembalian. Jika metode itu akan mengembalikan,List<T>
itu akan melempar pengecualian yang sama.IEnumerable
emptor peringatan. Tetapi saya juga berpikir bahwa sebagai penulis metode, Anda memiliki tanggung jawab untuk mematuhi apa yang tersirat dari tanda tangan Anda. Metode ini dipanggilGetRecords
, jadi ketika dikembalikan Anda akan mengharapkan untuk memiliki, Anda tahu, mendapatkan catatan . Jika disebutGiveMeSomethingICanPullRecordsFrom
(atau, lebih realistisGetRecordSource
atau serupa) maka malasIEnumerable
akan lebih dapat diterima.File
,ReadLines
danReadAllLines
dapat dengan mudah dibedakan dan itu hal yang baik. (Meskipun lebih karena fakta bahwa metode overload tidak dapat berbeda hanya dengan tipe return). Mungkin ReadRecords dan ReadAllRecords cocok di sini sebagai skema penamaan.Apa yang Anda lakukan tampaknya salah, tetapi akan bekerja dengan baik.
Anda harus menggunakan IEnumerator <> , karena juga mewarisi IDisposable .
Tetapi karena Anda menggunakan foreach, kompiler masih menggunakan IDisposable untuk menghasilkan IEnumerator <> untuk foreach.
pada kenyataannya, pendahuluan melibatkan banyak hal internal.
Periksa /programming/13459447/do-i-need-to-consider-disposing-of-any-ienumerablet-i-use
sumber