Filtrage des collections en C #

142

Je recherche un moyen très rapide de filtrer une collection en C #. J'utilise actuellement des collections génériques List <object>, mais je suis ouvert à l'utilisation d'autres structures si elles fonctionnent mieux.

Actuellement, je suis en train de créer une nouvelle liste <objet> et de parcourir la liste d'origine. Si les critères de filtrage correspondent, j'en mets une copie dans la nouvelle liste.

Y a-t-il une meilleure manière de faire cela? Existe-t-il un moyen de filtrer en place afin qu'aucune liste temporaire ne soit requise?

Jason Z
la source
Cela va être incroyablement rapide. Cela ralentit-il votre système? Est-ce une liste énorme ? Sinon, je ne m'inquiéterais pas.
Iain Holder

Réponses:

237

Si vous utilisez C # 3.0, vous pouvez utiliser linq, bien meilleur et bien plus élégant:

List<int> myList = GetListOfIntsFromSomewhere();

// This will filter out the list of ints that are > than 7, Where returns an
// IEnumerable<T> so a call to ToList is required to convert back to a List<T>.
List<int> filteredList = myList.Where( x => x > 7).ToList();

Si vous ne trouvez pas le .Where, cela signifie que vous devez importer using System.Linq;en haut de votre fichier.

Jorge Córdoba
la source
19
La méthode d'extension Where renvoie IEnumerable <T>, pas List <T>. Cela devrait être: myList.Where (x => x> 7) .ToList ()
Rafa Castaneda
1
Comment cela fonctionne pour le filtrage par chaînes. Comme trouver tous les éléments dans une liste de chaînes commençant par "ch"
joncodo
2
@JonathanO Vous pouvez utiliser des méthodes à l'intérieur du Func. listOfStrings.Where (s => s.StartsWith ("ch")). ToList ();
Mike G
1
Existe-t-il un moyen d'objectiver les requêtes linq? Par exemple, pour utiliser .Where(predefinedQuery)au lieu d'utiliser .Where(x => x > 7)?
XenoRo
2
@AlmightyR: Définissez-le simplement comme une méthode qui prend un argument. Ex: public bool predefinedQuery(int x) { return x > 7; }. Ensuite, votre .Where(predefinedQuery)fonctionnerait très bien.
Don
21

Voici un bloc de code / exemple de filtrage de liste à l'aide de trois méthodes différentes que j'ai rassemblées pour afficher le filtrage de liste basé sur Lambdas et LINQ.

#region List Filtering

static void Main(string[] args)
{
    ListFiltering();
    Console.ReadLine();
}

private static void ListFiltering()
{
    var PersonList = new List<Person>();

    PersonList.Add(new Person() { Age = 23, Name = "Jon", Gender = "M" }); //Non-Constructor Object Property Initialization
    PersonList.Add(new Person() { Age = 24, Name = "Jack", Gender = "M" });
    PersonList.Add(new Person() { Age = 29, Name = "Billy", Gender = "M" });

    PersonList.Add(new Person() { Age = 33, Name = "Bob", Gender = "M" });
    PersonList.Add(new Person() { Age = 45, Name = "Frank", Gender = "M" });

    PersonList.Add(new Person() { Age = 24, Name = "Anna", Gender = "F" });
    PersonList.Add(new Person() { Age = 29, Name = "Sue", Gender = "F" });
    PersonList.Add(new Person() { Age = 35, Name = "Sally", Gender = "F" });
    PersonList.Add(new Person() { Age = 36, Name = "Jane", Gender = "F" });
    PersonList.Add(new Person() { Age = 42, Name = "Jill", Gender = "F" });

    //Logic: Show me all males that are less than 30 years old.

    Console.WriteLine("");
    //Iterative Method
    Console.WriteLine("List Filter Normal Way:");
    foreach (var p in PersonList)
        if (p.Gender == "M" && p.Age < 30)
            Console.WriteLine(p.Name + " is " + p.Age);

    Console.WriteLine("");
    //Lambda Filter Method
    Console.WriteLine("List Filter Lambda Way");
    foreach (var p in PersonList.Where(p => (p.Gender == "M" && p.Age < 30))) //.Where is an extension method
        Console.WriteLine(p.Name + " is " + p.Age);

    Console.WriteLine("");
    //LINQ Query Method
    Console.WriteLine("List Filter LINQ Way:");
    foreach (var v in from p in PersonList
                      where p.Gender == "M" && p.Age < 30
                      select new { p.Name, p.Age })
        Console.WriteLine(v.Name + " is " + v.Age);
}

private class Person
{
    public Person() { }
    public int Age { get; set; }
    public string Name { get; set; }
    public string Gender { get; set; }
}

#endregion
Jon Erickson
la source
15

List<T>a une FindAllméthode qui effectuera le filtrage pour vous et retournera un sous-ensemble de la liste.

MSDN a un excellent exemple de code ici: http://msdn.microsoft.com/en-us/library/aa701359(VS.80).aspx

EDIT: j'ai écrit ceci avant d'avoir une bonne compréhension de LINQ et du Where() méthode. Si je devais écrire ceci aujourd'hui, j'utiliserais probablement la méthode mentionnée par Jorge ci-dessus. La FindAllméthode fonctionne toujours si vous êtes bloqué dans un environnement .NET 2.0.

Mykroft
la source
4
Linq est bien, mais au moins une magnitude plus lente, donc FindAll et les méthodes d'extension de filtrage (le tableau en a un tas par exemple) qui ne reposent pas sur IEnumerable ont toujours du sens pour les scénarios où les performances sont importantes. (FWIW, j'ai obtenu des résultats de facteur 7 à 50 fois plus de temps nécessaire à Linq et / ou IEnumerable, généralement)
Philm
Y a-t-il une raison pour laquelle ce n'est pas la réponse acceptée? Cela semble être plus rapide et la syntaxe est plus claire (pas d'appel toList ()) à la fin.
Ran Lottem le
6

Vous pouvez utiliser IEnumerable pour éliminer le besoin d'une liste temporaire.

public IEnumerable<T> GetFilteredItems(IEnumerable<T> collection)
{
    foreach (T item in collection)
    if (Matches<T>(item))
    {
        yield return item;
    }
}

où Matches est le nom de votre méthode de filtrage. Et vous pouvez utiliser ceci comme:

IEnumerable<MyType> filteredItems = GetFilteredItems(myList);
foreach (MyType item in filteredItems)
{
    // do sth with your filtered items
}

Cela appellera la fonction GetFilteredItems en cas de besoin et dans certains cas où vous n'utilisez pas tous les éléments de la collection filtrée, cela peut fournir un bon gain de performances.

Serhat Ozgel
la source
4

Pour le faire sur place, vous pouvez utiliser la méthode RemoveAll de la classe "List <>" avec une classe "Predicate" personnalisée ... mais tout ce qui ne fait que nettoyer le code ... sous le capot, c'est la même chose chose que vous êtes ... mais oui, il le fait en place, donc vous faites la même chose avec la liste temporaire.

Adam Haile
la source
4

Vous pouvez utiliser la méthode FindAll de la liste, en fournissant un délégué sur lequel filtrer. Cependant, je suis d'accord avec @ IainMH qu'il ne vaut pas la peine de trop s'inquiéter à moins que ce ne soit une liste énorme.

bdukes
la source
3

Si vous utilisez C # 3.0, vous pouvez utiliser linq

Ou, si vous préférez, utilisez la syntaxe de requête spéciale fournie par le compilateur C # 3:

var filteredList = from x in myList
                   where x > 7
                   select x;
Tom Lokhorst
la source
3

L'utilisation de LINQ est relativement beaucoup plus lente que l'utilisation d'un prédicat fourni à la FindAllméthode Lists . Faites également attention avec LINQ car l'énumération de listn'est pas réellement exécutée tant que vous n'avez pas accédé au résultat. Cela peut signifier que, lorsque vous pensez avoir créé une liste filtrée, le contenu peut différer de ce que vous attendiez lorsque vous la lisez réellement.

gouldos
la source
1

Si votre liste est très grande et que vous filtrez à plusieurs reprises, vous pouvez trier la liste d'origine sur l'attribut de filtre, effectuer une recherche binaire pour trouver les points de départ et d'arrivée.

Temps initial O (n * log (n)) puis O (log (n)).

Le filtrage standard prendra O (n) à chaque fois.

Daniel Roberts
la source