Проверка, если список пуст с LINQ - PullRequest
122 голосов
/ 03 сентября 2008

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

Прямо сейчас я перебираю это:

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

и это:

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

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

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

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

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

    return !list.Any();
}

Ответы [ 16 ]

99 голосов
/ 03 сентября 2008

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

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

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

14 голосов
/ 26 августа 2010

Я бы сделал одно небольшое дополнение к коду, на котором вы, похоже, остановились: проверьте также на 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();
}
8 голосов
/ 03 сентября 2008

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

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

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

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

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

6 голосов
/ 03 сентября 2008

Я только что написал быстрый тест, попробуйте это:

 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 объектов в Список, и подсчет был еще быстрее, в отличие от других типов)

4 голосов
/ 16 августа 2010

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

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

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

3 голосов
/ 03 сентября 2008

@ Конрад меня удивляет то, что в моих тестах я передаю список в метод, который принимает 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 mate - вы правы в том, что вы, скорее всего, находитесь на ICollection<T>.

3 голосов
/ 03 сентября 2008

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

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

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

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

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

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

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

1 голос
/ 16 декабря 2011

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

var genericCollection = list as ICollection<T>;

if (genericCollection != null)
{
   //your code 
}
1 голос
/ 21 декабря 2009

Другая идея:

if(enumerable.FirstOrDefault() != null)

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

1 голос
/ 26 октября 2009

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...