2018年9月19日 星期三

[C#] Reflection on create Generic Class or invoking Generic Method


  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





沒有留言:

張貼留言