2009-05-15 9 views
9

Est-il possible de voir quel constructeur était générique?Constructeurs génériques et réflexion

internal class Foo<T> 
{ 
    public Foo(T value) {} 
    public Foo(string value) {} 
} 

var constructors = typeof(Foo<string>).GetConstructors(); 

La propriété 'ContainsGenericParameters' me renvoie false pour les deux constructeurs. Est-il possible de découvrir que constructeurs [0] est le générique? Ils ont tous les deux la même signature, mais je voudrais appeler la "vraie" chaîne.

EDIT:

Je veux invoquer le type donné en utilisant

ilGen.Emit(OpCodes.Newobj, constructorInfo); 

donc je dois travailler avec la version liée. Mais je voudrais invoquer le "meilleur" constructeur. Cela devrait être le comportement standard. Quand j'appelle

new Foo<string>() 

le constructeur avec la chaîne signature (et non celui avec la signature générique) est appelée. La même chose devrait arriver avec mon code.

Répondre

8

Vous voulez System.Reflection.ParameterInfo.ParameterType.IsGenericParameter. Voici un test unitaire VS2008 qui passe qui illustre ceci:

Classe:

public class Foo<T> 
{ 
    public Foo(T val) 
    { 
     this.Value = val.ToString(); 
    } 
    public Foo(string val) 
    { 
     this.Value = "--" + val + "--"; 
    } 

    public string Value { get; set; } 
} 

Méthode d'essai:

Foo<string> f = new Foo<string>("hello"); 
Assert.AreEqual("--hello--", f.Value); 

Foo<int> g = new Foo<int>(10); 
Assert.AreEqual("10", g.Value); 

Type t = typeof(Foo<string>); 
t = t.GetGenericTypeDefinition(); 

Assert.AreEqual(2, t.GetConstructors().Length); 

System.Reflection.ConstructorInfo c = t.GetConstructors()[0]; 
System.Reflection.ParameterInfo[] parms = c.GetParameters(); 
Assert.AreEqual(1, parms.Length); 
Assert.IsTrue(parms[0].ParameterType.IsGenericParameter); 

c = t.GetConstructors()[1]; 
parms = c.GetParameters(); 
Assert.AreEqual(1, parms.Length); 
Assert.IsFalse(parms[0].ParameterType.IsGenericParameter); 

Le point remarquable ici est le parms [0] .ParameterType.IsGenericParameter de chèques qui vérifie si le paramètre est générique ou non. Une fois que vous avez trouvé votre constructeur, vous avez le ConstructorInfo à transmettre à Emit.

public System.Reflection.ConstructorInfo FindStringConstructor(Type t) 
{ 
    Type t2 = t.GetGenericTypeDefinition(); 

    System.Reflection.ConstructorInfo[] cs = t2.GetConstructors(); 
    for (int i = 0; i < cs.Length; i++) 
    { 
     if (cs[i].GetParameters()[0].ParameterType == typeof(string)) 
     { 
      return t.GetConstructors()[i]; 
     } 
    } 

    return null; 
} 

Vous ne savez pas exactement quelle est votre intention.

+0

Merci pour votre réponse - le problème ici est que vous utilisez "Type t = typeof (Foo <>);" - il ne fonctionnera plus avec "Type t = typeof (Foo );" – tanascius

+0

appelle GetGenericTypeDefinition() pour l'obtenir sur Foo <> alors les index du constructeur sont toujours mappés sur le même constructeur. Donc, vous pouvez réfléchir sur Foo <> .. ctor pour trouver l'index correct puis utilisez le Foo ..ctor correspondant à votre générateur IL. Autant que je sache, vous perdez tous les génériques quand vous allez avec Foo

+2

GetGenericTypeDefinition() était exactement ce que j'ai raté. Merci beaucoup! – tanascius

0

Vous pouvez vérifier le (s) type (s) de résultat Type.GetGenericArguments et le comparer au type de paramètre constructeur.

Appelez simplement celui avec un type qui n'est pas identique (tapez! = Typeof (T)).

+0

Malheureusement, cela ne fonctionne pas pour mon problème. J'utilise typeof (Foo ) - le résultat de GetGenericArguments() sera une chaîne ... mes deux constructeurs me disent qu'ils prennent une valeur de chaîne - mais laquelle est la générique? – tanascius

+0

n'est pas générique car vous avez fermé le type en fournissant une chaîne comme argument de type. utilisez plutôt typeof (Foo <>). – x0n

2

Légère clarification. Aucun des constructeurs n'est une méthode générique. Ce sont des méthodes normales sur une classe générique. Pour qu'une méthode soit "générique", elle doit avoir un paramètre générique. Donc, faire un test comme "IsGenericMethod" retournera faux.

Il est également difficile de simplement observer les paramètres et déterminer s'ils sont génériques. Pour l'échantillon que vous avez donné, il est possible de parcourir les arguments et de rechercher un paramètre générique. Prenez également en compte le code suivant:

public Foo(IEnumerable<T> p1) ... 
public Foo(IEnumerable<KeyValuePair<string,Func<T>>> p1) ... 

Vous devez prendre en compte les éléments de ce type.

EDIT

La raison pour laquelle vous voyez tous les arguments sous forme de chaîne est parce que vous lié explicitement le type Foo avant d'obtenir les constructeurs. Essayez de passer votre code à ce qui suit qui utilise un Foo non lié et retournera donc des paramètres génériques dans les méthodes.

var constructors = typeof(Foo<>).GetConstructors(); 
+0

"Pour l'échantillon que vous avez donné, il est possible de parcourir les arguments et de rechercher un paramètre générique." - C'est la question ... Je ne sais toujours pas comment faire. Tous les arguments semblent être une chaîne - pas un soupçon de générique-ness – tanascius

+0

@tanascius, juste mis à jour ma réponse pour expliquer ce problème – JaredPar

+0

merci pour votre explication et la clarification. Je sais, ça marche avec typeof (Foo <>) ... alors est-ce que je vous comprends bien, après que la liaison à la réflexion sur les chaînes ne m'aide plus? – tanascius

0

Pouvez-vous expliquer un peu plus ce que vous essayez d'accomplir, quand vous dites que vous voulez appeler le constructeur en béton? Je suis simplement curieux de savoir s'il existe une autre façon de résoudre votre problème sans avoir à détecter si le constructeur contient des paramètres génériques.

Je pense constructeurs enchaînant ou la logique de construction dans le générique de se comporter d'une certaine manière, si le paramètre transmis est une chaîne, telle que:

static void Main(string[] args) 
    { 
     Console.WriteLine(new Foo<string>("myValue").IsValueAString); 
     Console.WriteLine(new Foo<int>(1).IsValueAString); 
     Console.ReadLine(); 
    } 

    public class Foo<T> 
    { 
     public bool IsValueAString = false; 
     public Foo(T value) { 
      if (value is string) 
      { 
       IsValueAString = true; 
      } 
     } 
    } 

Une autre option serait de créer une mise en œuvre concrète de Foo, quelque chose comme:

internal class Foo<T> 
{ 
    ... 
} 
internal class MyFoo : Foo<string> 
{ 
    ... 
} 

et d'intégrer toute logique spécifique dans le constructeur du descendant.Toutes sortes d'options en bas de ce chemin sont possibles afin que vous puissiez éviter de refléter l'information de cette classe.

+0

Voir ma modification. Je veux invoquer un type générique - malheureusement, je ne peux pas modifier le type que je reçois. Je devrais travailler toutes sortes de types génériques. – tanascius