Recherche d'un item dans des classes en cascade

Résolu
MGD Software Messages postés 186 Date d'inscription vendredi 1 septembre 2006 Statut Membre Dernière intervention 23 avril 2022 - 28 nov. 2017 à 16:08
Whismeril Messages postés 19029 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 26 avril 2024 - 29 nov. 2017 à 21:47
Bonjour,

J'ai créé une classe qui contient des classes qui contiennent des classes qui ont des propriétés... Bref, une bonne vieille structure arborescente.
Certes, la structure est un peu complexe, mais elle permet de créer des fichiers XML élaborés destinés à des scripts JavaScript de pages Web à l'aide d'une simple sérialisation.

Je cherche à récupérer une instance de la classe en bout de chaine à l'aide d'une de ses propriétés.

Par exemple, je cherche :
WebAlbum.collection.albums.slides.title == "fleur", ou plus précisément:
l'instance de la classe slide_class ayant pour propriété "title" la valeur "fleur"

collection est une instance de classe "collection_class" déclarée dans la classe WebAlbum
albums est une List<album_class> déclarée dans la classe collection_class
album_class est une classe ayant (entre autres) une propriété slides qui est une List<slide_class>
slide_class est une classe ayant (entre autres) une propriété title

À noter que l'accès aux variables tel que l'exemple se passe très bien (bien qu'un peu verbeux). Le problème, c'est les recherches.

Habituellement, pour des classes simples (à un seul niveau), on utilise habituellement la méthode Find.
Mais là, je n'y arrive pas. En plus, je ne suis pas encore très au fait de Linq, avec lequel je suppose on peut se tirer d'affaire.

J'en suis à faire des boucles imbriquées pour trouver l'instance qui m'intéresse. Je suis bien conscient que c'est pas top.
Le point de départ de la recherche est la collection, qui contient plusieurs albums, qui contiennent plusieurs photos. Il n'y a donc que deux niveaux de recherche (plus la propriété).

Y aurait-il une bonne âme pour m'aider ?
Merci d'avance.

6 réponses

Whismeril Messages postés 19029 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 26 avril 2024 656
28 nov. 2017 à 18:57
Bonsoir

un petit exemple à 2 niveaux, soient les classe suivantes
    class Courses
    {
        public List<Nourriture> Nourritures { get; set; } = new List<Nourriture> { new Nourriture()};
    }

    class Nourriture
    {
        public List<Legume> Legumes { get; set; } = new List<Legume> { new Legume { Nom = "Tomate", Quantite = 1 }, new Legume { Nom = "Tomate Grappe", Quantite = 2 }, new Legume { Nom = "Patate", Quantite = 3 } };
    }

    class Legume
    {
        public string Nom { get; set; }

        public int Quantite { get; set; }

        public override string ToString()
        {
            return Nom;
        }
    }


Et le code test suivant
            Courses courses = new Courses();

            //On cherches les tomates
            List<Legume> lesTomates =   (from n in courses.Nourritures //on itère tous les éléments de la collection Nourritures dans la variable n, un peu comme un foreach
                                         from l in n.Legumes //idem pour la collection Légumes
                                         where l.Nom.Contains("Tomate") //on pose la condition
                                         select l //on selection les instances qui répondent à la condition
                                         )//ici on a un IEnumerable<Legumes>
                                         .ToList();//on caste l'IEnumerable en liste

            //On cherche la Tomate
            Legume tomate = (from n in courses.Nourritures
                             from l in n.Legumes
                             where l.Nom == "Tomate"
                             select l
                             ).First();

            //On cherche le nombre de patates, et on sait qu'il n'y a qu'une instance de patate
            int nombrePatates = (from n in courses.Nourritures
                                 from l in n.Legumes
                                 select l
                                 ).Single(l => l.Nom == "Patate").Quantite;

            //On cherche l'endive
            Legume endive = (from n in courses.Nourritures
                             from l in n.Legumes
                             where l.Nom == "Tomate"
                             select l
                            ).FirstOrDefault();//retourne null s'il n'y a pas d'occurence

            if (endive == null)
                MessageBox.Show("Y a pas d'endives!")



0
MGD Software Messages postés 186 Date d'inscription vendredi 1 septembre 2006 Statut Membre Dernière intervention 23 avril 2022 2
28 nov. 2017 à 19:58
Bonsoir Whismeril,

Çà, c'est exactement ce que je fais : des boucles...
Je cherchais plutôt une sorte de Find(item => machin => bidule => chose == truc).

De plus, ma classe n'est pas structurée ainsi : les classes ne sont pas en parallèle, mais en série : pour reprendre ton exemple, cela donne :
class Courses
{
  public List<Nourriture> Nourritures { get; set; } = new List<Nourriture> { new Nourriture()};
  
  class Nourriture
  {
    public List<Legume> Legumes { get; set; } = new List<Legume> { new Legume { Nom = "Tomate", Quantite = 1 }, new Legume { Nom = "Tomate Grappe", Quantite = 2 }, new Legume { Nom = "Patate", Quantite = 3 } };
    
    class Legume
    {
      public string Nom { get; set; }
      
      public int Quantite { get; set; }
      
      public override string ToString()
      {
        return Nom;
      }
    }
  }
}

Chaque niveau gère un drapeau "Modified" qu'il remonte au niveau supérieur, afin de savoir au niveau le plus haut si la config est à sauvegarder ou non.

Je ne sais pas si c'est mieux ou plus mal, mais cela me semblait bien représenter la structure dont j'ai besoin.
Il me parait assez délicat d'en changer maintenant, alors qu'il y a plusieurs milliers de lignes de code qui l'utilisent.

De toute façon, je pense que le problème de la recherche reste le même, et à priori je vois qu'il n'y a pas de solution hors les boucles.

Amitiés
MGD
0
Whismeril Messages postés 19029 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 26 avril 2024 656
28 nov. 2017 à 20:43
Ecrire une classe imbriquée ne sert qu'à se compliquer la vie. Et à mon avis n'a aucune influence sur la serialization.
Mais quoi qu'il en soit, ça marche pareil

    public class Courses
    {
        public List<Nourriture> Nourritures { get; set; } = new List<Nourriture> { new Nourriture() };


        public class Nourriture
        {
            public List<Legume> Legumes { get; set; } = new List<Legume> { new Legume { Nom = "Tomate", Quantite = 1 }, new Legume { Nom = "Tomate Grappe", Quantite = 2 }, new Legume { Nom = "Patate", Quantite = 3 } };

            public class Legume
            {
                public string Nom { get; set; }

                public int Quantite { get; set; }

                public override string ToString()
                {
                    return Nom;
                }
            }

        }
    }


            Courses courses = new Courses();

            //On cherches les tomates
            List<Courses.Nourriture.Legume> lesTomates = (from n in courses.Nourritures //on itère tous les éléments de la collection Nourritures dans la variable n, un peu comme un foreach
                                       from l in n.Legumes //idem pour la collection Légumes
                                       where l.Nom.Contains("Tomate") //on pose la condition
                                       select l //on selection les instances qui répondent à la condition
                                         )//ici on a un IEnumerable<Legumes>
                                         .ToList();//on caste l'IEnumerable en liste

            //On cherche la Tomate
            Courses.Nourriture.Legume tomate = (from n in courses.Nourritures
                             from l in n.Legumes
                             where l.Nom == "Tomate"
                             select l
                             ).First();

            //On cherche le nombre de patates, et on sait qu'il n'y a qu'une instance de patate
            int nombrePatates = (from n in courses.Nourritures
                                 from l in n.Legumes
                                 select l
                                 ).Single(l => l.Nom == "Patate").Quantite;

            //On cherche l'endive
            Courses.Nourriture.Legume endive = (from n in courses.Nourritures
                             from l in n.Legumes
                             where l.Nom == "Endive"
                             select l
                            ).FirstOrDefault();//retourne null s'il n'y a pas d'occurence

            if (endive == null)
                MessageBox.Show("Y a pas d'endives!");



Et oui une requête Linq (ou sql), c'est une façon d'écrire des boucles, avec d'autres mots clés que for, foreach, while, etc...
Par contre, c'est sensé être plus rapide à l'exécution

0
Whismeril Messages postés 19029 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 26 avril 2024 656
29 nov. 2017 à 08:25
Bonjour,
Deux précisions:
  • Find, c’est aussi une boucle
  • Linq peut s'écrire en ligne, certaines méthodes (Single par exemple) n’ont que l’ecriture en ligne de disponible.


Si tu veux, je pourrais te les mettre en ligne ce soir, mais ça n’est pas franchement plus lisible.
0
MGD Software Messages postés 186 Date d'inscription vendredi 1 septembre 2006 Statut Membre Dernière intervention 23 avril 2022 2 > Whismeril Messages postés 19029 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 26 avril 2024
29 nov. 2017 à 10:01
Non, ça ira comme ça. je vais conserver mes boucles imbriquées, qui si elles ne sont pas forcément performantes, sont par contre parfaitement lisibles.
C'est mieux pour la maintenance du programme.

J'ai le souvenir d'un jeu qui faisait fureur dans les années 80 en langage C, qui consistait à rédiger un programme en une seule ligne.
En général, une fois écrit, le programme (s'il fonctionnait), était absolument non modifiable car totalement incompréhensible...

La concision c'est bien à condition de ne pas tomber dans l'herméticité.

A+
0
Whismeril Messages postés 19029 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 26 avril 2024 656
29 nov. 2017 à 17:52
En fait ça se lit bien.
J'ai ajouté un niveau pour que tu voies la suite de SelectMany
            List<Courses> courses = new List<Courses> { new Courses()};
            //On cherches les tomates
            List<Courses.Nourriture.Legume> lesTomates = courses.SelectMany(c => c.Nourritures).SelectMany(n => n.Legumes).Where(l => l.Nom.Contains("Tomate")).ToList();

            //On cherches la tomate
            Courses.Nourriture.Legume tomate = courses.SelectMany(c => c.Nourritures).SelectMany(n => n.Legumes).Single(l => l.Nom == "Tomate");

            //On cherche l'endive
            Courses.Nourriture.Legume endive = courses.SelectMany(c => c.Nourritures).SelectMany(n => n.Legumes).SingleOrDefault(l => l.Nom == "Endive");

            if (endive == null)
                MessageBox.Show("Y a pas d'endives!");



0

Vous n’avez pas trouvé la réponse que vous recherchez ?

Posez votre question
MGD Software Messages postés 186 Date d'inscription vendredi 1 septembre 2006 Statut Membre Dernière intervention 23 avril 2022 2
29 nov. 2017 à 19:23
Ça, ça me plait beaucoup plus, même si au cœur de la machine il se passe la même chose.

Mais à l'heure actuelle, je comprends pas tout...
Notamment les SelectMany, les singlexxx etc. La doc de la MSDN étant totalement incompréhensible, je vais essayer de trouver mieux. Heureusement, la recherche dans Google en ramène plein de pages.

Comme je n'ai pas l'habitude d'utiliser du code que je ne maitrise pas, je vais me pencher un certain temps pour voir comment ça fonctionne et faire des essais avec mes propres classes.

Merci et @+
0
Whismeril Messages postés 19029 Date d'inscription mardi 11 mars 2003 Statut Contributeur Dernière intervention 26 avril 2024 656
29 nov. 2017 à 21:47
Je te conseille ce lien
https://code.msdn.microsoft.com/101-LINQ-Samples-3fb9811b
Ce sont de tous petits exemples simples.
  • SelectMany concatène tous les éléments d’une « sous collection ».

Le premier retourne la totalité des Nouriturres contenus dans l’ensemble des Courses.
Le second retourne l’ensemble des légumes.
Une fois que j’ai tous les légumes, je peux faire ma recherche.
  • Select sélectionne tous les légumes qui correspondent à la condition.
  • Single retourne la seule et unique instance qui correspondent à la condition, ça plante s’il n’y en a aucune et s’il y en a plusieurs.

SingleOrDefaut retourne null s’il n’y a pas d’occurence, s’il y en a plusieurs je ne sais pas et là je ne peux pas tester.

Attention cependant avec Linq, un de ses points fort est aussi son plus grand piège => l’exécution différée.
Tant que tu es en phase d’apprentissage de Linq, je te conseille de forcer l’exécution immédiate avec ToList ou ToArray.
0
Rejoignez-nous