Перевірка, чи список порожній за допомогою LINQ


122

Який "найкращий" (враховуючи швидкість і читабельність) спосіб визначити, чи список порожній? Навіть якщо список має тип 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();
}

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 буде швидким, якщо базове джерело насправді має властивість швидкого підрахунку. Дійсною оптимізацією, описаною вище, було б виявити кілька базових типів і просто використовувати властивість .Count з них замість підходу .Any (), але потім повернутися до.


4
Або використовувати один рядок і повернутись (source == null)? true:! source.Any (); (Якщо ви не кидаєте винятку)
Gage

1
Я б сказав, так, киньте виняток для null, але потім додайте другий метод розширення, який називається IsNullOrEmpty().
devuxer

1
загальнодоступна статична булева IsNullOrEmpty <T> (це джерело IE численні <T>) {return source == null || ! source.Any (); }
дан

1
@Gage У наш час: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. Я вперше застосував ваш приклад коду, коли я дізнався, що колекція, яку я використовував, кинула виняток для Count (хто знав ...).
Сем

1
Я розумію, чому така оптимізація корисна для таких методів, як Count (), які потребують перерахування всіх елементів. Але Any () потребує перерахування максимум одного елемента, тому я не бачу сенсу тут. З іншого боку, трансляції та додатки, які ви додаєте, - це фіксована вартість, яку ви повинні сплачувати за кожен дзвінок тоді.
кодиманікс

8

LINQ сам повинен якось робити серйозну оптимізацію навколо методу Count ().

Це вас дивує? Я думаю, що для IListреалізацій Countпросто читає кількість елементів безпосередньо, в той час як Anyмає запитувати IEnumerable.GetEnumeratorметод, створити екземпляр і викликати MoveNextхоча б один раз.

/ EDIT @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();

Другий майже втричі повільніше :)

Повторне спробу тестування секундоміра за допомогою стека чи масиву чи інших сценаріїв, це дійсно залежить від типу списку, який здається - адже вони виявляють, що кількість рахунків повільніше.

Тож я думаю, це залежить від типу списку, який ви використовуєте!

(Тільки для того, щоб зазначити, я помістив до списку 2000+ об'єктів, і підрахунок все ще був швидшим, навпаки іншим типам)


12
Enumerable.Count<T>()має спеціальну обробку для ICollection<T>. Якщо ви спробуєте це з чимось іншим, ніж основний список, я сподіваюся, що ви побачите значно інші (повільніші) результати. Any()залишиться приблизно однаковою.
Марк Гравелл

2
Я повинен погодитися з Марком; це не справді чесне випробування.
Дан Тао,

Будь-яка ідея , чому не існує спеціальна обробка Enumerable.Any<T>()для ICollection<T>? напевно, параметри Any()можуть просто перевірити Countвластивість ICollection<T>?
Луказоїд

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 вивчити кожен окремий елемент у списку. Тоді як "Будь-який" повернеться, як тільки знайде 1 елемент.
00jt

Це залежить від джерела даних. Якщо ви використовуєте урожай для створення IEnumerable, йому доведеться пройти IEnumerable, щоб знати, що його розмір. Отже, це лише O (1) в деяких випадках. Це не завжди O (1).
TamusJRoyce

3

Другий варіант набагато швидше, якщо у вас є кілька предметів.

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

Наприклад, припустимо, що в перерахунку було 1000 пунктів.

  • Any() перевірив би перший, а потім поверне справжній.
  • Count() поверне 1000 після проходження всього перерахунку.

Це потенційно гірше, якщо ви використовуєте одне з переваг предикатів - Count () все одно повинен перевіряти кожен окремий елемент, навіть якщо це лише одна відповідність.

Ви звикаєте до використання будь-якого - це має сенс і читається.

Одне застереження - якщо у вас є Список, а не просто IEnumerable, використовуйте властивість Count цього списку.


Різниці між Any () та Count () здаються очевидними, але, як видається, профілюючий код @ crucible вказує на те, що Count () швидше для певних реалізацій IEnumerable <T>. Для списку <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();
}

EDIT: Я щойно зрозумів, що хтось уже замальовував це рішення. Згадувалося, що метод Any () зробить це, але чому б не зробити цього самостійно? З повагою


3
Але НЕ стає менш лаконічним, коли ви належним чином укладете його в usingблок, оскільки в іншому випадку ви сконструювали IDisposableоб'єкт, а потім відмовились від нього. Тоді, звичайно, це стає більш лаконічним, коли ви використовуєте метод розширення, який вже є, і просто змінюєте його на return !enumerable.Any()(що робить саме це).
Дан Тао,

Навіщо переписувати вже існуючий метод? Як вже було сказано, Any()саме це робить, тому додавання точно такого ж методу до іншого імені буде просто заплутаним.
Julien N

1

Ще одна ідея:

if(enumerable.FirstOrDefault() != null)

Однак мені більше подобається будь-який () підхід.


3
Що робити, якщо у вас є не порожній список, у якому перший елемент є нульовим?
Екеву

1

Це було надзвичайно важливим для того, щоб це працювало з Entity Framework:

var genericCollection = list as ICollection<T>;

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

Як це відповідає на питання? колекція може бути недійсною, не маючи елементів всередині неї.
Martin Verjans

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

-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. Це все


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.
Даніель Роуз

3
Обробка винятків має дуже низьку ефективність роботи. Тож це може бути найгірше рішення тут.
Julien N

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