Vérifier si une liste est vide avec LINQ

122

Quelle est la «meilleure» façon (en tenant compte à la fois de la vitesse et de la lisibilité) pour déterminer si une liste est vide? Même si la liste est de type IEnumerable<T>et n'a pas de propriété Count.

En ce moment, je me balance entre ceci:

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

et ça:

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

Je suppose que la deuxième option est plus rapide, car elle reviendra avec un résultat dès qu'elle verra le premier élément, tandis que la deuxième option (pour un IEnumerable) devra visiter chaque élément pour renvoyer le décompte.

Cela étant dit, la deuxième option vous paraît-elle aussi lisible? Lequel préférez-vous? Ou pouvez-vous penser à une meilleure façon de tester une liste vide?

La réponse de Edit @ lassevk semble être la plus logique, associée à un peu de vérification à l'exécution pour utiliser un nombre mis en cache si possible, comme ceci:

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

    return !list.Any();
}
Matt Hamilton
la source
5
Beaucoup plus mieux ne pas mélanger iset castmais utiliser aset nullvérifier:ICollection<T> collection = list as ICollection<T>; if (collection != null) return colllection.Count;
abatishchev
2
Pourquoi écrire une méthode supplémentaire? N'est-ce pas list.Any()équivalent à list.IsEmpty? La méthode du framework doit être optimisée - cela ne vaut la peine d'en écrire une nouvelle que si vous avez compris que c'est un goulot d'étranglement de performance.
dbkk
6
Quelqu'un a-t-il pris la peine de mesurer les performances des implémentations suggérées ou tout le monde ne fait-il que jeter des idées?
Michael Brown
J'ai suggéré un problème à la bibliothèque de classes .NET Core qui ajoute une IsEmptyméthode d'extension. github.com/dotnet/corefx/issues/35054 S'il vous plaît vérifier et voter si vous aimez et acceptez.
RyotaMurohoshi

Réponses:

100

Vous pouvez faire ceci:

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

Modifier : notez que la simple utilisation de la méthode .Count sera rapide si la source sous-jacente possède en fait une propriété Count rapide. Une optimisation valide ci-dessus serait de détecter quelques types de base et d'utiliser simplement la propriété .Count de ceux-ci, au lieu de l'approche .Any (), puis de revenir à .Any () si aucune garantie ne peut être faite.

Lasse V. Karlsen
la source
4
Ou utiliser une ligne et retourner (source == null)? vrai:! source.Any (); (Si vous ne lancez pas d'exception)
Gage
1
Je dirais, oui, lève une exception pour null, mais ajoute ensuite une deuxième méthode d'extension appelée IsNullOrEmpty().
devuxer
1
public static Boolean IsNullOrEmpty <T> (cette source IEnumerable <T>) {return source == null || ! source.Any (); }
dan
1
@Gage Nowadays:return !source?.Any() ?? true;
ricksmt
@ricksmt Merci pour la mise à jour! Je vais certainement l'utiliser!
Gage le
14

Je ferais un petit ajout au code sur lequel vous semblez avoir choisi: vérifiez également ICollection, car cela est également implémenté par certaines classes génériques non obsolètes (c'est-à-dire Queue<T>et Stack<T>). J'utiliserais également asau lieu de iscar il est plus idiomatique et s'est avéré plus rapide .

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();
}
Dan Tao
la source
1
J'aime cette réponse. Un mot d'avertissement est que certaines collections lèvent des exceptions lorsqu'elles n'implémentent pas complètement une interface telle que NotSupportedExceptionou NotImplementedException. J'ai utilisé votre exemple de code pour la première fois lorsque j'ai découvert qu'une collection que j'utilisais a jeté une exception pour Count (qui savait ...).
Sam
1
Je comprends pourquoi une telle optimisation est utile pour des méthodes comme Count () qui doivent énumérer tous les éléments. Mais Any () n'a besoin que d'énumérer au plus un élément, donc je ne vois pas le point ici. D'autre part, les lancers et les déclarations if que vous ajoutez sont un coût fixe que vous devez payer à chaque appel.
codymanix
8

LINQ lui-même doit faire une optimisation sérieuse autour de la méthode Count () d'une manière ou d'une autre.

Cela vous surprend-il? J'imagine que pour les IListimplémentations, Countlit simplement le nombre d'éléments directement tout Anyen interrogeant la IEnumerable.GetEnumeratorméthode, créez une instance et appelez MoveNextau moins une fois.

/ EDIT @Matt:

Je ne peux que supposer que la méthode d'extension Count () pour IEnumerable fait quelque chose comme ceci:

Oui, bien sûr. C'est ce que je voulais dire. En fait, il utilise à la ICollectionplace de IListmais le résultat est le même.

Konrad Rudolph
la source
6

Je viens de rédiger un test rapide, essayez ceci:

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

Le second est presque trois fois plus lent :)

Essayer à nouveau le test du chronomètre avec une pile ou un tableau ou d'autres scénarios, cela dépend vraiment du type de liste qu'il semble - car ils prouvent que Count est plus lent.

Donc je suppose que cela dépend du type de liste que vous utilisez!

(Juste pour souligner, j'ai mis plus de 2000 objets dans la liste et le comptage était encore plus rapide, contrairement aux autres types)

creuset
la source
12
Enumerable.Count<T>()a un traitement spécial pour ICollection<T>. Si vous essayez ceci avec autre chose qu'une liste de base, j'espère que vous verrez des résultats significativement différents (plus lents). Any()restera à peu près le même, cependant.
Marc Gravell
2
Je dois être d'accord avec Marc; ce n'est pas un test vraiment juste.
Dan Tao
Une idée pourquoi il n'y a pas de traitement spécial Enumerable.Any<T>()pour ICollection<T>? Sûrement le sans paramètre Any()pourrait simplement vérifier la Countpropriété ICollection<T>aussi?
Lukazoid
5

List.Countest O (1) selon la documentation de Microsoft:
http://msdn.microsoft.com/en-us/library/27b47ht3.aspx

alors utilise juste List.Count == 0 c'est beaucoup plus rapide qu'une requête

En effet, il a un membre de données appelé Count qui est mis à jour chaque fois que quelque chose est ajouté ou supprimé de la liste, de sorte que lorsque vous l'appelez, List.Countil n'a pas à parcourir chaque élément pour l'obtenir, il renvoie simplement le membre de données.

Dasmowenator
la source
1
si c'est un "IEnumerable" alors non. (pour les débutants, IEnumerable n'a pas de propriété "Count", il a une méthode Count ().). L'appel de "Count ()" exigera que IEnumerable examine chaque élément de la liste. Alors que "Any" retournera juste dès qu'il trouvera 1 élément.
00jt
Cela dépend de la source de données. Si vous utilisez yield pour créer un IEnumerable, il devra traverser le IEnumerable pour connaître sa taille. Ce n'est donc que O (1) dans certains cas. Ce n'est pas toujours O (1).
TamusJRoyce
3

La deuxième option est beaucoup plus rapide si vous avez plusieurs éléments.

  • Any() retourne dès qu'un article est trouvé.
  • Count() doit continuer à parcourir toute la liste.

Par exemple, supposons que l'énumération contienne 1000 éléments.

  • Any() vérifierait le premier, puis retournerait vrai.
  • Count() retournerait 1000 après avoir parcouru toute l'énumération.

C'est potentiellement pire si vous utilisez l'un des remplacements de prédicat - Count () doit toujours vérifier chaque élément, même s'il n'y a qu'une seule correspondance.

Vous vous habituez à utiliser Any one - cela a du sens et est lisible.

Une mise en garde - si vous avez une liste, plutôt qu'un simple IEnumerable, utilisez la propriété Count de cette liste.

Keith
la source
Les différences entre Any () et Count () semblent claires, mais le code de profilage de @ crucible semble indiquer que Count () est plus rapide pour certaines implémentations de IEnumerable <T>. Pour List <T>, je ne peux pas obtenir Any () pour donner un résultat plus rapide que Count () jusqu'à ce que la taille de la liste atteigne des milliers d'éléments. LINQ lui-même doit faire une optimisation sérieuse autour de la méthode Count () d'une manière ou d'une autre.
Matt Hamilton le
3

@Konrad ce qui me surprend, c'est que dans mes tests, je passe la liste dans une méthode qui accepte IEnumerable<T>, donc le runtime ne peut pas l'optimiser en appelant la méthode d'extension Count () pour IList<T>.

Je ne peux que supposer que la méthode d'extension Count () pour IEnumerable fait quelque chose comme ceci:

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

... en d'autres termes, un peu d'optimisation du runtime pour le cas particulier de IList<T>.

/ EDIT @Konrad +1 compagnon - vous avez raison, il est plus probable que vous soyez allumé ICollection<T>.

Matt Hamilton
la source
1

Ok, alors qu'en est-il de celui-ci?

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

EDIT: Je viens de réaliser que quelqu'un a déjà esquissé cette solution. Il a été mentionné que la méthode Any () le fera, mais pourquoi ne pas le faire vous-même? Cordialement

Jonny Dee
la source
3
MAIS cela devient moins succinct lorsque vous l'enfermez correctement dans un usingbloc, sinon vous avez construit un IDisposableobjet et l' avez ensuite abandonné. Ensuite, bien sûr, cela devient plus succinct lorsque vous utilisez la méthode d'extension qui existe déjà et que vous la modifiez simplement en return !enumerable.Any()(ce qui fait précisément cela).
Dan Tao
Pourquoi réécrire une méthode déjà existante? Comme mentionné, cela Any()exécute exactement cela, donc ajouter exactement la même méthode avec un autre nom sera juste déroutant.
Julien N
1

Une autre idée:

if(enumerable.FirstOrDefault() != null)

Cependant, j'aime plus l'approche Any ().

ChulioMartinez
la source
3
Que faire si vous avez une liste non vide dans laquelle le premier élément est nul?
Ekevoo
1

C'était essentiel pour que cela fonctionne avec Entity Framework:

var genericCollection = list as ICollection<T>;

if (genericCollection != null)
{
   //your code 
}
Holt Mansfield
la source
Comment cela répond-il à la question? la collection peut être non nulle sans avoir d'éléments à l'intérieur.
Martin Verjans le
0

Si je vérifie avec Count () Linq exécute un "SELECT COUNT (*) .." dans la base de données, mais que je dois vérifier si les résultats contiennent des données, j'ai résolu d'introduire FirstOrDefault () au lieu de Count ();

Avant

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

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

Après

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

var itemCfop = cfop.FirstOrDefault();

if (itemCfop != null)
{
    //....
}
Gandarez
la source
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);
    }
Suneelsarraf
la source
0

Voici ma mise en œuvre de la réponse de Dan Tao, permettant un prédicat:

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;
}
devuxer
la source
-1
List<T> li = new List<T>();
(li.First().DefaultValue.HasValue) ? string.Format("{0:yyyy/MM/dd}", sender.First().DefaultValue.Value) : string.Empty;
Milad Sadeghi
la source
-3

myList.ToList().Count == 0. C'est tout

user3149517
la source
1
C'est une mauvaise idée. ToList () ne doit pas être surutilisé, car il force l'énumérable à être entièrement évalué. Utilisez plutôt .Any ().
Jon Rea
-5

Cette méthode d'extension fonctionne pour moi:

public static bool IsEmpty<T>(this IEnumerable<T> enumerable)
{
    try
    {
        enumerable.First();
        return false;
    }
    catch (InvalidOperationException)
    {
        return true;
    }
}
Jonny Dee
la source
5
Évitez une telle utilisation des exceptions. Dans le code ci-dessus, vous attendez une exception pour certaines entrées bien définies (c'est-à-dire des énumérations vides). Par conséquent, ils ne font pas exception, ils sont la règle. C'est un abus de ce mécanisme de contrôle qui a des implications sur la lisibilité et les performances. Réservez le recours aux exceptions pour des cas vraiment exceptionnels.
Konrad Rudolph
En général, je suis d'accord. Mais c'est une solution de contournement pour une méthode IsEmpty manquante correspondante. Et je dirais qu'une solution de contournement n'est jamais le moyen idéal de faire quelque chose ... De plus, surtout dans ce cas, l'intention est très claire et le code «sale» est encapsulé et caché dans un endroit bien défini.
Jonny Dee
3
-1: Si vous voulez le faire de cette façon, utilisez FirstOrDefault (), comme dans la réponse de ChulioMartinez.
Daniel Rose
3
La gestion des exceptions a une efficacité de performance vraiment médiocre. C'est peut-être la pire solution ici.
Julien N
"Les exceptions devraient être exceptionnelles." - ne les utilisez pas pour le déroulement normal du programme.
Jon Rea