Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic constructors and reflection

Is it possible to see which constructor was the generic one?

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

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

The property 'ContainsGenericParameters' returns me for both constructors false. Is there any way to find out that constructors[0] is the generic one? They both have the same signature, but I would like to call the "real" string one.

EDIT:

I want to invoke the given type using

ilGen.Emit( OpCodes.Newobj, constructorInfo );

so I need to work with the bound version. But I would like to invoke the "best" constructor. That should be the standard behaviour. When I call

new Foo<string>()

the constructor with the string-signature (and not the one with the generic signature) is called. The same should happen with my code.

like image 940
tanascius Avatar asked May 15 '09 15:05

tanascius


2 Answers

You want System.Reflection.ParameterInfo.ParameterType.IsGenericParameter. Here's a VS2008 unit test that passes that illustrates this:

Class:

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

    public string Value { get; set; }
}

Test method:

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

The notable point here is the parms[0].ParameterType.IsGenericParameter check which checks if the parameter is a generic or not.

Once you've found your constructor then you've got the ConstructorInfo to pass to 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;
}

Not exactly sure what your intention is though.

like image 53
Colin Burnett Avatar answered Oct 25 '22 01:10

Colin Burnett


Slight clarification. Neither of the constructors are generic methods. They are normal methods on a generic class. For a method to be "generic" it must have a generic parameter . So doing a test like "IsGenericMethod" will return false.

It's also not easy to simply look at the parameters and determine if they are generic. For the sample you gave it's possible to walk the arguments and look for a generic parameter. But also consider the following code

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

You'll need to take items like this into account.

EDIT

The reason you're seeing all arguments as string is because you explicitly bound the type Foo before getting the constructors. Try switching your code to the following which uses an unbound Foo and hence will return generic parameters in the methods.

var constructors = typeof( Foo<> ).GetConstructors();
like image 29
JaredPar Avatar answered Oct 25 '22 01:10

JaredPar