Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get MethodInfo for open generic type from MethodInfo of closed type

Assume I have a class like so:

public class MyClass<T>
{
    public void Foo(T t)
    {
    }
}

Now, assume, I have an instance of MyClass<int> and a MethodInfo of its Foo method. Calling methodInfo.GetParameters() will return a ParameterInfo array with one entry, referring to type int. My problem is, that I can't seem to find out, if that parameter was declared as int in the class or as T.

What am I trying to achieve?
At runtime, I want to read the documentation of the method specified by MethodInfo from the XML Doc file generated by Visual Studio.
For the above defined method, the key looks like this:

<namespace>.MyClass`1.Foo(`0)

The `0 refers to the first generic type parameter of the declaring class. To be able to construct this string, I need to somehow get this information.
But how? MethodInfo doesn't seem to contain that info...

like image 844
Daniel Hilgarth Avatar asked Oct 28 '12 11:10

Daniel Hilgarth


4 Answers

The key seems to be Type.ContainsGenericParameters on the parameter type:

Given

public class MyClass<T>
{
    public void Foo(T t)
    {
    }

    public void Bar(int i)
    {

    }
}

Then

class Program
{
    static void Main(string[] args)
    {
        var obj = new MyClass<int>();

        // Closed type
        var closedType = obj.GetType();

        // Open generic (typeof(MyClass<>))
        var openType = closedType.GetGenericTypeDefinition();

        // Methods on open type
        var fooT = openType.GetMethod("Foo");
        var barint = openType.GetMethod("Bar");

        // Parameter types
        var tInFoo = fooT.GetParameters()[0].ParameterType;
        var iInBar = barint.GetParameters()[0].ParameterType;

        // Are they generic?
        var tInFooIsGeneric = tInFoo.ContainsGenericParameters;
        var iInBarIsGeneric = iInBar.ContainsGenericParameters;

        Console.WriteLine(tInFooIsGeneric);
        Console.WriteLine(iInBarIsGeneric);

        Console.ReadKey();
    }
}

outputs

True
False

This will obviously need more work for overloads and so on.

like image 57
AakashM Avatar answered Oct 11 '22 12:10

AakashM


Could you get the definition of the generic class through Type.GetGenericTypeDefinition Method and find there the definition for the same method, say, by name (and the signature), and then compare Foo(T t) and Foo(int t):

MyClass<int> c = new MyClass<int>();

Type concreteType = c.GetType();
Console.Write("Concrete type name:");
Console.WriteLine(concreteType.FullName);
Console.WriteLine();

MethodInfo concreteMethod = concreteType.GetMethod("Foo");
if (concreteMethod != null)
{
    Console.WriteLine(concreteMethod.Name);
    foreach (ParameterInfo pinfo in concreteMethod.GetParameters())
    {
        Console.WriteLine(pinfo.Name);
        Console.WriteLine(pinfo.ParameterType);
        Console.WriteLine();
    }
    Console.WriteLine();
}

if (concreteType.IsGenericType)
{
    Console.Write("Generic type name:");
    Type genericType = concreteType.GetGenericTypeDefinition();
    Console.WriteLine(genericType.FullName);
    Console.WriteLine();

    MethodInfo genericMethod = genericType.GetMethod("Foo");
    if (genericMethod != null)
    {
        Console.WriteLine(genericMethod.Name);
        foreach (ParameterInfo pinfo in genericMethod.GetParameters())
        {
            Console.WriteLine(pinfo.Name);
            Console.WriteLine(pinfo.ParameterType);
            Console.WriteLine();
        }
        Console.WriteLine();
    }
}
like image 26
horgh Avatar answered Oct 11 '22 14:10

horgh


I don't know if you have considered using Mono.Cecil instead of .Net's reflection.

// Gets the AssemblyDefinition (similar to .Net's Assembly).
Type testType = typeof(MyClass<>);
AssemblyDefinition assemblyDef = AssemblyDefinition.ReadAssembly(new Uri(testType.Assembly.CodeBase).LocalPath);
// Gets the TypeDefinition (similar to .Net's Type).
TypeDefinition classDef = assemblyDef.MainModule.Types.Single(typeDef => typeDef.Name == testType.Name);
// Gets the MethodDefinition (similar to .Net's MethodInfo).
MethodDefinition myMethodDef = classDef.Methods.Single(methDef => methDef.Name == "Foo");

then myMethodDef.FullName returns

"System.Void MyNamespace.MyClass`1::Foo(System.Int32,T,System.String)"

and classDef.GenericParameters[0].FullName returns

"T"

Note that Mono.Cecil uses a different way of writing generics, nested classes and arrays:

List[T] => List<T>
MyClass+MyNestedClass => MyClass/MyNestedClass
int[,] => int[0...,0...]
like image 20
user276648 Avatar answered Oct 11 '22 13:10

user276648


This was a tricky one. If you have a MethodInfo instance of a (non-generic) method, which was obtained on an open generic Type, you can actually use it to repeatedly close the method (i.e., with different generic arguments for the enclosing type).

The key is to use the obscure static function MethodInfo.GetMethodFromHandle(...):

Complete working example:

static class MyType<T>
{
    public static int TheMethod() => typeof(T).MetadataToken;  // demo method
};

How to use:

static void demo()
{
    var Topen = typeof(MyType<>);
    var mi_open = Topen.GetMethod("TheMethod");  // can't Invoke() this one...

    // ...later perhaps...  MyType<ushort>.TheMethod()

    var Tclose = Topen.MakeGenericType(typeof(ushort));
    var mi = MethodInfo.GetMethodFromHandle(mi_open.MethodHandle, Tclose.TypeHandle);
    var ret = (int)mi.Invoke(null, null);        // works --> 0x02000150

    // later yet... MyType<Guid>.TheMethod(), reusing 'mi_open' (and Topen)...

    Tclose = Topen.MakeGenericType(typeof(Guid));
    mi = MethodInfo.GetMethodFromHandle(mi_open.MethodHandle, Tclose.TypeHandle);
    ret = (int)mi.Invoke(null, null);            // works --> 0x020000eb
}

Notice that the Tclose type handle (passed in as the second arg) is not simply the handle of the type argument T that you ultimately want to parametrize with. Rather, you have to use that T to close the original Topen (open) generic type first, and then use the handle for the resulting (closed) generic type.

In other words, you can't close a method without first explicitly closing its generic type.

like image 35
Glenn Slayden Avatar answered Oct 11 '22 14:10

Glenn Slayden