C# Reflection Generic class Generic method
▌Introduction
In this tutorial, we will use Reflection in C# to pass an Unknown Class Type to a Generic class or Generic method in order to create the Generic object(instance) or
invoking the Generic method.
▌Environment
▋Visual Studio 2017 community
▌Implement
First lets create the class which will be passed as the
generic type for the Generic class or Generic methods.
internal class Me
{
public string Name { get; set; }
public int Age { get; set; }
}
internal class You
{
public string Name { get; set; }
public int Age { get; set; }
}
▋Create an instance from Generic class
▋Generic class: MyModel<T>
Now create the Generic class with one generic type: T.
internal class MyModel<T> where T : new()
{
private T _foo = default(T);
public MyModel()
{
this._foo = new T();
}
public override string ToString()
{
return $"{this._foo.GetType().Name}";
}
}
Create instance:
MyModel<T>:
Type typeArgument = typeof(Me); //Me is the Unknown type for T
#region Reflection
Type genericClass = typeof(MyModel<>);
Type constructedClass = genericClass.MakeGenericType(typeArgument);
object created = Activator.CreateInstance(constructedClass);
#endregion
▋Generic class: MyModel<T, K>
Furthermore, for a class with multiple generic types,
internal class MyModel<T, K> where T : new()
where K : new()
{
private T _foo1 = default(T);
private K _foo2 = default(K);
public MyModel()
{
this._foo1 = new T();
this._foo2 = new K();
}
public override string ToString()
{
return $"{this._foo1.GetType().Name},{this._foo2.GetType().Name}";
}
}
Here is the code to create
instance: MyModel<T, K>,
Type typeArgument1 = typeof(Me); //Me is
the Unknown type for T
Type typeArgument2 = typeof(You); //You is
the Unknown type for K
#region Reflection
Type genericClass = typeof(MyModel<,>);
Type constructedClass = genericClass.MakeGenericType(typeArgument1, typeArgument2);
object created = Activator.CreateInstance(constructedClass);
#endregion
▋Generic class: MyModel<T> with constructor parameter(s)
Assume the Generic Class needs a parameter in constructor
like this,
internal class MyModelCtor<T> where T : new()
{
private T _foo = default(T);
private string _value = string.Empty;
public MyModelCtor(string value)
{
this._foo = new T();
this._value = value;
}
public override string ToString()
{
return $"{this._foo.GetType().Name} with parameter = {this._value}";
}
}
Simply add the value as the parameter in Activator.CreateInstance.
Type typeArgument = typeof(Me); //Me is the Unknown type for T
#region Reflection
string ctorParamValue = "Test";
Type genericClass = typeof(MyModelCtor<>);
Type constructedClass = genericClass.MakeGenericType(typeArgument);
object created = Activator.CreateInstance(constructedClass, new object[] { ctorParamValue });
#endregion
▋Invoke Generic method
Here are the key functions we will user later:
The target Generic method in a class,
internal class MyModel
{
public string NonOverloadingGet<T>()
{
return "From non-overloading method";
}
}
The way to invoke it:
Type typeArgument = (new Me()).GetType(); //The Unknown type for T
#region Reflection
var target = new MyModel();
MethodInfo method = typeof(MyModel).GetMethod("NonOverloadingGet");
MethodInfo generic = method.MakeGenericMethod(typeArgument);
var result = generic.Invoke(target, null);
#endregion
▋Invoke overloading Generic methods
However, if invoking a overloading method, we will get System.Reflection.AmbiguousMatchException
So we need to give more information to Type.GetMethods, such as parameter’s
type, while we are trying to get MethodInfo
instance to avoid ambiguous matching.
Now let’s modify the original class with Generic method
like following,
internal class MyModel
{
public string Get()
{
//This method is used to cause
AmbiguousMatchException
return $"From non-generic method";
}
//Case 1.
public string Get<T>()
{
return $"From generic method witout argument";
}
//Case 2.
public string Get<T>(string argument)
{
return $"From generic method with argument: string";
}
//Case 3.
public string Get<T>(T foo)
{
return $"From generic method with argument: {foo.GetType().Name}";
}
//Case 4.
public static string Get<T>(T foo, string value)
{
return $"From static generic method: Given type is {foo.GetType().Name} and given value is {value}";
}
}
And here are the ways to invoke each generic methods in
this class.
▋Case1. Reflection on Generic Method without argument
Type typeArgument = (new Me()).GetType(); //The
Unknown type for T
#region Reflection
var target = new MyModel();
MethodInfo method = typeof(MyModel).GetMethods()
.Where(x =>
x.Name.Equals("Get")
&&
x.GetParameters().Count().Equals(0)
&&
x.IsGenericMethod
).FirstOrDefault();
MethodInfo generic = method.MakeGenericMethod(typeArgument);
var result = generic.Invoke(target, null);
#endregion
I put very strict constraints to make sure the exact
method.
▋Case2. Reflection on Generic Method with argument
Notice we can add the type(s) of
arguments for the method into Type.GetMethod to get the exact method.
Type typeArgument = (new Me()).GetType(); //The Unknown type for T
#region Reflection
var target = new MyModel();
MethodInfo method = typeof(MyModel).GetMethod("Get", new[] { typeof(string) });
MethodInfo generic = method.MakeGenericMethod(typeArgument);
var result = generic.Invoke(target, new object[] { "foo argument" });
#endregion
▋Case3. Reflection on Generic Method with generic argument
Type typeArgument = (new Me()).GetType(); //The Unknown type for T
#region Reflection
var target = new MyModel();
var foo = typeof(MyModel).GetMethods();
MethodInfo method = typeof(MyModel).GetMethods()
.Where(x =>
x.Name.Equals("Get") &&
x.GetParameters().Count() == 1 &&
x.GetParameters()[0].ParameterType.IsGenericParameter)
.FirstOrDefault();
MethodInfo generic = method.MakeGenericMethod(typeArgument);
var result = generic.Invoke(target, new object[] { new Me() });
#endregion
▋Case 4. Reflection on Generic Method with complex arguments
Type typeArgument = (new Me()).GetType(); //The Unknown type for T
#region Reflection
var target = new MyModel();
MethodInfo method = typeof(MyModel).GetMethods()
.Where(x =>
x.Name.Equals("Get") &&
x.GetParameters().Count() == 2 &&
x.GetParameters()[0].ParameterType.IsGenericParameter &&
x.GetParameters()[1].ParameterType.Equals(typeof(string))
)
.FirstOrDefault();
MethodInfo generic = method.MakeGenericMethod(typeArgument);
var arg1 = new Me();
var arg2 = "Demo";
var result = generic.Invoke(target, new object[] { arg1, arg2 });
#endregion
Notice that the document of MethodBase.Invoke shows that parameter:
obj, will be ignored in a static method as case 4.
So the following code also
works in case 4.
var result = generic.Invoke(null, new object[] { arg1, arg2 });
▌Reference
沒有留言:
張貼留言