Apakah if (item! = Null) berlebihan sebelum foreach (T item dalam item)?

103

Saya sering menemukan kode seperti berikut:

if ( items != null)
{
   foreach(T item in items)
   {
        //...
   }
}

Pada dasarnya, ifkondisi tersebut memastikan bahwa foreachblok hanya akan dijalankan jika itemsbukan null. Saya bertanya-tanya apakah ifkondisinya benar-benar diperlukan, atau foreachakan menangani kasusnya jika items == null.

Maksud saya, bisakah saya menulis

foreach(T item in items)
{
    //...
}

tanpa khawatir apakah itemsnull atau tidak? Apakah ifkondisinya berlebihan? Atau ini tergantung pada jenis dari itemsatau mungkin di Tjuga?

Nawaz
sumber
1
@ jawaban kjbartel ini (di " stackoverflow.com/a/32134295/401246 " adalah solusi terbaik, karena tidak: a) melibatkan degradasi kinerja (bahkan ketika tidak null) generalisasi seluruh lingkaran ke LCD dari Enumerable(seperti menggunakan ??akan ), b) memerlukan penambahan Metode Ekstensi untuk setiap Proyek, atau c) memerlukan penghindaran null IEnumerables (Pffft! Puh-LEAZE! SMH.) untuk memulai dengan (cuz, nullberarti N / A, sedangkan daftar kosong berarti, itu adalah appl. but is saat ini, yah, kosong !, yaitu seorang Karyawan dapat memiliki Komisi yang N / A untuk non-Penjualan atau kosong untuk Penjualan ketika mereka belum memperolehnya).
Tom

Jawaban:

115

Anda masih perlu memeriksa apakah (items! = Null) jika tidak, Anda akan mendapatkan NullReferenceException. Bagaimanapun Anda dapat melakukan sesuatu seperti ini:

List<string> items = null;  
foreach (var item in items ?? new List<string>())
{
    item.Dump();
}

tetapi Anda mungkin memeriksa kinerjanya. Jadi saya masih lebih suka jika (item! = Null) dulu.

Berdasarkan saran Eric's Lippert, saya mengubah kode menjadi:

List<string> items = null;  
foreach (var item in items ?? Enumerable.Empty<string>())
{
    item.Dump();
}
Vlad Bezden
sumber
31
Ide lucu; array kosong lebih disukai karena menggunakan lebih sedikit memori dan menghasilkan lebih sedikit tekanan memori. Enumerable.Empty <string> bahkan lebih disukai karena ia menyimpan cache larik kosong yang dihasilkan dan menggunakannya kembali.
Eric Lippert
5
Saya berharap kode kedua menjadi lebih lambat. Ini merosot urutan ke IEnumerable<T>yang pada gilirannya menurunkan pencacah ke antarmuka membuat iterasi lebih lambat. Pengujian saya menunjukkan degradasi faktor 5 untuk iterasi melalui array int.
CodesInChaos
11
@ CodeInChaos: Apakah Anda biasanya menemukan bahwa kecepatan pencacahan urutan kosong adalah penghambat kinerja dalam program Anda?
Eric Lippert
14
Ini tidak hanya mengurangi kecepatan pencacahan urutan kosong, tetapi juga urutan lengkap. Dan jika urutannya cukup panjang, itu penting. Untuk sebagian besar kode kita harus memilih kode idiomatik. Namun dua alokasi yang Anda sebutkan akan menjadi masalah kinerja dalam kasus yang lebih sedikit.
CodesInChaos
15
@CodeInChaos: Ah, saya mengerti maksud Anda sekarang. Ketika compiler dapat mendeteksi bahwa "foreach" melakukan iterasi pada List <T> atau array, maka compiler dapat mengoptimalkan foreach untuk menggunakan enumerator tipe-nilai atau sebenarnya menghasilkan loop "for". Ketika dipaksa untuk menghitung salah satu daftar atau urutan kosong itu harus kembali ke codegen "penyebut umum terendah", yang dalam beberapa kasus dapat lebih lambat dan menghasilkan lebih banyak tekanan memori. Ini adalah poin yang halus tapi sangat bagus. Tentu saja, moral dari cerita ini adalah - seperti biasa - jika Anda memiliki masalah kinerja, buat profil untuk mengetahui apa sebenarnya hambatan.
Eric Lippert
68

Menggunakan C # 6 Anda dapat menggunakan operator bersyarat null baru bersama-sama dengan List<T>.ForEach(Action<T>)(atau IEnumerable<T>.ForEachmetode ekstensi Anda sendiri ).

List<string> items = null;
items?.ForEach(item =>
{
    // ...
});
kjbartel.dll
sumber
Jawaban yang elegan. Terima kasih!
Steve
2
Ini adalah solusi terbaik, karena tidak: a) melibatkan penurunan kinerja (bahkan jika tidak null) menggeneralisasi seluruh loop ke LCD Enumerable(seperti yang ??akan digunakan ), b) memerlukan penambahan Metode Ekstensi untuk setiap Proyek, atau c ) memerlukan penghindaran null IEnumerables (Pffft! Puh-LEAZE! SMH.) untuk memulai dengan (cuz, nullartinya T / A, sedangkan daftar kosong berarti, itu berlaku. tetapi saat ini, yah, kosong !, yaitu sebuah Pekerjaan dapat memiliki Komisi yang T / A untuk non-Penjualan atau kosongkan untuk Penjualan ketika mereka belum memperolehnya).
Tom
6
@ Tom: Ini mengasumsikan itu itemsadalah List<T>meskipun, bukan sembarang IEnumerable<T>. (Atau memiliki metode ekstensi khusus, yang Anda katakan Anda tidak ingin ada ...) Selain itu, saya akan mengatakan itu benar-benar tidak layak menambahkan 11 komentar yang pada dasarnya mengatakan bahwa Anda menyukai jawaban tertentu.
Jon Skeet
2
@ Tom: Saya akan sangat melarang Anda melakukannya di masa mendatang. Bayangkan jika setiap orang yang tidak setuju dengan komentar Anda kemudian menambahkan komentar mereka ke semua komentar Anda . (Bayangkan saya telah menulis balasan saya di sini tetapi 11 kali.) Ini sama sekali bukan penggunaan Stack Overflow yang produktif.
Jon Skeet
1
Saya juga akan berasumsi bahwa akan ada kinerja sukses yang memanggil delegasi versus standar foreach. Khususnya untuk List yang menurut saya akan diubah menjadi forloop.
kjbartel
37

Pengambilan sebenarnya di sini harus berupa urutan yang hampir tidak pernah boleh nol di tempat pertama . Cukup jadikan itu invarian di semua program Anda yang jika Anda memiliki urutan, itu tidak pernah nol. Itu selalu diinisialisasi menjadi urutan kosong atau urutan asli lainnya.

Jika suatu urutan tidak pernah nol maka jelas Anda tidak perlu memeriksanya.

Eric Lippert
sumber
1
Bagaimana jika Anda mendapatkan urutan dari layanan WCF? Mungkin nihil, kan?
Nawaz
4
@Nawaz: Jika saya memiliki layanan WCF yang mengembalikan saya urutan nol yang dimaksudkan untuk menjadi urutan kosong maka saya akan melaporkannya kepada mereka sebagai bug. Yang mengatakan: jika Anda harus berurusan dengan output yang tidak benar dari layanan yang bisa dibilang buggy, maka ya, Anda harus menghadapinya dengan memeriksa null.
Eric Lippert
7
Kecuali, tentu saja, null dan empty memiliki arti yang sangat berbeda. Terkadang itu berlaku untuk urutan.
konfigurator
@Nawaz Bagaimana dengan DataTable. Baris yang mengembalikan null alih-alih koleksi kosong. Mungkin itu bug?
Neil B
@ jawaban kjbartel ini (di " stackoverflow.com/a/32134295/401246 " adalah solusi terbaik, karena tidak: a) melibatkan degradasi kinerja (bahkan ketika tidak null) generalisasi seluruh lingkaran ke LCD dari Enumerable(seperti menggunakan ??akan ), b) memerlukan penambahan Metode Ekstensi untuk setiap Proyek, atau c) memerlukan penghindaran null IEnumerables (Pffft! Puh-LEAZE! SMH.) untuk memulai dengan (cuz, nullberarti N / A, sedangkan daftar kosong berarti, itu adalah appl. but is saat ini, yah, kosong !, yaitu seorang Karyawan dapat memiliki Komisi yang N / A untuk non-Penjualan atau kosong untuk Penjualan ketika mereka belum memperolehnya).
Tom
10

Sebenarnya ada permintaan fitur pada @Connect itu: http://connect.microsoft.com/VisualStudio/feedback/details/93497/foreach-should-check-for-null

Dan tanggapannya cukup logis:

Saya pikir kebanyakan foreach loop ditulis dengan maksud untuk mengulang koleksi non-null. Jika Anda mencoba mengulang melalui null, Anda akan mendapatkan pengecualian, sehingga Anda dapat memperbaiki kode Anda.

Teoman Soygul
sumber
Saya kira ada pro dan kontra untuk ini, jadi mereka memutuskan untuk menyimpannya seperti yang dirancang di tempat pertama. bagaimanapun, foreach hanyalah beberapa gula sintaksis. jika Anda memanggil items.GetEnumerator () yang juga akan macet jika item bernilai null, jadi Anda harus mengujinya terlebih dahulu.
Marius Bancila
6

Anda selalu dapat mengujinya dengan daftar null ... tetapi ini yang saya temukan di situs msdn

foreach-statement:
    foreach   (   type   identifier   in   expression   )   embedded-statement 

Jika ekspresi memiliki nilai null, System.NullReferenceException dilempar.

nbz
sumber
2

Itu tidak berlebihan. Saat runtime, item akan dicor ke IEnumerable dan metode GetEnumeratornya akan dipanggil. Itu akan menyebabkan dereferensi item yang akan gagal

boca
sumber
1
1) Urutan tidak harus dilemparkan ke IEnumerabledan 2) Ini adalah keputusan desain untuk membuatnya terlempar . C # dapat dengan mudah memasukkan nullcek itu jika pengembang menganggapnya sebagai ide yang bagus.
CodesInChaos
2

Anda dapat merangkum pemeriksaan null dalam metode ekstensi dan menggunakan lambda:

public static class EnumerableExtensions {
  public static void ForEach<T>(this IEnumerable<T> self, Action<T> action) {
    if (self != null) {
      foreach (var element in self) {
        action(element);
      }
    }
  }
}

Kode menjadi:

items.ForEach(item => { 
  ...
});

Jika bisa lebih ringkas jika Anda hanya ingin memanggil metode yang mengambil item dan mengembalikan void:

items.ForEach(MethodThatTakesAnItem);
Jordão
sumber
1

Anda memang membutuhkan ini. Anda akan mendapatkan pengecualian saat foreachmengakses penampung untuk menyiapkan iterasi sebaliknya.

Di bawah sampul, foreachmenggunakan antarmuka yang diimplementasikan pada kelas koleksi untuk melakukan iterasi. Antarmuka umum yang setara ada di sini .

Pernyataan foreach dari bahasa C # (untuk masing-masing dalam Visual Basic) menyembunyikan kompleksitas enumerator. Oleh karena itu, disarankan menggunakan foreach daripada memanipulasi enumerator secara langsung.

Steve Townsend
sumber
1
Sebagai catatan, secara teknis tidak menggunakan antarmuka, ia menggunakan pengetikan bebek: blogs.msdn.com/b/kcwalina/archive/2007/07/18/ducknotation.aspx antarmuka memastikan bahwa metode dan properti yang tepat ada di sana meskipun, dan membantu pemahaman tentang niat. serta menggunakan bagian depan luar ...
ShuggyCoUk
0

Pengujian ini diperlukan, karena jika collectionnya null, foreach akan memunculkan NullReferenceException. Sebenarnya cukup mudah untuk mencobanya.

List<string> items = null;
foreach(var item in items)
{
   Console.WriteLine(item);
}
Marius Bancila
sumber
0

yang kedua akan melempar NullReferenceExceptiondengan pesanObject reference not set to an instance of an object.

harryovers
sumber
0

Seperti yang disebutkan di sini, Anda perlu memeriksa apakah itu bukan nol.

Jangan gunakan ekspresi yang bernilai null.

Renatas M.
sumber
0

Di C # 6 Anda bisa menulis sth seperti ini:

// some string from file or UI, i.e.:
// a) string s = "Hello, World!";
// b) string s = "";
// ...
var items = s?.Split(new char[] { ',', '!', ' ' }) ?? Enumerable.Empty<string>();  
foreach (var item in items)
{
    //..
}

Ini pada dasarnya adalah solusi Vlad Bezden tetapi menggunakan ?? ekspresi untuk selalu menghasilkan larik yang bukan nol dan oleh karena itu bertahan dari foreach daripada memiliki pemeriksaan ini di dalam braket foreach.

dr. rAI
sumber