Memeriksa apakah daftar kosong dengan LINQ


122

Apa cara "terbaik" (dengan mempertimbangkan kecepatan dan keterbacaan) untuk menentukan apakah sebuah daftar kosong? Meskipun daftarnya berjenis IEnumerable<T>dan tidak memiliki properti Hitungan.

Saat ini saya sedang memikirkan di antara ini:

if (myList.Count() == 0) { ... }

dan ini:

if (!myList.Any()) { ... }

Dugaan saya adalah bahwa opsi kedua lebih cepat, karena akan kembali dengan hasil segera setelah melihat item pertama, sedangkan opsi kedua (untuk IEnumerable) perlu mengunjungi setiap item untuk mengembalikan hitungan.

Karena itu, apakah opsi kedua terlihat dapat dibaca oleh Anda? Mana yang lebih kamu sukai? Atau dapatkah Anda memikirkan cara yang lebih baik untuk menguji daftar kosong?

Respons edit @ lassevk tampaknya yang paling logis, ditambah dengan sedikit pemeriksaan waktu proses untuk menggunakan jumlah cache jika memungkinkan, seperti ini:

public static bool IsEmpty<T>(this IEnumerable<T> list)
{
    if (list is ICollection<T>) return ((ICollection<T>)list).Count == 0;

    return !list.Any();
}

5
Jauh lebih baik jangan mencampur isdan casttapi gunakan asdan nullperiksa:ICollection<T> collection = list as ICollection<T>; if (collection != null) return colllection.Count;
abatishchev

2
Mengapa menulis metode tambahan? Bukankah tidak list.Any()setara dengan list.IsEmpty? Metode kerangka kerja harus dioptimalkan - ada baiknya menulis yang baru hanya jika Anda mengetahuinya sebagai penghambat kinerja.
dbkk

6
Apakah ada yang repot-repot mengukur kinerja pada implementasi yang mereka sarankan atau apakah semua orang hanya membuang ide?
Michael Brown

Saya menyarankan masalah ke perpustakaan kelas .NET Core yang menambahkan IsEmptymetode ekstensi. github.com/dotnet/corefx/issues/35054 Silakan centang dan pilih jika Anda suka dan setuju.
RyotaMurohoshi

Jawaban:


100

Anda bisa melakukan ini:

public static Boolean IsEmpty<T>(this IEnumerable<T> source)
{
    if (source == null)
        return true; // or throw an exception
    return !source.Any();
}

Edit : Perhatikan bahwa hanya menggunakan metode .Count akan cepat jika sumber yang mendasarinya sebenarnya memiliki properti Hitungan cepat. Pengoptimalan yang valid di atas adalah dengan mendeteksi beberapa jenis dasar dan cukup menggunakan properti .Count dari keduanya, bukan pendekatan .Any (), tetapi kemudian kembali ke .Any () jika tidak ada jaminan yang dapat dibuat.


4
Atau gunakan satu baris dan lakukan return (source == null)? true:! source.Any (); (Jika Anda tidak melempar pengecualian)
Gage

1
Saya akan mengatakan, ya, lempar pengecualian untuk null, tetapi kemudian tambahkan metode ekstensi kedua yang disebut IsNullOrEmpty().
devuxer

1
public static Boolean IsNullOrEmpty <T> (sumber <T> IEnumerable ini) {return source == null || ! source.Any (); }
dan

1
@Gage Saat ini:return !source?.Any() ?? true;
ricksmt

@ricksmt Terima kasih atas pembaruannya! Saya pasti akan menggunakan itu!
Gage

14

Saya akan membuat satu tambahan kecil untuk kode yang tampaknya telah Anda selesaikan: periksa juga ICollection, karena ini diterapkan bahkan oleh beberapa kelas generik yang tidak usang juga (yaitu, Queue<T>dan Stack<T>). Saya juga akan menggunakan asalih-alih iskarena itu lebih idiomatis dan telah terbukti lebih cepat .

public static bool IsEmpty<T>(this IEnumerable<T> list)
{
    if (list == null)
    {
        throw new ArgumentNullException("list");
    }

    var genericCollection = list as ICollection<T>;
    if (genericCollection != null)
    {
        return genericCollection.Count == 0;
    }

    var nonGenericCollection = list as ICollection;
    if (nonGenericCollection != null)
    {
        return nonGenericCollection.Count == 0;
    }

    return !list.Any();
}

1
Saya suka jawaban ini. Satu kata peringatan adalah bahwa beberapa koleksi akan mengeluarkan pengecualian ketika mereka tidak sepenuhnya mengimplementasikan antarmuka seperti NotSupportedExceptionatau NotImplementedException. Saya pertama kali menggunakan contoh kode Anda ketika saya menemukan koleksi yang saya gunakan membuat pengecualian untuk Count (siapa yang tahu ...).
Sam

1
Saya mengerti mengapa pengoptimalan seperti itu berguna untuk metode seperti Count () yang perlu menghitung semua elemen. Tetapi Any () hanya perlu menyebutkan paling banyak satu elemen, jadi saya gagal untuk melihat intinya di sini. Di sisi lain, gips dan pernyataan jika yang Anda tambahkan adalah biaya tetap yang harus Anda bayar pada setiap panggilan.
codymanix

8

LINQ sendiri harus melakukan beberapa optimasi serius seputar metode Count ().

Apakah ini mengejutkan Anda? Saya membayangkan bahwa untuk IListimplementasi, Countcukup membaca jumlah elemen secara langsung sementara Anyharus menanyakan IEnumerable.GetEnumeratormetode, membuat instance dan memanggil MoveNextsetidaknya sekali.

/ EDIT @Matt:

Saya hanya dapat berasumsi bahwa metode ekstensi Count () untuk IEnumerable melakukan sesuatu seperti ini:

Ya, tentu saja. Ini yang saya maksud. Sebenarnya, ini menggunakan ICollectionalih-alih IListtetapi hasilnya sama.


6

Saya baru saja menulis tes singkat, coba ini:

 IEnumerable<Object> myList = new List<Object>();

 Stopwatch watch = new Stopwatch();

 int x;

 watch.Start();
 for (var i = 0; i <= 1000000; i++)
 {
    if (myList.Count() == 0) x = i; 
 }
 watch.Stop();

 Stopwatch watch2 = new Stopwatch();

 watch2.Start();
 for (var i = 0; i <= 1000000; i++)
 {
     if (!myList.Any()) x = i;
 }
 watch2.Stop();

 Console.WriteLine("myList.Count() = " + watch.ElapsedMilliseconds.ToString());
 Console.WriteLine("myList.Any() = " + watch2.ElapsedMilliseconds.ToString());
 Console.ReadLine();

Yang kedua hampir tiga kali lebih lambat :)

Mencoba uji stopwatch lagi dengan Stack atau array atau skenario lain, itu benar-benar tergantung pada jenis daftar yang terlihat - karena mereka membuktikan Hitungan lebih lambat.

Jadi saya kira itu tergantung pada jenis daftar yang Anda gunakan!

(Sekadar menunjukkan, saya memasukkan 2000+ objek dalam Daftar dan menghitung masih lebih cepat, berlawanan dengan jenis lainnya)


12
Enumerable.Count<T>()memiliki penanganan khusus untuk ICollection<T>. Jika Anda mencoba ini dengan sesuatu yang lain dari daftar dasar, saya berharap Anda akan melihat secara signifikan berbeda (lebih lambat) hasil. Any()akan tetap sama.
Marc Gravell

2
Saya harus setuju dengan Marc; ini bukan ujian yang benar-benar adil.
Dan Tao

Tahu kenapa tidak ada penanganan khusus untuk Enumerable.Any<T>()for ICollection<T>? tentunya parameterless Any()bisa memeriksa Countproperti ICollection<T>juga?
Lukazoid

5

List.Countadalah O (1) menurut dokumentasi Microsoft:
http://msdn.microsoft.com/en-us/library/27b47ht3.aspx

jadi gunakan saja List.Count == 0 saja jauh lebih cepat daripada kueri

Ini karena ia memiliki anggota data yang disebut Hitungan yang diperbarui setiap kali ada sesuatu yang ditambahkan atau dihapus dari daftar, jadi ketika Anda memanggilnya List.Counttidak harus mengulang melalui setiap elemen untuk mendapatkannya, ia hanya mengembalikan anggota data.


1
jika itu adalah "IEnumerable" maka tidak. (sebagai permulaan IEnumerable tidak memiliki properti "Count", ia memiliki metode Count ().). Memanggil "Count ()" akan meminta IEnumerable untuk memeriksa setiap elemen dalam daftar. Sedangkan "Any" hanya akan muncul begitu menemukan 1 elemen.
00jt

Itu tergantung pada sumber data. Jika Anda menggunakan yield untuk membangun IEnumerable, IEnumerable harus melewati IEnumerable untuk mengetahui ukurannya. Jadi hanya O (1) dalam beberapa kasus. Itu tidak selalu O (1).
TamusJRoyce

3

Opsi kedua jauh lebih cepat jika Anda memiliki banyak item.

  • Any() kembali segera setelah 1 item ditemukan.
  • Count() harus terus menelusuri seluruh daftar.

Misalkan pencacahan memiliki 1000 item.

  • Any() akan memeriksa yang pertama, lalu mengembalikan true.
  • Count() akan mengembalikan 1000 setelah melewati seluruh pencacahan.

Ini berpotensi lebih buruk jika Anda menggunakan salah satu dari predicate overrides - Count () masih harus memeriksa setiap item, meskipun hanya ada satu yang cocok.

Anda terbiasa menggunakan Apa saja - ini masuk akal dan dapat dibaca.

Satu peringatan - jika Anda memiliki Daftar, bukan hanya IEnumerable, gunakan properti Hitung daftar itu.


Perbedaan antara Any () dan Count () tampak jelas, tetapi kode profil @ crucible tampaknya menunjukkan bahwa Count () lebih cepat untuk implementasi IEnumerable <T> tertentu. Untuk List <T> saya tidak bisa mendapatkan Any () untuk memberikan hasil yang lebih cepat daripada Hitungan () hingga ukuran daftar naik menjadi ribuan item. LINQ sendiri harus melakukan beberapa optimasi serius seputar metode Count ().
Matt Hamilton

3

@Konrad yang mengejutkan saya adalah bahwa dalam pengujian saya, saya meneruskan daftar ke metode yang menerima IEnumerable<T>, sehingga runtime tidak dapat mengoptimalkannya dengan memanggil metode ekstensi Count () untuk IList<T>.

Saya hanya dapat berasumsi bahwa metode ekstensi Count () untuk IEnumerable melakukan sesuatu seperti ini:

public static int Count<T>(this IEnumerable<T> list)
{
    if (list is IList<T>) return ((IList<T>)list).Count;

    int i = 0;
    foreach (var t in list) i++;
    return i;
}

... dengan kata lain, sedikit pengoptimalan waktu proses untuk kasus khusus IList<T>.

/ EDIT @Konrad +1 sobat - Anda benar tentang hal itu kemungkinan besar ada di ICollection<T>.


1

Oke, jadi bagaimana dengan yang ini?

public static bool IsEmpty<T>(this IEnumerable<T> enumerable)
{
    return !enumerable.GetEnumerator().MoveNext();
}

EDIT: Saya baru saja menyadari bahwa seseorang telah membuat sketsa solusi ini. Telah disebutkan bahwa metode Any () akan melakukan ini, tetapi mengapa tidak melakukannya sendiri? Salam


3
TAPI itu menjadi kurang ringkas ketika Anda menutupnya dengan benar dalam usingblok karena jika tidak, Anda telah membangun sebuah IDisposableobjek dan kemudian meninggalkannya. Kemudian, tentu saja, ini menjadi lebih ringkas ketika Anda menggunakan metode ekstensi yang sudah ada dan hanya mengubahnya menjadi return !enumerable.Any()(yang melakukan hal ini dengan tepat).
Dan Tao

Mengapa menulis ulang metode yang sudah ada? Seperti yang disebutkan Any()melakukan persis seperti itu, jadi menambahkan metode yang persis sama dengan nama lain hanya akan membingungkan.
Julien N

1

Ide lain:

if(enumerable.FirstOrDefault() != null)

Namun saya lebih suka pendekatan Any ().


3
Bagaimana jika Anda memiliki daftar yang tidak kosong di mana elemen pertama adalah null?
Ekevoo

1

Ini sangat penting agar ini berfungsi dengan Entity Framework:

var genericCollection = list as ICollection<T>;

if (genericCollection != null)
{
   //your code 
}

Bagaimana itu menjawab pertanyaan itu? koleksi tidak boleh kosong jika tidak memiliki elemen di dalamnya.
Martin Verjans

0

Jika saya memeriksa dengan Count () Linq mengeksekusi "SELECT COUNT (*) .." dalam database, tetapi saya perlu memeriksa apakah hasilnya berisi data, saya memutuskan untuk memperkenalkan FirstOrDefault () daripada Count ();

Sebelum

var cfop = from tabelaCFOPs in ERPDAOManager.GetTable<TabelaCFOPs>()

if (cfop.Count() > 0)
{
    var itemCfop = cfop.First();
    //....
}

Setelah

var cfop = from tabelaCFOPs in ERPDAOManager.GetTable<TabelaCFOPs>()

var itemCfop = cfop.FirstOrDefault();

if (itemCfop != null)
{
    //....
}

0
private bool NullTest<T>(T[] list, string attribute)

    {
        bool status = false;
        if (list != null)
        {
            int flag = 0;
            var property = GetProperty(list.FirstOrDefault(), attribute);
            foreach (T obj in list)
            {
                if (property.GetValue(obj, null) == null)
                    flag++;
            }
            status = flag == 0 ? true : false;
        }
        return status;
    }


public PropertyInfo GetProperty<T>(T obj, string str)

    {
        Expression<Func<T, string, PropertyInfo>> GetProperty = (TypeObj, Column) => TypeObj.GetType().GetProperty(TypeObj
            .GetType().GetProperties().ToList()
            .Find(property => property.Name
            .ToLower() == Column
            .ToLower()).Name.ToString());
        return GetProperty.Compile()(obj, str);
    }

0

Inilah implementasi saya atas jawaban Dan Tao, memungkinkan untuk sebuah predikat:

public static bool IsEmpty<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
    if (source == null) throw new ArgumentNullException();
    if (IsCollectionAndEmpty(source)) return true;
    return !source.Any(predicate);
}

public static bool IsEmpty<TSource>(this IEnumerable<TSource> source)
{
    if (source == null) throw new ArgumentNullException();
    if (IsCollectionAndEmpty(source)) return true;
    return !source.Any();
}

private static bool IsCollectionAndEmpty<TSource>(IEnumerable<TSource> source)
{
    var genericCollection = source as ICollection<TSource>;
    if (genericCollection != null) return genericCollection.Count == 0;
    var nonGenericCollection = source as ICollection;
    if (nonGenericCollection != null) return nonGenericCollection.Count == 0;
    return false;
}

-1
List<T> li = new List<T>();
(li.First().DefaultValue.HasValue) ? string.Format("{0:yyyy/MM/dd}", sender.First().DefaultValue.Value) : string.Empty;

-3

myList.ToList().Count == 0. Itu saja


1
Ini adalah ide yang buruk. ToList () tidak boleh digunakan secara berlebihan, karena memaksa enumerable untuk dievaluasi sepenuhnya. Gunakan .Any () sebagai gantinya.
Jon Rea

-5

Metode ekstensi ini berfungsi untuk saya:

public static bool IsEmpty<T>(this IEnumerable<T> enumerable)
{
    try
    {
        enumerable.First();
        return false;
    }
    catch (InvalidOperationException)
    {
        return true;
    }
}

5
Hindari penggunaan pengecualian seperti itu. Dalam kode di atas, Anda mengharapkan pengecualian untuk input tertentu yang terdefinisi dengan baik (yaitu enumerasi kosong). Oleh karena itu, mereka bukanlah pengecualian, mereka adalah aturannya. Itu adalah penyalahgunaan mekanisme kontrol yang berimplikasi pada keterbacaan dan kinerja. Cadangkan penggunaan pengecualian untuk kasus yang benar-benar luar biasa.
Konrad Rudolph

Secara umum, saya setuju. Tapi ini adalah solusi untuk metode IsEmpty yang hilang. Dan saya berpendapat bahwa solusi tidak pernah merupakan cara ideal untuk melakukan sesuatu ... Lebih jauh, terutama dalam kasus ini, maksudnya sangat jelas dan kode "kotor" dikemas dan disembunyikan di tempat yang ditentukan dengan baik.
Jonny Dee

3
-1: Jika Anda ingin melakukannya dengan cara ini, gunakan FirstOrDefault (), seperti dalam jawaban ChulioMartinez.
Daniel Rose

3
Penanganan pengecualian memiliki efisiensi kinerja yang sangat buruk. Jadi ini mungkin solusi terburuk di sini.
Julien N

"Pengecualian harus luar biasa." - jangan menggunakannya untuk aliran program normal.
Jon Rea
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.