LINQ ile bir listenin boş olup olmadığını kontrol etme


122

Bir listenin boş olup olmadığını belirlemenin "en iyi" (hem hızı hem de okunabilirliği hesaba katarak) yolu nedir? Liste türü olsa IEnumerable<T>ve Count özelliği olmasa bile.

Şu anda bunun arasında gidip geliyorum:

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

ve bu:

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

Tahminimce, ikinci seçenek daha hızlıdır, çünkü ilk öğeyi görür görmez bir sonuçla geri dönecektir, oysa ikinci seçeneğin (bir IEnumerable için) sayımı döndürmek için her öğeyi ziyaret etmesi gerekecektir.

Bununla birlikte, ikinci seçenek size okunabilir mi görünüyor? Hangisini tercih edersin? Veya boş bir listeyi test etmenin daha iyi bir yolunu düşünebilir misiniz?

Edit @ lassevk'in yanıtı en mantıklı gibi görünüyor, mümkünse önbelleğe alınmış bir sayımı kullanmak için biraz çalışma zamanı kontrolü ile birleştiğinde, şöyle:

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

    return !list.Any();
}

5
Çok daha iyisi karıştırmayın is, castkullanın asve nullkontrol edin:ICollection<T> collection = list as ICollection<T>; if (collection != null) return colllection.Count;
abatishchev

2
Neden ekstra bir yöntem yazasınız? list.Any()Eşdeğeri değil list.IsEmptymi? Çerçeve yöntemi optimize edilmelidir - ancak bunun mükemmel bir darboğaz olduğunu anladıysanız yeni bir tane yazmaya değer.
dbkk

6
Önerilen uygulamalarda performansı ölçmekle zahmet eden oldu mu yoksa herkes sadece fikir mi atıyor?
Michael Brown 13

IsEmptyUzantı yöntemi ekleyen .NET Core sınıf kitaplığına sorun önerdim . github.com/dotnet/corefx/issues/35054 Lütfen beğenir ve kabul ederseniz kontrol edip oylayın.
RyotaMurohoshi

Yanıtlar:


100

Bunu yapabilirsin:

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

Düzenleme : Temel alınan kaynak gerçekten hızlı bir Count özelliğine sahipse, .Count yöntemini kullanmanın hızlı olacağını unutmayın. Yukarıdaki geçerli bir optimizasyon, birkaç temel türü tespit etmek ve .Any () yaklaşımı yerine bunların .Count özelliğini kullanmak, ancak daha sonra garanti verilemezse .Any () 'ye geri dönmek olacaktır.


4
Veya bir satır kullanın ve dönüş yapın (source == null)? true:! source.Any (); (Bir istisna yapmıyorsanız)
Gage

1
Evet diyebilirim, null için bir istisna at, ama sonra adında ikinci bir uzantı yöntemi ekle IsNullOrEmpty().
devuxer

1
public static Boolean IsNullOrEmpty <T> (bu IEnumerable <T> kaynağı) {return kaynağı == null || ! source.Any (); }
dan

1
@Gage Bugünlerde:return !source?.Any() ?? true;
ricksmt

@ricksmt Güncelleme için teşekkürler! Bunu kesinlikle kullanacağım!
Gage

14

Koda karar verdiğinize dair küçük bir ekleme yapacağım: ayrıca kontrol edin ICollection, çünkü bu bazı eski olmayan jenerik sınıflar tarafından da uygulanıyor (yani, Queue<T>ve Stack<T>). Daha deyimsel olduğu ve daha hızlı olduğu görüldüğü içinas bunun yerine de kullanırdım .is

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
Bu cevabı beğendim. Bir uyarı kelimesi, bazı koleksiyonların NotSupportedExceptionveya gibi bir arabirimi tam olarak uygulamadıklarında istisnalar atacağıdır NotImplementedException. Kod örneğinizi ilk olarak kullandığım bir koleksiyon bulduğumda Count için bir istisna fırlattım (kim bilebilirdi ...).
Sam

1
Tüm öğeleri numaralandırması gereken Count () gibi yöntemler için böyle bir optimizasyonun neden yararlı olduğunu anlıyorum. Ancak Any () 'nin yalnızca en fazla bir öğeyi numaralandırması gerekir, bu yüzden buradaki noktayı göremiyorum. Öte yandan, eklediğiniz yayınlar ve if-ifadeleri, o zaman her görüşmede ödemeniz gereken sabit bir maliyettir.
codymanix

8

LINQ'nun kendisi bir şekilde Count () yöntemi etrafında ciddi bir optimizasyon yapıyor olmalıdır.

Bu seni şaşırttı mı? Ben hayal IListuygulamaları, Countsadece doğrudan ederken elemanların sayısını okur Anysorgulamak zorundadır IEnumerable.GetEnumeratorörneği ve görüşmesi oluşturursa yöntemiMoveNext kez en azından.

/ EDIT @Matt:

Yalnızca IEnumerable için Count () uzantı yönteminin şöyle bir şey yaptığını varsayabilirim:

Evet, tabii ki öyle. Demek istediğim bu. Aslında ICollectionyerine kullanıyor IListama sonuç aynı.


6

Hızlı bir test yazdım, şunu dene:

 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();

İkincisi neredeyse üç kat daha yavaş :)

Kronometre testini bir Yığın veya dizi veya diğer senaryolarla tekrar denemek, gerçekten göründüğü listenin türüne bağlıdır - çünkü Saymanın daha yavaş olduğunu kanıtlarlar.

Sanırım bu, kullandığınız listenin türüne bağlı!

(Sadece belirtmek için Listeye 2000'den fazla nesne koydum ve sayım diğer türlerin aksine daha hızlıydı)


12
Enumerable.Count<T>()için özel kullanımı vardır ICollection<T>. Eğer bir şey ile bu denerseniz diğer bir temel listeye daha ben göreceksiniz bekliyoruz anlamlı farklı (daha yavaş) sonuçları. Any()yine de aynı kalacaktır.
Marc Gravell

2
Marc ile aynı fikirde olmalıyım; bu gerçekten adil bir test değil.
Dan Tao

İçin özel işlem yoktur herhangi bir fikriniz Enumerable.Any<T>()için ICollection<T>? kesinlikle parametresiz özelliği de Any()kontrol edebilir mi? CountICollection<T>
Lukazoid

5

List.CountMicrosoft'un belgelerine göre O (1):
http://msdn.microsoft.com/en-us/library/27b47ht3.aspx

bu yüzden sadece kullan List.Count == 0 bir sorgudan çok daha hızlı

Bunun nedeni, listeye bir şey eklendiğinde veya listeden her kaldırıldığında güncellenen Count adında bir veri üyesine sahip olmasıdır, bu nedenle aradığınızda List.Count, onu almak için her öğeyi yinelemek zorunda kalmaz, yalnızca veri üyesini döndürür.


1
bu bir "IEnumerable" ise hayır. (yeni başlayanlar için IEnumerable'ın "Count" özelliği yoktur, Count () yöntemine sahiptir.). "Count ()" çağrısı, IEnumerable'ın listedeki her bir öğeyi incelemesini gerektirir. Oysa "Herhangi biri" 1 elementi bulur bulmaz geri dönecektir.
00jt

Veri kaynağına bağlıdır. Bir IEnumerable oluşturmak için verim kullanırsanız, boyutunu bilmek için IEnumerable'ı geçmek zorunda kalacaktır. Yani bazı durumlarda sadece O (1) 'dir. Her zaman O (1) değildir.
TamusJRoyce

3

Birden çok öğeniz varsa ikinci seçenek çok daha hızlıdır.

  • Any() 1 öğe bulunduğunda geri döner.
  • Count() tüm listeyi gözden geçirmeye devam etmelidir.

Örneğin, numaralandırmanın 1000 maddeye sahip olduğunu varsayalım.

  • Any() ilkini kontrol eder, sonra doğruya döner.
  • Count() tüm numaralandırmayı geçtikten sonra 1000 döndürür.

Koşul geçersiz kılmalarından birini kullanırsanız, bu potansiyel olarak daha kötüdür - Count (), yalnızca bir eşleşme olsa bile her bir öğeyi kontrol etmek zorundadır.

Herhangi birini kullanmaya alışırsınız - mantıklı ve okunabilir.

Bir uyarı - sadece bir IEnumerable yerine bir Listeniz varsa, o listenin Count özelliğini kullanın.


Any () ve Count () arasındaki farklar açık görünmektedir, ancak @ Crucible'ın profil oluşturma kodu, Count () işlevinin belirli IEnumerable <T> uygulamaları için daha hızlı olduğunu gösteriyor gibi görünüyor. Liste <T> için, liste boyutu binlerce öğede çıkana kadar Count () 'den daha hızlı sonuç veren Any ()' yi alamıyorum. LINQ'nun kendisi bir şekilde Count () yöntemi etrafında ciddi bir optimizasyon yapıyor olmalıdır.
Matt Hamilton

3

@Konrad beni şaşırtan şey, testlerimde listeyi kabul eden bir yönteme geçiriyorum IEnumerable<T>, böylece çalışma zamanı Count () uzantı yöntemini çağırarak onu optimize edemez IList<T>.

Yalnızca IEnumerable için Count () uzantı yönteminin şöyle bir şey yaptığını varsayabilirim:

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;
}

... başka bir deyişle, özel durum için biraz çalışma zamanı optimizasyonu IList<T> .

/ EDIT @Konrad +1 mat - haklısınız daha büyük olasılıkla ICollection<T> .


1

Tamam, peki ya bu?

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

DÜZENLEME: Birisinin bu çözümü zaten çizdiğini fark ettim. Any () yönteminin bunu yapacağından bahsedildi, ancak neden kendiniz yapmıyorsunuz? Saygılarımızla


3
ANCAK, onu bir usingbloğun içine düzgün bir şekilde yerleştirdiğinizde daha az özlü hale gelir, çünkü aksi takdirde bir IDisposablenesne oluşturup sonra onu terk etmişsinizdir. Sonra, elbette, zaten var olan uzatma yöntemini kullandığınızda ve onu değiştirdiğinizde (tam olarak bunu yapar) daha kısa olur return !enumerable.Any().
Dan Tao

Zaten var olan bir yöntemi neden yeniden yazmak? Bahsedildiği gibi Any()tam olarak bunu gerçekleştirir, bu nedenle tam olarak aynı yöntemi başka bir adla eklemek kafa karıştırıcı olacaktır.
Julien N

1

Diğer bir fikir:

if(enumerable.FirstOrDefault() != null)

Ancak Any () yaklaşımını daha çok seviyorum.


3
Ya ilk öğenin null olduğu boş olmayan bir listeniz varsa?
Ekevoo

1

Bu, Entity Framework ile çalışmasını sağlamak için kritikti:

var genericCollection = list as ICollection<T>;

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

Bu soruya nasıl cevap veriyor? koleksiyon, içinde hiçbir öğe bulunmadığında boş olamaz.
Martin Verjans

0

Count () Linq ile kontrol edersem, veritabanında bir "SELECT COUNT (*) .." çalıştırır, ancak sonuçların veri içerip içermediğini kontrol etmem gerekir, Count () yerine FirstOrDefault () kullanmaya karar verdim;

Önce

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

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

Sonra

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

İşte benim Dan Tao'nun cevabını uyguladığım, bir dayanağa izin veren:

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. Bu kadar


1
Bu berbat bir fikir. ToList (), numaralandırılabilirliği tam olarak değerlendirilmeye zorladığından fazla kullanılmamalıdır. Bunun yerine .Any () kullanın.
Jon Rea

-5

Bu uzantı yöntemi benim için çalışıyor:

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

5
Bu tür istisnaların kullanımından kaçının. Yukarıdaki kodda, belirli, iyi tanımlanmış girdiler (yani boş numaralandırmalar) için bir istisna beklersiniz . Dolayısıyla onlar istisna değil, kuraldır. Bu, okunabilirlik ve performans üzerinde etkileri olan bu kontrol mekanizmasının kötüye kullanılmasıdır. Gerçekten istisnai durumlar için istisnaların kullanımını rezerve edin.
Konrad Rudolph

Genel olarak katılıyorum. Ancak bu, karşılık gelen eksik bir IsEmpty yöntemi için geçici bir çözümdür. Ve bir geçici çözümün bir şeyi yapmanın ideal yolu olmadığını iddia ediyorum ... Dahası, özellikle bu durumda, amaç çok açık ve "kirli" kod, iyi tanımlanmış bir yerde kapsüllenmiş ve saklanmış.
Jonny Dee

3
-1: Bu şekilde yapmak istiyorsanız, ChulioMartinez'in cevabında olduğu gibi FirstOrDefault () kullanın.
Daniel Rose

3
İstisna işleme gerçekten zayıf performans verimliliğine sahiptir. Yani buradaki en kötü çözüm bu olabilir.
Julien N

"İstisnalar istisnai olmalıdır." - bunları normal program akışı için kullanmayın.
Jon Rea
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.