views:

1071

answers:

4

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.

A: 

You could check the Type.GetGenericArguments result type(s), and compare that to the constructor parameter type.

Just call the one with a type that is not the same (type != typeof(T)).

Reed Copsey
Unfortunatly this is not working for my problem. I am using typeof( Foo<string> ) - the result of GetGenericArguments() will be string ... both of my constructors tell me that they are taking a string value - but which one is the generic one?
tanascius
neither is generic because you closed the type by providing string as the type argument. use typeof(Foo<>) instead.
x0n
+2  A: 

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();
JaredPar
"For the sample you gave it's possible to walk the arguments and look for a generic parameter." - that is the question ... I am still unsure about how to do that. All arguments seem to be string - no hint of generic-ness
tanascius
@tanascius, just updated my answer to explain that problem
JaredPar
thanks for your explaination and the clarification. I know, that it works with typeof( Foo<> ) ... so do I understand you correctly, that after the binding to string reflection will not help me anymore?
tanascius
A: 

Can you explain a bit more what you're trying to accomplish, when you say you want to call the concrete constructor? I'm just curious if there's another way to solve your issue without having to detect whether the constructor contains generic parameters.

I'm thinking chaining constructors or building logic into the generic one to behave a certain way if the parameter passed in is a string, such as:

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

Another option would be to create a concrete implementation of Foo, something like:

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

and embed any specific logic in the constructor of the descendant. All kinds of options down this path are possible so you can avoid having to reflect the info out of that one class.

mannish
See my edit. I want to invoke a generic type - unfortunately I can't modify the type I receive. I should work will all kind of generic types.
tanascius
+5  A: 

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.

Colin Burnett
Thanks for your answer - the problem here is that you use "Type t = typeof(Foo<>);" - it will not work anymore with "Type t = typeof(Foo<string>);"
tanascius
call GetGenericTypeDefinition() to get it on Foo<> then the constructor indexes still map to the same constructor. So you can reflect on Foo<>..ctor to find the correct index then use the matching Foo<string>..ctor to your IL generator.As far as I can tell you lose all generic-ness when you go with Foo<string>.
Colin Burnett
GetGenericTypeDefinition() was exactly what I missed. Thank you very much!
tanascius
Btw. nice style to answer such question with a working unittest!
tanascius