Periksa null di foreach loop

97

Apakah ada cara yang lebih baik untuk melakukan hal berikut:
Saya perlu memeriksa null untuk terjadi pada file. Header sebelum melanjutkan dengan loop

if (file.Headers != null)
{
  foreach (var h in file.Headers)
  {
   //set lots of properties & some other stuff
  }
}

Singkatnya, tampaknya agak jelek untuk menulis foreach di dalam if karena tingkat lekukan yang terjadi di kode saya.

Adalah sesuatu yang akan mengevaluasi

foreach(var h in (file.Headers != null))
{
  //do stuff
}

bisa jadi?

Eminem
sumber
3
Anda dapat melihatnya di sini: stackoverflow.com/questions/6937407/…
Adrian Fâciu
stackoverflow.com/questions/872323/… adalah ide lain.
weismat
1
@AdrianFaciu Saya pikir itu sama sekali berbeda. Pertanyaannya memeriksa apakah collectionnya nol terlebih dahulu sebelum melakukan for-each. Tautan Anda memeriksa apakah item dalam koleksi tersebut nol.
rikitikitik
1
C # 8 bisa saja memiliki foreach bersyarat nol dari beberapa jenis, yaitu sintaks seperti ini: foreach? (var i in collection) {} Saya pikir itu skenario yang cukup umum untuk membenarkan hal ini, dan mengingat penambahan bersyarat null baru-baru ini ke bahasa yang masuk akal di sini?
mms

Jawaban:

125

Hanya sebagai tambahan kosmetik untuk saran Rune, Anda dapat membuat metode ekstensi Anda sendiri:

public static IEnumerable<T> OrEmptyIfNull<T>(this IEnumerable<T> source)
{
    return source ?? Enumerable.Empty<T>();
}

Kemudian Anda bisa menulis:

foreach (var header in file.Headers.OrEmptyIfNull())
{
}

Ganti nama sesuai selera :)

Jon Skeet
sumber
80

Dengan asumsi bahwa jenis elemen dalam file.Headers adalah T, Anda dapat melakukan ini

foreach(var header in file.Headers ?? Enumerable.Empty<T>()){
  //do stuff
}

ini akan membuat enumerable T yang kosong jika file.Headers adalah null. Jika tipe file adalah tipe yang Anda miliki, saya akan, bagaimanapun, pertimbangkan untuk mengubah pengambil Headersalih - alih. nulladalah nilai tidak diketahui jadi jika memungkinkan daripada menggunakan null sebagai "Saya tahu tidak ada elemen" ketika null sebenarnya (/ aslinya) harus diartikan sebagai "Saya tidak tahu apakah ada elemen" gunakan set kosong untuk menunjukkan bahwa Anda tahu tidak ada elemen di himpunan. Itu juga akan lebih KERING karena Anda tidak perlu melakukan pemeriksaan null sesering mungkin.

EDIT sebagai tindak lanjut dari saran Jons, Anda juga dapat membuat metode ekstensi dengan mengubah kode di atas menjadi

foreach(var header in file.Headers.OrEmptyIfNull()){
  //do stuff
}

Dalam kasus di mana Anda tidak dapat mengubah pengambil, ini akan menjadi pilihan saya sendiri karena ini mengungkapkan maksud lebih jelas dengan memberi nama operasi (OrEmptyIfNull)

Metode ekstensi yang disebutkan di atas mungkin membuat pengoptimalan tertentu tidak mungkin dideteksi oleh pengoptimal. Secara khusus, yang terkait dengan IList menggunakan metode overloading ini dapat dihilangkan

public static IList<T> OrEmptyIfNull<T>(this IList<T> source)
{
    return source ?? Array.Empty<T>();
}
Rune FS
sumber
Jawaban @ kjbartel (di "stackoverflow.com/a/32134295/401246" adalah solusi terbaik, karena tidak: a) melibatkan penurunan kinerja (bahkan jika tidak null) menurunkan seluruh loop ke LCD IEnumerable<T>(seperti menggunakan ?? akan), b) memerlukan penambahan Metode Ekstensi untuk setiap Proyek, atau c) memerlukan penghindaran null IEnumerables(Pffft! Puh-LEAZE! SMH.) untuk memulai (cuz nullberarti T / A, sedangkan daftar kosong berarti, itu berlaku tetapi saat ini, baik, kosong !, yaitu seorang Karyawan dapat memiliki Komisi yang N / A untuk non-Penjualan atau kosong untuk Penjualan ketika mereka belum memperolehnya).
Tom
@Tom selain dari satu cek nol, tidak ada hukuman untuk kasus-kasus di mana pencacah tidak nol. Menghindari pemeriksaan itu sambil juga memastikan bahwa enumerable tidak null adalah tidak mungkin. Kode di atas memerlukan Header ke IEnumerable yang lebih ketat daripada foreachpersyaratan tetapi kurang ketat daripada persyaratan List<T>dalam jawaban yang Anda tautkan. Yang memiliki hukuman kinerja yang sama untuk menguji apakah enumerable adalah null.
Rune FS
Saya mendasarkan masalah "LCD" pada komentar Eric Lippert pada Jawaban Vlad Bezden di utas yang sama dengan Jawaban kjbartel: "@CodeInChaos: Ah, saya mengerti maksud Anda sekarang. Saat kompilator dapat mendeteksi bahwa" foreach "sedang berulang di atas List <T> atau larik maka itu dapat mengoptimalkan foreach untuk menggunakan pencacah tipe-nilai atau benar-benar menghasilkan perulangan "untuk". Ketika dipaksa untuk menyebutkan daftar atau urutan kosong, ia harus kembali ke " penyebut umum terendah "codegen, yang dalam beberapa kasus dapat lebih lambat dan menghasilkan lebih banyak tekanan memori ....". Setuju itu membutuhkan List<T>.
Tom
@tom Premis jawabannya adalah bahwa file.Headers adalah IEnumerable <T> dalam hal ini kompilator tidak dapat melakukan optimasi. Namun agak mudah untuk memperluas solusi metode ekstensi untuk menghindari hal ini. Lihat edit
Rune FS
19

Terus terang, saya menyarankan: menyedot saja nullujiannya. Sebuah nulltes hanya sebuah brfalseatau brfalse.s; segala sesuatu yang lain akan melibatkan kerja lebih banyak (tes, tugas, metode ekstra panggilan, yang tidak perlu GetEnumerator(), MoveNext(), Dispose()pada iterator, dll).

Sebuah iftes sederhana, jelas, dan efisien.

Marc Gravell
sumber
1
Anda membuat poin yang menarik Marc, namun, saat ini saya sedang mencari untuk mengurangi tingkat indentasi kode. Tetapi saya akan mengingat komentar Anda ketika saya perlu mencatat kinerja.
Eminem
3
Hanya catatan singkat tentang Marc ini .. bertahun-tahun setelah saya mengajukan pertanyaan ini dan perlu menerapkan beberapa peningkatan kinerja, saran Anda sangat berguna. Terima kasih
Eminem
13

"jika" sebelum iterasi baik-baik saja, beberapa dari semantik "cantik" tersebut dapat membuat kode Anda kurang dapat dibaca.

bagaimanapun, jika lekukan mengganggu Anda, Anda dapat mengubah jika untuk memeriksa:

if(file.Headers == null)  
   return;

dan Anda akan sampai ke loop foreach hanya jika ada nilai true di properti headers.

opsi lain yang dapat saya pikirkan adalah menggunakan operator penggabungan-nol di dalam loop foreach Anda dan untuk sepenuhnya menghindari pemeriksaan nol. Sampel:

List<int> collection = new List<int>();
collection = null;
foreach (var i in collection ?? Enumerable.Empty<int>())
{
    //your code here
}

(ganti koleksi dengan objek / tipe Anda yang sebenarnya)

Tamir
sumber
Opsi pertama Anda tidak akan berfungsi jika ada kode di luar pernyataan if.
rikitikitik
Saya setuju, if statement itu mudah dan murah untuk diterapkan dibandingkan dengan membuat list baru, hanya untuk kecantikan kode.
Andreas Johansson
11

Menggunakan Null-conditional Operator dan ForEach () yang bekerja lebih cepat dari loop foreach standar.
Anda harus memasukkan koleksi ke Daftar.

   listOfItems?.ForEach(item => // ... );
Andrei Karcheuski
sumber
4
Harap tambahkan beberapa penjelasan seputar jawaban Anda, dengan menyatakan secara eksplisit mengapa solusi ini bekerja, bukan hanya kode satu baris
LordWilmore
solusi terbaik untuk kasus saya
Josef Henn
3

Saya menggunakan metode ekstensi kecil yang bagus untuk skenario ini:

  public static class Extensions
  {
    public static IList<T> EnsureNotNull<T>(this IList<T> list)
    {
      return list ?? new List<T>();
    }
  }

Mengingat bahwa Header adalah daftar tipe, Anda dapat melakukan hal berikut:

foreach(var h in (file.Headers.EnsureNotNull()))
{
  //do stuff
}
Wolfgang Ziegler
sumber
1
Anda dapat menggunakan ??operator dan mempersingkat pernyataan pengembalian kereturn list ?? new List<T>;
Rune FS
1
@wolfgangziegler, Jika saya mengerti dengan benar pengujian nulldalam sampel Anda file.Headers.EnsureNotNull() != nulltidak diperlukan, dan bahkan salah?
Remko Jansen
0

Untuk beberapa kasus, saya lebih memilih varian generik lain, dengan asumsi bahwa, sebagai aturan, konstruktor collection default mengembalikan instance kosong.

Akan lebih baik untuk menamai metode ini NewIfDefault. Ini bisa berguna tidak hanya untuk koleksi, jadi batasan jenis IEnumerable<T>mungkin berlebihan.

public static TCollection EmptyIfDefault<TCollection, T>(this TCollection collection)
        where TCollection: class, IEnumerable<T>, new()
    {
        return collection ?? new TCollection();
    }
N. Kudryavtsev
sumber