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.
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!")
Çà, 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.
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
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, ç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.
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.