Проверка пустого списка с помощью LINQ


122

Какой "лучший" (принимая во внимание скорость и удобочитаемость) способ определить, пуст ли список? Даже если список относится к типу IEnumerable<T>и не имеет свойства Count.

Прямо сейчас я бросаюсь между этим:

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

и это:

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

Я предполагаю, что второй вариант быстрее, так как он вернется с результатом, как только увидит первый элемент, тогда как второй вариант (для IEnumerable) должен будет посетить каждый элемент, чтобы вернуть счет.

При этом второй вариант кажется вам читаемым? Что бы вы предпочли? Или вы можете придумать лучший способ проверить пустой список?

Ответ Edit @ lassevk кажется наиболее логичным, в сочетании с небольшой проверкой времени выполнения, чтобы использовать кешированный счетчик, если это возможно, например:

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

    return !list.Any();
}

5
Гораздо лучше не смешивать isи castно использование asи nullпроверить:ICollection<T> collection = list as ICollection<T>; if (collection != null) return colllection.Count;
abatishchev

2
Зачем писать лишний метод? Не list.Any()эквивалентно list.IsEmpty? Следует оптимизировать метод фреймворка - писать новый стоит только в том случае, если вы поняли, что это узкое место в производительности.
dbkk

6
Кто-нибудь потрудился измерить производительность предложенных реализаций, или все просто выкидывают идеи?
Майкл Браун

Я предложил проблему с библиотекой классов .NET Core, которая добавляет IsEmptyметод расширения. github.com/dotnet/corefx/issues/35054 Пожалуйста, проверьте и проголосуйте, если хотите и согласны.
RyotaMurohoshi

Ответы:


100

Вы могли сделать это:

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

Изменить : обратите внимание, что простое использование метода .Count будет быстрым, если у базового источника действительно есть свойство fast Count. Допустимая оптимизация, приведенная выше, будет заключаться в обнаружении нескольких базовых типов и простом использовании их свойства .Count вместо подхода .Any (), но затем откат к .Any (), если нет гарантии.


4
Или использовать одну строку и вернуть (source == null)? истина:! source.Any (); (Если вы не выбрасываете исключение)
Гейдж

1
Я бы сказал, да, выбросить исключение для null, но затем добавить второй вызываемый метод расширения IsNullOrEmpty().
devuxer

1
public static Boolean IsNullOrEmpty <T> (этот IEnumerable <T> источник) {return source == null || ! source.Any (); }
дан

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

@ricksmt Спасибо за обновление! Я обязательно этим воспользуюсь!
Gage

14

Я бы сделал одно небольшое дополнение к коду, на котором вы, кажется, остановились: проверьте также ICollection, поскольку это реализовано даже некоторыми не устаревшими универсальными классами (например, Queue<T>и Stack<T>). Я бы также использовал asвместо этого, 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
Мне нравится этот ответ. Одно предупреждение: некоторые коллекции будут генерировать исключения, если они не полностью реализуют интерфейс, такой как NotSupportedExceptionили NotImplementedException. Я впервые использовал ваш пример кода, когда обнаружил, что коллекция, которую я использую, вызвала исключение для графа (кто знал ...).
Сэм

1
Я понимаю, почему такая оптимизация полезна для таких методов, как Count (), которым необходимо перечислить все элементы. Но Any () нужно перечислить не более одного элемента, поэтому я не вижу здесь сути. С другой стороны, приведение типов и добавляемые операторы if представляют собой фиксированную стоимость, которую вы должны платить за каждый вызов.
codymanix

8

Сам LINQ должен как-то серьезно оптимизировать метод Count ().

Вас это удивляет? Я предполагаю, что для IListреализаций Countпросто считывает количество элементов напрямую, при этом Anyон должен запросить IEnumerable.GetEnumeratorметод, создать экземпляр и вызвать MoveNextхотя бы один раз.

/ РЕДАКТИРОВАТЬ @Matt:

Я могу только предположить, что метод расширения Count () для IEnumerable делает что-то вроде этого:

Да, конечно. Вот что я имел в виду. На самом деле он использует ICollectionвместо, IListно результат тот же.


6

Я только что написал небольшой тест, попробуйте следующее:

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

Второй почти в три раза медленнее :)

При повторной попытке теста секундомера со стеком или массивом или в других сценариях это действительно зависит от типа списка, который кажется, потому что они доказывают, что Count работает медленнее.

Думаю, это зависит от типа списка, который вы используете!

(Чтобы отметить, я поместил 2000+ объектов в список, и счет был еще быстрее, в отличие от других типов)


12
Enumerable.Count<T>()имеет специальную обработку для ICollection<T>. Если вы попробуете это с чем-то другим, кроме базового списка, я ожидаю, что вы увидите значительно другие (более медленные) результаты. Any()останется примерно таким же.
Марк Грейвелл

2
Я должен согласиться с Марком; это не совсем честный тест.
Дэн Тао

Есть идеи, почему для Enumerable.Any<T>()for нет специальной обработки ICollection<T>? конечно, без параметров Any()можно просто проверить Countсвойство ICollection<T>?
Lukazoid

5

List.Countравно O (1) согласно документации Microsoft:
http://msdn.microsoft.com/en-us/library/27b47ht3.aspx

так что просто используйте List.Count == 0его намного быстрее, чем запрос

Это связано с тем, что у него есть элемент данных с именем Count, который обновляется каждый раз, когда что-то добавляется или удаляется из списка, поэтому, когда вы вызываете List.Countего, не нужно перебирать каждый элемент для его получения, он просто возвращает элемент данных.


1
если это "IEnumerable", то нет. (для начала IEnumerable не имеет свойства "Count", у него есть метод Count ().) Вызов «Count ()» потребует от IEnumerable проверки каждого отдельного элемента в списке. Тогда как "Any" просто вернется, как только найдет 1 элемент.
00jt 08

Это зависит от источника данных. Если вы используете yield для создания IEnumerable, ему придется пройти через IEnumerable, чтобы узнать его размер. Так что в некоторых случаях это только O (1). Это не всегда O (1).
TamusJRoyce

3

Второй вариант будет намного быстрее, если у вас несколько предметов.

  • Any() возвращается, как только будет найден 1 предмет.
  • Count() должен продолжать просматривать весь список.

Например, предположим, что в перечислении было 1000 элементов.

  • Any() проверит первый, а затем вернет true.
  • Count() вернет 1000 после прохождения всего перечисления.

Это потенциально хуже, если вы используете одно из переопределений предиката - Count () все равно должен проверять каждый элемент, даже если есть только одно совпадение.

Вы привыкаете использовать Any one - он имеет смысл и удобочитаем.

Одно предостережение - если у вас есть List, а не просто IEnumerable, используйте свойство Count этого списка.


Различия между Any () и Count () кажутся очевидными, но код профилирования @ crucible, похоже, указывает на то, что Count () быстрее для некоторых реализаций IEnumerable <T>. Для List <T> я не могу заставить Any () давать более быстрый результат, чем Count (), пока размер списка не достигнет тысяч элементов. Сам LINQ должен как-то серьезно оптимизировать метод Count ().
Мэтт Гамильтон,

3

@Konrad меня удивляет то, что в своих тестах я передаю список в метод, который принимает IEnumerable<T>, поэтому среда выполнения не может оптимизировать его, вызывая метод расширения Count () для IList<T>.

Я могу только предположить, что метод расширения Count () для IEnumerable делает что-то вроде этого:

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

... другими словами, небольшая оптимизация времени выполнения для особого случая IList<T> .

/ EDIT @Konrad +1 приятель - вы правы, скорее всего, он включен ICollection<T>.


1

Хорошо, а что насчет этого?

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

РЕДАКТИРОВАТЬ: Я только что понял, что кто-то уже набросал это решение. Было упомянуто, что это сделает метод Any (), но почему бы не сделать это самому? С уважением


3
НО он становится менее лаконичным, когда вы правильно заключаете его в usingблок, поскольку в противном случае вы построили IDisposableобъект, а затем отказались от него. Затем, конечно, он станет более лаконичным, если вы воспользуетесь уже существующим методом расширения и просто измените его на return !enumerable.Any()(который делает именно это).
Дэн Тао

Зачем переписывать уже существующий метод? Как уже упоминалось, Any()именно это и выполняется, поэтому добавление точно такого же метода с другим именем просто сбивает с толку.
Julien N

1

Еще одна идея:

if(enumerable.FirstOrDefault() != null)

Однако мне больше нравится подход Any ().


3
Что делать, если у вас есть непустой список, в котором первый элемент равен нулю?
Ekevoo

1

Это было критически важно, чтобы заставить это работать с Entity Framework:

var genericCollection = list as ICollection<T>;

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

Как это отвечает на вопрос? коллекция не может быть нулевой, если внутри нее нет элементов.
Martin Verjans 04

0

Если я проверяю с помощью Count (), Linq выполняет «SELECT COUNT (*) ..» в базе данных, но мне нужно проверить, содержат ли результаты данные, я решил ввести FirstOrDefault () вместо Count ();

Перед

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

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

После

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

Вот моя реализация ответа Дэна Тао с учетом предиката:

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


-3

myList.ToList().Count == 0, Вот и все


1
Это ужасная идея. ToList () не следует злоупотреблять, так как он заставляет перечислимое значение быть полностью вычисленным. Вместо этого используйте .Any ().
Джон Ри

-5

У меня работает этот метод расширения:

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

5
Избегайте такого использования исключений. В приведенном выше коде вы ожидаете исключения для определенных четко определенных входов (например, пустых перечислений). Следовательно, они не исключение, это правило. Это злоупотребление этим механизмом управления, которое влияет на удобочитаемость и производительность. Зарезервируйте использование исключений для действительно исключительных случаев.
Конрад Рудольф

В общем, соглашусь. Но это обходной путь для соответствующего отсутствующего метода IsEmpty. И я бы сказал, что обходной путь никогда не является идеальным способом что-то сделать ... Более того, особенно в этом случае, цель очень ясна, а «грязный» код инкапсулирован и спрятан в четко определенном месте.
Джонни Ди,

3
-1: Если вы хотите сделать это таким образом, используйте FirstOrDefault (), как в ответе ChulioMartinez.
Daniel Rose

3
Обработка исключений имеет действительно низкую эффективность. Так что это может быть худшим решением.
Julien N

«Исключения должны быть исключительными». - не используйте их для нормального выполнения программы.
Джон Ри
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.