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...
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.
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();
}
}
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...]
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With