The previous three articles introduced the usage of assemblies, how to obtain Type objects, and how to use Type objects to access member information.
From the previous study, we have a general understanding of the existence and output information of Assembly, PropertyInfo, FieldInfo, ConstructorInfo, MethodInfo, ParameterInfo, EventInfo, and MemberInfo.
This article will start with reflecting on instantiating types and will conduct a series of practical reflection operations.
This article mainly discusses instantiating types and instantiating delegates.
1. Instantiating Types
There are two ways to create an instance of a type (Type):
- Using the Activator.CreateInstance() method, which operates on the Type.
- Using ConstructorInfo.Invoke(), which operates on the ConstructorInfo.
When instantiating a type, the constructor of the type should be considered first.
1.1 Activator.CreateInstance()
First, this is how it's defined in Microsoft Docs:
Create an instance of the specified type using the constructor that matches the specified parameters most closely.
What does that mean?
Let's take a look at the two most commonly used overloads of Activator.CreateInstance()
.
object? CreateInstance(Type type);
object? CreateInstance(Type type, params object[] args);
The args
are the parameters passed to the constructor when instantiating the type. Since it uses object
, the constructor used for instantiation will be the one with the highest matching degree.
Alright, let's move on to the practical part.
1.1.1 Simple Types
Type typeA = typeof(int);
object objA = Activator.CreateInstance(typeA);
With the above code, we can easily instantiate a simple type.
Of course, you can see that the type created is object
.
Now, here's the problem:
After reflection, boxing and unboxing become unavoidable.
As of now, we can't use int's methods, only those of object. What should we do?
We'll deal with that later.
Of course, we could just use int
, so why use reflection?
int i = 666;
Wouldn't that work?
If you need to reference code after generating the program that references a dll, we can do it like this:
Assembly ass = Assembly.LoadFrom(@"C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.0.0\ref\netcoreapp3.0\System.Runtime.dll");
Type typeA = ass.GetType("System.Int32");
object objA = Activator.CreateInstance(typeA);
1.1.2 Constructors of Simple Types
For simple types like int
, there's no other operation; we can just instantiate directly. Here, let's take the DateTime
type as an example and instantiate it using different parameters to call the constructor.
Type typeA = typeof(DateTime);
object objA = Activator.CreateInstance(typeA, 2020, 1, 5);
Of course, if a suitable constructor cannot be found to instantiate the type, a System.MissingMethodException
exception will be thrown.
1.1.3 object
Let's create a type:
public MyClass(object a, object b)
{
}
public MyClass(string a, string b)
{
}
public MyClass(string a, object b)
{
}</code></pre>
Now, let’s create an instance via reflection:
Type typeA = typeof(MyClass);
object objA = Activator.CreateInstance(typeA, 2020, 666);
Console.WriteLine(typeA.Name);</code></pre>
The code above will not throw an error.
There are two reasons for this: 1) the type conversion to object retains information about the original type; 2) Activator.CreateInstance()
will look for the optimal constructor.
Thus, the instantiation above will call public MyClass(int a, int b)
. What if there's no match? Then it will fall back to the optimal solution.
So this little detail doesn't cause any issues.
For simple types, the lookup process is as follows:
1. Search for the corresponding constructor of the type.
Activator.CreateInstance(typeA, 2020, 666)
, where 2020 is typeof(int)
and 666 is typeof(int)
.
The optimal choice is public MyClass(int a, int b)
.
2. If not found, look for a constructor that can be implicitly converted.
For example, int -> long
;
public MyClass(long a, long b)
3. If there's no implicit conversion, then object
.
public MyClass(object a, object b)
If none match, then an error will be raised.
Let's verify:
public class MyClass
{
public MyClass(string a, string b) { }
public MyClass(int a, int b) { }
}
class Program
{
static void Main(string[] args)
{
Type typeA = typeof(MyClass);
long a = 666;
long b = 666;
object objA = Activator.CreateInstance(typeA, a, b);
Console.WriteLine(typeA.Name);
Console.ReadKey();
}
}</code></pre>
Unless something goes wrong, the above code will throw an error.
1.1.4 Intentional Error
public class MyClass
{
public MyClass(string a = null) { }
public MyClass(StringBuilder a = null) { }
}
class Program
{
static void Main(string[] args)
{
Type typeA = typeof(MyClass);
object objA = Activator.CreateInstance(typeA, null);
Console.WriteLine(typeA.Name);
Console.ReadKey();
}
}</code></pre>
If all goes well, the above code will throw an error upon execution.
This is because, during instantiation, when the parameter is null, two constructors meet the requirements.
In other scenarios, following the constructor lookup steps from 1.1.3, one can generally determine whether it will throw an error.
1.1.5 Activator.CreateInstance() Performance
Let's instantiate a type normally by declaring and assigning values a total of 10 million times.
Stopwatch time = new Stopwatch();
time.Start();
for (int i = 0; i < 10_000_000; i++)
{
int a = 666;
}
time.Stop();
Console.WriteLine(time.ElapsedMilliseconds);
time.Reset();
time.Restart();
for (int i = 0; i < 10_000_000; i++)
{
int a = 666;
}
time.Stop();
Console.WriteLine(time.ElapsedMilliseconds);
Time:
24
23
Now using reflection:
Type typeA = typeof(int);
Stopwatch time = new Stopwatch();
time.Start();
for (int i = 0; i < 10_000_000; i++)
{
object objA = Activator.CreateInstance(typeA);
}
time.Stop();
Console.WriteLine(time.ElapsedMilliseconds);
time.Reset();
time.Restart();
for (int i = 0; i < 10_000_000; i++)
{
object objA = Activator.CreateInstance(typeA);
}
time.Stop();
Console.WriteLine(time.ElapsedMilliseconds);</code></pre>
Time:
589
504
500 / 25 = 20, indeed the performance difference is over 20 times.
1.2 ConstructorInfo.Invoke()
ConstructorInfo.Invoke()
has stricter limitations than Activator.CreateInstance()
and requires stricter correspondence.
In the code from 1.1.4 where an error was intentionally caused, it can be seen that using null resulted in multiple constructors matching, leading to an error.
We can test by creating an instance using ConstructorInfo.Invoke()
.
public class MyClass
{
public MyClass(string a = null) { Console.WriteLine(6666); }
public MyClass(StringBuilder a = null) { }
}
class Program
{
static void Main(string[] args)
{
// Get the constructor uniquely based on the parameter type and count
ConstructorInfo conStruct = typeof(MyClass).GetConstructor(new Type[] { typeof(string) });
// Pass parameter values and instantiate
object objA = conStruct.Invoke(new object[] { null });
Console.ReadKey();
}
}</code></pre>
Here, we obtain the constructor of the type using typeof(MyClass).GetConstructor(new Type[] { typeof(string) });
and then instantiate using ConstructorInfo.Invoke()
.
The method GetConstructor()
overload is defined as follows:
public ConstructorInfo? GetConstructor(Type[] types);
What methods can be used to instantiate a type using public constructors? What if we want to call non-public constructors?
We can use BindingFlags
, which we will learn more about later.
2. Instantiating Delegates
Use Delegate.CreateDelegate()
method to instantiate a delegate, and use Delegate.DynamicInvoke()
to invoke the delegate and pass parameters.
The usage is:
CreateDelegate(Type, Object, MethodInfo)
Type
is the type of the delegate, while Object
and MethodInfo
are the instance type and the method.
There are two scenarios: one for instance methods and one for static methods.
We create a delegate and a type:
delegate int Test(int a, int b);
public class MyClass
{
public int A(int a, int b)
{
Console.WriteLine("A");
return a + b;
}
public static int B(int a, int b)
{
Console.WriteLine("B");
return a - b;
}
}
The experimental code in Main() is as follows:
// Bind instance method
Delegate d1 = Delegate.CreateDelegate(typeof(Test), new MyClass(), "A");
// Bind static method
Delegate d2 = Delegate.CreateDelegate(typeof(Test), typeof(MyClass), "B");
Console.WriteLine(d1.DynamicInvoke(333, 333));
Console.WriteLine(d2.DynamicInvoke(999, 333));
Console.ReadKey();</code></pre>
Output:
A
666
B
666
3. Instantiating Generic Types
3.1 Instantiating Generics
When instantiating a generic type, you can operate similarly to instantiating a regular type.
// Normal instantiation
Type type = typeof(List);
object obj = Activator.CreateInstance(type);
// The following will throw an error
Type _type = typeof(List<>);
object _obj = Activator.CreateInstance(_type);</code></pre>
When using Activator.CreateInstance
to instantiate a generic type, it must be a closed generic Type
with specified type parameters.
List<int>
is closed √; List<>
is open ×.
Additionally, the same applies when instantiating through ConstructorInfo.Invoke()
.
public class MyClass
{
public MyClass(T a)
{
Console.WriteLine(a);
}
}
class Program
{
static void Main(string[] args)
{
// Normal
ConstructorInfo type = typeof(MyClass).GetConstructor(new Type[] { typeof(int) });
object obj = type.Invoke(new object[] { 666 });
Console.ReadKey();
}
}</code></pre>
3.2 Constructing Closed Generic Types and Reflection
3.2.1 Constructing Closed Constructors
Sometimes, what is passed may be a List<>
?
Using Type.MakeGenericType(Type)
,
we can take the extra step to convert an open generic Type with unbound type parameters into a closed generic Type.
Type type = typeof(List<>);
// Constructing generic Type
Type _type = type.MakeGenericType(typeof(int));
object _obj = Activator.CreateInstance(_type);</code></pre>
3.2.2 Removing Generic Type Parameter Binding
Using the Type.GetGenericTypeDefinition()
method allows you to remove the parameter types from a generic type with bound parameter types.
Type type = typeof(List);
Console.WriteLine(type.FullName);
// Constructing generic Type
Type _type = type.GetGenericTypeDefinition();
Console.WriteLine(_type.FullName);
Output
System.Collections.Generic.List`1[[System.Int32, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]
System.Collections.Generic.List`1
List<int>
has been transformed into List<>
.
3.2.3 Let's Practice
The above section introduced the instantiation of generic types and the two usages regarding parameter types. Now let's practice.
static void Main(string[] args)
{
Type typeA = typeof(Console);
Type typeB = typeof(List<>);
Type typeC = typeof(List);
RemoveGenericTypeParameterBinding(typeA);
RemoveGenericTypeParameterBinding(typeB);
RemoveGenericTypeParameterBinding(typeC);
Console.ReadKey();
}
/// <summary>
/// Converts List<T> to List<>
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static (bool, Type) RemoveGenericTypeParameterBinding(Type type)
{
// Check if it's a generic type
if (type.IsGenericType == false)
{
Console.WriteLine("This type is not a generic type");
return (false, type);
}
// Check if it's an open generic type
if (type.IsGenericTypeDefinition)
{
Console.WriteLine("No processing needed");
return (true, type);
}
Type _type = type.GetGenericTypeDefinition();
Console.WriteLine("Processing complete");
return (true, _type);
}
}</code></pre>
文章评论