2009-05-15 19 views
9

¿Es posible ver qué constructor era el genérico?Constructores genéricos y reflejo

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

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

La propiedad 'ContainsGenericParameters' me devuelve false para ambos constructores. ¿Hay alguna manera de descubrir que los constructores [0] son ​​genéricos? Ambos tienen la misma firma, pero me gustaría llamar a la cadena "real".

EDIT:

Quiero invocar el tipo determinado usando

ilGen.Emit(OpCodes.Newobj, constructorInfo); 

así que necesitamos trabajar con la versión encuadernada. Pero me gustaría invocar al "mejor" constructor. Ese debería ser el comportamiento estándar. Cuando llamo

new Foo<string>() 

el constructor con la cadena de firmas (y no el que tiene la firma genérica) se llama. Lo mismo debería suceder con mi código.

Respuesta

8

Quiere System.Reflection.ParameterInfo.ParameterType.IsGenericParameter. Aquí hay una prueba de la unidad VS2008 que pasa que ilustra esto:

Clase:

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étodo de ensayo:

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); 

El punto notable aquí es las parms [0] .ParameterType.IsGenericParameter comprobar qué comprueba si el parámetro es genérico o no.

Una vez que haya encontrado su constructor, entonces usted tiene el ConstructorInfo para pasar a Emitir.

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; 
} 

No estoy seguro de cuál es su intención.

+0

Gracias por su respuesta - el problema aquí es que utiliza "Tipo t = typeof (Foo <>);" - ya no funcionará con "Tipo t = typeof (Foo );" – tanascius

+0

llame a GetGenericTypeDefinition() para obtenerlo en Foo <>, entonces los índices del constructor aún se asignan al mismo constructor. De modo que puede reflexionar sobre Foo <> .. ctor para encontrar el índice correcto y luego usar el Foo ..ctor correspondiente a su generador de IL. Por lo que puedo decir, pierdes todo el genérico cuando vas con Foo . –

+2

GetGenericTypeDefinition() fue exactamente lo que me perdí. ¡Muchas gracias! – tanascius

0

Puede consultar el Type.GetGenericArguments tipo (s) de resultado y compararlo con el tipo de parámetro del constructor.

Simplemente llama al que tiene un tipo que no es el mismo (tipo! = Typeof (T)).

+0

Desafortunadamente, esto no funciona para mi problema. Estoy usando typeof (Foo ) - el resultado de GetGenericArguments() será string ... ambos de mis constructores me dicen que están tomando un valor de cadena, pero ¿cuál es el genérico? – tanascius

+0

ninguno es genérico porque cerró el tipo al proporcionar cadena como el argumento de tipo. use typeof (Foo <>) en su lugar. – x0n

2

Ligera aclaración. Ninguno de los constructores son métodos genéricos. Son métodos normales en una clase genérica. Para que un método sea "genérico" debe tener un parámetro genérico. Entonces hacer una prueba como "IsGenericMethod" devolverá falso.

Tampoco es fácil simplemente mirar los parámetros y determinar si son genéricos. Para la muestra que proporcionó, es posible recorrer los argumentos y buscar un parámetro genérico. Pero también considere el siguiente código

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

Deberá tener en cuenta elementos como este.

EDITAR

La razón por la que estamos viendo todos los argumentos como cadena se debe a que usted limita explícitamente el tipo de Foo antes de conseguir los constructores. Intenta cambiar tu código a lo siguiente, que usa un Foo independiente y, por lo tanto, devolverá parámetros genéricos en los métodos.

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

"Para la muestra que dio, es posible recorrer los argumentos y buscar un parámetro genérico". - Esa es la pregunta ... Todavía no estoy seguro de cómo hacerlo. Todos los argumentos parecen ser cadena - no hay indicio de genérico ness – tanascius

+0

@tanascius, acaba de actualizar mi respuesta para explicar ese problema – JaredPar

+0

gracias por su explicación y la aclaración. Sé que funciona con typeof (Foo <>) ... así que te entiendo correctamente, ¿que después de la unión al reflejo de cuerdas ya no me ayudará? – tanascius

0

¿Puedes explicar un poco más lo que estás tratando de lograr cuando dices que quieres llamar al constructor de concreto? Solo tengo curiosidad si hay otra forma de resolver su problema sin tener que detectar si el constructor contiene parámetros genéricos.

estoy pensando constructores encadenamiento lógico o edificio en el genérico a comportarse de cierta manera si el parámetro pasado es una cadena, tales como:

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; 
      } 
     } 
    } 

Otra opción sería crear una aplicación concreta de Foo, algo así como:

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

e incrusta cualquier lógica específica en el constructor del descendiente.Todo tipo de opciones en este camino son posibles, así que puedes evitar tener que reflejar la información de esa clase.

+0

Ver mi edición. Quiero invocar un tipo genérico: desafortunadamente no puedo modificar el tipo que recibo. Debería trabajar todo tipo de tipos genéricos. – tanascius