Saya punya metode ekstensi string C # yang harus mengembalikan IEnumerable<int>
semua indeks substring di dalam string. Ini berfungsi dengan sempurna untuk tujuan yang dimaksudkan dan hasil yang diharapkan dikembalikan (sebagaimana dibuktikan oleh salah satu pengujian saya, meskipun bukan yang di bawah), tetapi pengujian unit lain telah menemukan masalah dengannya: tidak dapat menangani argumen nol.
Inilah metode ekstensi yang saya uji:
public static IEnumerable<int> AllIndexesOf(this string str, string searchText)
{
if (searchText == null)
{
throw new ArgumentNullException("searchText");
}
for (int index = 0; ; index += searchText.Length)
{
index = str.IndexOf(searchText, index);
if (index == -1)
break;
yield return index;
}
}
Berikut adalah tes yang menandai masalah tersebut:
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void Extensions_AllIndexesOf_HandlesNullArguments()
{
string test = "a.b.c.d.e";
test.AllIndexesOf(null);
}
Ketika pengujian dijalankan terhadap metode ekstensi saya, itu gagal, dengan pesan kesalahan standar bahwa metode "tidak melempar pengecualian".
Ini membingungkan: Saya telah dengan jelas meneruskan null
ke fungsinya, namun untuk beberapa alasan perbandingannya null == null
kembali false
. Oleh karena itu, tidak ada pengecualian yang dilempar dan kode berlanjut.
Saya telah mengonfirmasi bahwa ini bukan bug dengan pengujian: ketika menjalankan metode di proyek utama saya dengan panggilan ke Console.WriteLine
di if
blok perbandingan-null , tidak ada yang ditampilkan di konsol dan tidak ada pengecualian yang tertangkap oleh catch
blok apa pun yang saya tambahkan. Selain itu, menggunakan string.IsNullOrEmpty
bukannya == null
memiliki masalah yang sama.
Mengapa perbandingan yang seharusnya sederhana ini gagal?
sumber
Jawaban:
Anda sedang menggunakan
yield return
. Saat melakukannya, kompilator akan menulis ulang metode Anda menjadi fungsi yang mengembalikan kelas yang dihasilkan yang mengimplementasikan mesin status.Secara umum, ini menulis ulang penduduk setempat ke bidang kelas itu dan setiap bagian dari algoritme Anda di antara
yield return
instruksi menjadi status. Anda dapat memeriksa dengan decompiler akan menjadi apa metode ini setelah kompilasi (pastikan untuk mematikan dekompilasi cerdas yang akan menghasilkanyield return
).Tetapi intinya adalah: kode metode Anda tidak akan dieksekusi sampai Anda mulai mengulang.
Cara biasa untuk memeriksa prasyarat adalah dengan membagi metode Anda menjadi dua:
Ini berfungsi karena metode pertama akan berperilaku seperti yang Anda harapkan (eksekusi langsung), dan akan mengembalikan mesin status yang diimplementasikan oleh metode kedua.
Perhatikan bahwa Anda juga harus memeriksa
str
parameter untuknull
, karena metode ekstensi dapat dipanggil padanull
nilai, karena mereka hanya gula sintaksis.Jika Anda penasaran tentang apa yang dilakukan compiler pada kode Anda, berikut adalah metode Anda, didekompilasi dengan dotPeek menggunakan opsi Show Compiler-generated Code .
Ini adalah kode C # yang tidak valid, karena kompilator diizinkan untuk melakukan hal-hal yang tidak diizinkan oleh bahasa, tetapi legal di IL - misalnya menamai variabel dengan cara yang tidak dapat Anda lakukan untuk menghindari benturan nama.
Tapi seperti yang Anda lihat,
AllIndexesOf
satu - satunya membangun dan mengembalikan sebuah objek, yang konstruktornya hanya menginisialisasi beberapa keadaan.GetEnumerator
hanya menyalin objek. Pekerjaan sebenarnya dilakukan ketika Anda mulai menghitung (dengan memanggilMoveNext
metode).sumber
str
parameternyanull
, karena metode ekstensi dapat dipanggil padanull
nilai, karena mereka hanya gula sintaksis.yield return
pada prinsipnya adalah ide yang bagus, tetapi memiliki banyak hal aneh. Terima kasih telah mengungkap yang satu ini!MoveNext
disebut di bawah tenda olehforeach
konstruksi. Saya menulis penjelasan tentang apa yangforeach
ada dalam jawaban saya menjelaskan semantik koleksi jika Anda ingin melihat pola yang tepat.Anda memiliki blok iterator. Tidak ada kode dalam metode itu yang pernah dijalankan di luar panggilan ke
MoveNext
pada iterator yang dikembalikan. Memanggil metode tidak akan mencatat tetapi membuat mesin status, dan itu tidak akan pernah gagal (di luar yang ekstrem seperti kesalahan kehabisan memori, stack overflows, atau pengecualian pembatalan thread).Saat Anda benar-benar mencoba mengulang urutan, Anda akan mendapatkan pengecualian.
Inilah sebabnya mengapa metode LINQ sebenarnya membutuhkan dua metode untuk memiliki semantik penanganan kesalahan yang mereka inginkan. Mereka memiliki metode pribadi yang merupakan blok iterator, dan kemudian metode blok non-iterator yang tidak melakukan apa-apa selain melakukan validasi argumen (sehingga dapat dilakukan dengan penuh semangat, daripada ditunda) sambil tetap menunda semua fungsionalitas lainnya.
Jadi ini pola umumnya:
sumber
Pencacah, seperti yang dikatakan orang lain, tidak dievaluasi sampai mereka mulai melakukan pencacahan (yaitu,
IEnumerable.GetNext
metode ini dipanggil). Jadi initidak dievaluasi sampai Anda mulai menghitung, yaitu
sumber