IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

ASP.NET Discussion :

Classes génériques, réflexion


Sujet :

ASP.NET

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre averti
    Profil pro
    Inscrit en
    Novembre 2006
    Messages
    46
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2006
    Messages : 46
    Par défaut Classes génériques, réflexion
    Bonjour a tous,

    Je viens de tomber sur un problème assez important en commençant a toucher a la réflexion en C#, voici le contexte :

    Le but de bout de code est de faire une interface de gestion des entrées / sorties de base (clavier, souris, manette ..).
    Étant donne que ces 3 périphériques sont assez proches le code l'est aussi donc j'ai voulu utiliser un maximum de templates.

    (Je développe sur XNA mais pour cet exemple j'ai fait sans).

    J'ai donc 2 classes :

    - LocalInputManager :
    Se charge de stocker une liste de Control, de les modifier, supprimer, mettre en pause etc etc.

    - Control :
    Stock un groupe de touches liées a un évènements entre autre.


    Voici un exemple :

    LocalInputManager :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
     
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
     
    namespace TestReflection
    {
        public enum Keys
        {
            Left,
            Right,
            Up,
            Down, 
            Space
        };
     
        class LocalInputManager<T, C> where C : class, new()
        {
            protected List<KeyValuePair<string, C>> controls;
     
            private int totalControls;
     
     
            public LocalInputManager()
            {
                this.totalControls = 0;
                this.controls = new List<KeyValuePair<string, C>>();
            }
     
            public void AddControl(string Name, T Keys, bool KeyRepeat)
            {
                C instance = (C)Activator.CreateInstance(typeof(C), Keys, KeyRepeat);
                this.controls.Add(new KeyValuePair<string, C>(Name, instance));
                this.totalControls++;
            }
     
            public void UpdateAllControls()
            {
                for (int i = 0; i < this.totalControls; i++)
                {
                    this.controls[i].Value.Update();
                }
            }
        }
    }
    Control :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
     
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
     
    namespace TestReflection
    {
        class Control<T>
        {
            private T controls;
     
            private bool repeat;
     
            public Control()
            {
                this.repeat = false;
            }
     
            public Control(T Key, bool Repeat)
            {
                this.controls = Key;
                this.repeat = Repeat;
            }
     
            public void Update()
            {
                Console.WriteLine("Mise a jour effectuee.");
            }
        }
    }
    Et un petit main de test qui expose le probleme :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
     
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
     
    namespace TestReflection
    {
        class Program
        {
            static void Main(string[] args)
            {
                LocalInputManager<Keys, Control<Keys>> manager;
     
                manager = new LocalInputManager<Keys, Control<Keys>>();
                manager.AddControl("Courir droite", Keys.Right, true);
                manager.AddControl("Sauter", Keys.Space, false);
            }
        }
    }
    Le problème se situe dans la classe LocalInputManager, dans la méthode UpdateAllControls.
    En effet, il ne trouve pas la méthode Update() contenue dans C, ce en quoi j'ai du mal a lui en vouloir car C est un type générique.

    Alors la question est : Comment lui préciser que C possède bel et bien la méthode sans préciser le type car il n'y a plus d'intérêt dans ce cas ?

    Merci pour votre aide.

  2. #2
    Rédacteur/Modérateur


    Homme Profil pro
    Développeur .NET
    Inscrit en
    Février 2004
    Messages
    19 875
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Développeur .NET
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2004
    Messages : 19 875
    Par défaut
    Ton problème n'a rien à voir avec la réflexion en fait...

    Il suffit de dire que C est un Control<T> (car je suppose que ce sera toujours le cas, comme dans ton exemple ?)

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    class LocalInputManager<T, C> where C : Control<T>, new()
    Mais à moins que tu utilises des classes héritées de Control<T>, tu peux aussi bien dégager le paramètre C et utiliser Control<T> à la place :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    class LocalInputManager<T>
    {
        protected List<KeyValuePair<string, Control<T>>> controls;
     
        ...
    }
    soit dit en passant, ça sert à quoi une liste de KeyValuePair ??? Utilise plutôt un Dictionary...

  3. #3
    Membre averti
    Profil pro
    Inscrit en
    Novembre 2006
    Messages
    46
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2006
    Messages : 46
    Par défaut
    Salut,

    Le fait de faire :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    class LocalInputManager<T, C> where C : Control<T>, new()
    ne m'est d'aucune utilité car ce n'est plus un type générique.
    Dans l'exemple poste je n'utilise que Control<T> mais c'était pour simplifier.
    Dans l'implémentation réelle j'utilise des classes héritées de Control<T>.

    Sinon l'intérêt, pour moi, de la liste de KeyValuePair c'est qu'elle soit indexée, ou du moins accessible en faisant controls[i].

    Utiliser un dictionnaire m'oblige a faire ElementAt(i) et j'ai peur qu'il re-parcours toute la liste a chaque appel.

    Je me trompe peut-être après, je n'ai commencé que récemment le C# je n'en maitrise que peu de facettes.

    Autrement, puisque le débat est lance, la surcharge de [] d'une List<> est-elle beaucoup plus lente que celle d'un tableau classique ?

    Sinon pour mon problème de base, on m'a finalement débloqué et il suffit de créer une interface décrivant les éléments composant la classe générique (C dans mon cas).

    Cela donne quelque chose comme ceci :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
     
    public interface IControlInterface
    {
        void Update();
    };
     
    class Control<T> : IControlInterface
    {
      [...]
    }
     
    class LocalInputManager<T, C> where C : class, IControlInterface, new()
    {
      [...]
    }
    Cela fonctionne et permet d'appeler C.Update();

    En revanche, je serais curieux de savoir si c'est possible (et donc comment) éviter l'appel a Activator.CreateInstance().

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    C instance = (C)Activator.CreateInstance(typeof(C), Keys, KeyRepeat);
    this.controls.Add(new KeyValuePair<string, C>(Name, instance));
    C'est le seul moyen que j'ai trouvé pour appeler un constructeur avec paramètres de C, le mot clef new() ne m'autorisant qu'un appel au constructeur par défaut.

  4. #4
    Rédacteur/Modérateur


    Homme Profil pro
    Développeur .NET
    Inscrit en
    Février 2004
    Messages
    19 875
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Développeur .NET
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2004
    Messages : 19 875
    Par défaut
    Citation Envoyé par Stnaire Voir le message
    Le fait de faire :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    class LocalInputManager<T, C> where C : Control<T>, new()
    ne m'est d'aucune utilité car ce n'est plus un type générique.
    Dans l'exemple poste je n'utilise que Control<T> mais c'était pour simplifier.
    Dans l'implémentation réelle j'utilise des classes héritées de Control<T>.
    Ben justement, avec ce code tu imposes à C d'être Control<T> OU une classe héritée de Control<T>... je comprends pas ton problème

    Citation Envoyé par Stnaire Voir le message
    Sinon l'intérêt, pour moi, de la liste de KeyValuePair c'est qu'elle soit indexée, ou du moins accessible en faisant controls[i].
    OK... c'est juste que normalement les KeyValuePair servent dans un dictionnaire, pour accéder à un élément via sa clé (et non via son index). Mais si ça correspond à ton besoin, pourquoi pas...

    Citation Envoyé par Stnaire Voir le message
    Utiliser un dictionnaire m'oblige a faire ElementAt(i) et j'ai peur qu'il re-parcours toute la liste a chaque appel.
    Ben c'est surtout qu'un dictionnaire n'est pas conçu pour accéder aux éléments via leur index... D'ailleurs l'ordre des éléments d'un dictionnaire n'est pas déterminé, vu que c'est lié à leur HashCode

    Citation Envoyé par Stnaire Voir le message
    Autrement, puisque le débat est lance, la surcharge de [] d'une List<> est-elle beaucoup plus lente que celle d'un tableau classique ?
    L'accès à un élément d'une List<T> est une opération en O(1), c'est à dire en temps constant (ça ne dépend pas du nombre d'éléments. Donc c'est comparable à un tableau, sans doute un peu moins rapide mais quand même très correct...

    Citation Envoyé par Stnaire Voir le message
    Sinon pour mon problème de base, on m'a finalement débloqué et il suffit de créer une interface décrivant les éléments composant la classe générique (C dans mon cas).
    C'est aussi une solution, mais celle que je te proposais fonctionne aussi a priori... Par contre, utiliser une interface c'est vrai que c'est plutôt mieux, ça donne plus de souplesse. Par contre j'aurais rendu l'interface générique... enfin il faut voir si ça a un sens dans ton cas.

    Citation Envoyé par Stnaire Voir le message
    En revanche, je serais curieux de savoir si c'est possible (et donc comment) éviter l'appel a Activator.CreateInstance().
    Si le constructeur a des paramètres, tu ne peux pas... La contrainte "new()" permet seulement de spécifier qu'il y a un constructeur par défaut, pas de spécifier des paramètres. Sinon tu peux aussi chercher le constructeur qui va bien par réflexion, mais c'est déjà ce que fait Activator.CreateInstance, donc ça n'a pas vraiment d'intérêt...

  5. #5
    Membre averti
    Profil pro
    Inscrit en
    Novembre 2006
    Messages
    46
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2006
    Messages : 46
    Par défaut
    Citation Envoyé par tomlev Voir le message
    Ben justement, avec ce code tu imposes à C d'être Control<T> OU une classe héritée de Control<T>... je comprends pas ton problème
    Bah je n'utilise pas directement Control<T> dans la déclaration de la classe justement, mais l'interface la décrivant.

    La déclaration dans mon implémentation réelle est la suivante :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    public class LocalInputManager<T, T2, C> where C : class, IInputManager<T>
    {
       [...]
    }
    Citation Envoyé par tomlev Voir le message
    OK... c'est juste que normalement les KeyValuePair servent dans un dictionnaire, pour accéder à un élément via sa clé (et non via son index). Mais si ça correspond à ton besoin, pourquoi pas...


    Ben c'est surtout qu'un dictionnaire n'est pas conçu pour accéder aux éléments via leur index... D'ailleurs l'ordre des éléments d'un dictionnaire n'est pas déterminé, vu que c'est lié à leur HashCode

    L'accès à un élément d'une List<T> est une opération en O(1), c'est à dire en temps constant (ça ne dépend pas du nombre d'éléments. Donc c'est comparable à un tableau, sans doute un peu moins rapide mais quand même très correct...
    J'ai finalement supprime la liste de KeyValuePair et laisse uniquement une liste de C. Ça me convient car je peux y accéder par index numérique.

    Sinon merci pour l'info sur le temps d'accès a une liste


    Citation Envoyé par tomlev Voir le message
    Si le constructeur a des paramètres, tu ne peux pas... La contrainte "new()" permet seulement de spécifier qu'il y a un constructeur par défaut, pas de spécifier des paramètres. Sinon tu peux aussi chercher le constructeur qui va bien par réflexion, mais c'est déjà ce que fait Activator.CreateInstance, donc ça n'a pas vraiment d'intérêt...
    Bon a savoir, j'ai finalement fais autrement, en déclarant mes fonctions d'ajouts dans les classes filles ce qui évite ce problème.
    Mais merci pour l'information.

    Je penses que tout a été dit a propos du sujet de départ, sujet résolu

    Merci

  6. #6
    Rédacteur/Modérateur


    Homme Profil pro
    Développeur .NET
    Inscrit en
    Février 2004
    Messages
    19 875
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Développeur .NET
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2004
    Messages : 19 875
    Par défaut
    n'oublie pas de marquer le sujet comme alors

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Utilisation d'une classe générique
    Par bandit_debutant dans le forum Langage
    Réponses: 4
    Dernier message: 06/12/2006, 16h54
  2. Réponses: 3
    Dernier message: 05/10/2006, 17h15
  3. [C# 2.0] Un exemple de classe générique qui ne compile pas.
    Par Pierre8r dans le forum Windows Forms
    Réponses: 4
    Dernier message: 31/05/2006, 11h11
  4. [C#][ADO] Classe Générique ADO
    Par tscoops dans le forum Accès aux données
    Réponses: 2
    Dernier message: 15/11/2005, 14h21
  5. [Generics] Classe générique
    Par norkius dans le forum Langage
    Réponses: 4
    Dernier message: 29/10/2004, 15h57

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo