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

C# Discussion :

Fonction Générique Constructeur Paramètre


Sujet :

C#

  1. #1
    Membre confirmé Avatar de adrienfehr
    Homme Profil pro
    Inscrit en
    Mai 2008
    Messages
    203
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Mai 2008
    Messages : 203
    Par défaut Fonction Générique Constructeur Paramètre
    Bonjour,

    Je cherche à coder une fonction générique.
    La classe générique ici T est de type EntityClass.
    EntityClass a uniquement un constructeur qui prend un paramètre : EntityClass(DataRow monParamètre)

    Mon problème c'est que je n'arrive pas à écrire ma fonction générique . Comment faire dire au compilateur que le constructeur du générique T prend un paramètre ?

    Ce code ne passe pas (partie en rouge pose problème):
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    private List<T> GetListOfEntities<T>(string sSql, string sTableName) where T : EntityClass, new()
    {
          ...
          T maVariable=new T(monParamètre);
          ...
    }
    J'ai trouvé un workaround mais pas très joli (consiste à ajouter une méthode setParameter, le code compile alors):
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    private List<T> GetListOfEntities<T>(string sSql, string sTableName) where T : EntityClass, new()
    {
          ...
          T maVariable=new T();
          maVariable.setParameter(monParamètre)
          ...
    }

  2. #2
    Inactif  
    Homme Profil pro
    Chef de projet NTIC
    Inscrit en
    Janvier 2007
    Messages
    6 604
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 64
    Localisation : France

    Informations professionnelles :
    Activité : Chef de projet NTIC

    Informations forums :
    Inscription : Janvier 2007
    Messages : 6 604
    Par défaut
    Bonjour

    Pour appeler un constructeur avec paramètre, il y a toujours la solution de passer par Activator.CreateInstance. (mais c'est moins performant car utilisant la Reflection).

    Mais il faut dans ce cas virer la clause new() dans la contrainte de généricité car elle implique l'existence d'un constructeur SANS paramètres (sinon, la déclaration de la classe générique ne sera pas acceptée à la compil).

    Exemple :

    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
    	class MyTest
    	{
    		private int _x;
    		public MyTest(int x)
    		{
    			_x = x;
    		}
    	}
     
    	class TestGen<T> where T : MyTest
    	{
    		T _myInstance;
    		public TestGen()
    		{
    			_myInstance = (T)Activator.CreateInstance(typeof(T), 1);
    		}
    	}

    Effectivement, c'est un peu casse-pied de ne pas pouvoir exprimer la contrainte new() avec des paramètres.

  3. #3
    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 : 44
    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
    En effet il n'est malheureusement pas possible de spécifier les paramètres du constructeur dans la contrainte... et la principale alternative, Activator.CreateInstance, est très lente (plus lente que de chercher le constructeur par réflexion et de l'invoquer, car la classe Activator gère aussi pleins d'autres choses, genre remoting, interop COM, etc).

    Une solution peut être de générer dynamiquement une méthode qui crée l'objet, de la mettre en cache, et de la réutiliser à chaque fois pour gagner du temps. Petit exemple :

    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
    using System.Linq.Expressions;
    ...
     
     
    T CreateInstance<T>(string param)
    {
        return ConstructorCache<T, string>.Create(param);
    }
     
    private static class ConstructorCache<T, TParam>
    {
        public static readonly Func<TParam, T> Create = CreateFactory();
     
        private static Func<TParam, T> CreateFactory()
        {
            Debug.Print("Type: {0}", typeof(T).FullName);
            Debug.Print("Parameter: {0}", typeof(TParam).FullName);
     
            var ctor = typeof(T).GetConstructor(new[] { typeof(TParam) });
            if (ctor == null)
                throw new InvalidOperationException(string.Format("Type '{0}' doesn't have a constructor with a parameter of type '{1}'", typeof(T).FullName, typeof(TParam).FullName));
     
            var prm = Expression.Parameter(typeof(TParam));
            var newExpr = Expression.New(ctor, prm);
            var lambda = Expression.Lambda<Func<TParam, T>>(newExpr, prm);
            return lambda.Compile();
        }   
    }
    La trace de debug sert juste à prouver que la génération de la méthode n'est faite qu'une seule fois (pour un couple T/TParam donné), quel que soit le nombre d'appels à CreateInstance

    J'ai utilisé les expressions Linq, mais si tu as pas peur de mettre les mains dans le cambouis (ou que tu utilises une version de .NET antérieure à 3.5), tu peux aussi le faire directement en IL avec Reflection.Emit et DynamicMethod...

  4. #4
    Inactif  
    Homme Profil pro
    Chef de projet NTIC
    Inscrit en
    Janvier 2007
    Messages
    6 604
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 64
    Localisation : France

    Informations professionnelles :
    Activité : Chef de projet NTIC

    Informations forums :
    Inscription : Janvier 2007
    Messages : 6 604
    Par défaut
    Joli

    J'aurais plutôt utilisé l'Emit (pour avoir choisi cette solution dans un cas similaire, mais en 2.0) mais je ne suis pas sur que cela soit beaucoup plus performant (en tous cas, ce l'était notablement vs la Reflection, mais je ne suis pas sur que cela l'est plus que ta solution).

  5. #5
    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 : 44
    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 Bluedeep Voir le message
    J'aurais plutôt utilisé l'Emit (pour avoir choisi cette solution dans un cas similaire, mais en 2.0) mais je ne suis pas sur que cela soit beaucoup plus performant
    Avec Linq, c'est sans doute un peu plus long pour compiler la méthode, mais ça reste très raisonnable, surtout si ce n'est fait qu'une fois. Une fois que c'est compilé, a priori il n'y a pas de différence . Et puis c'est surtout plus facile avec Linq, enfin je trouve...

    Je vais essayer de faire un petit benchmark pour comparer d'ailleurs...

  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 : 44
    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 tomlev Voir le message
    Je vais essayer de faire un petit benchmark pour comparer d'ailleurs...
    Bon bah c'est à peu près comme j'avais dit : la génération de la méthode avec Emit est un peu plus rapide qu'avec Linq (de 10% environ), et pour l'exécution c'est pareil

    Method generation - Linq (10000 iterations)
    Elapsed: 00:00:05.1955450
    Average: 00:00:00.0005195

    Method generation - Emit (10000 iterations)
    Elapsed: 00:00:04.5354479
    Average: 00:00:00.0004535

    Method execution - Linq (10000000 iterations)
    Elapsed: 00:00:00.4527235
    Average: 00:00:00

    Method execution - Emit (10000000 iterations)
    Elapsed: 00:00:00.4902423
    Average: 00:00:00
    (les temps d'exécution varie un peu d'un run à l'autre, parfois c'est la méthode générée par Emit est plus rapide, et parfois c'est celle avec Linq... donc en gros c'est kif kif)

    A titre indicatif, le code de la méthode CreateFactory en utilisant Emit :

    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
    using System.Reflection;
    using System.Reflection.Emit;
    ...
     
        private static Func<TParam, T> CreateFactoryEmit()
        {
            Debug.Print("Type: {0}", typeof(T).FullName);
            Debug.Print("Parameter: {0}", typeof(TParam).FullName);
     
            var ctor = typeof(T).GetConstructor(new[] { typeof(TParam) });
            if (ctor == null)
                throw new InvalidOperationException(string.Format("Type '{0}' doesn't have a constructor with a parameter of type '{1}'", typeof(T).FullName, typeof(TParam).FullName));
     
            var dm = new DynamicMethod(
                "Create" + typeof(T).Name,
                typeof(T),
                new[] { typeof(TParam) },
                true);
            var il = dm.GetILGenerator();
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Newobj, ctor);
            il.Emit(OpCodes.Ret);
     
            return (Func<TParam, T>)dm.CreateDelegate(typeof(Func<TParam, T>));
        }

  7. #7
    Membre confirmé Avatar de adrienfehr
    Homme Profil pro
    Inscrit en
    Mai 2008
    Messages
    203
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Mai 2008
    Messages : 203
    Par défaut
    Merci !

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

Discussions similaires

  1. fonction générique avec paramètres
    Par daninou dans le forum Général JavaScript
    Réponses: 26
    Dernier message: 04/09/2009, 13h01
  2. Fonctions génériques et listes
    Par DevloNewb' dans le forum C++
    Réponses: 6
    Dernier message: 13/01/2006, 15h47
  3. [XSLT] Tri en fonction d'un paramètre
    Par virgul dans le forum XSL/XSLT/XPATH
    Réponses: 9
    Dernier message: 21/04/2005, 11h29
  4. Réponses: 6
    Dernier message: 24/02/2005, 10h44
  5. Erreur sur une fonction avec des paramètres
    Par Elois dans le forum PostgreSQL
    Réponses: 2
    Dernier message: 05/05/2004, 22h00

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