Do you ever experience in writing a plugin or given a system requirement that require to load some external class library during runtime? Today's topic covers how to use Reflection (with C#) to dynamically load object into your application during runtime.
Before we jump into coding, we need to know how .NET works. As you may know, the C# or VB code that you have written are actually compiled into MSIL (Microsoft Intermediate Language). The code is also known as managed code. Then, the JIT (Just In Time) compiler will translate the MSIL into native code. Finally, the CLR (Common Language Runtime) will go through some code verification then prepare the environment like allocating system resource, initiate garbage collector, load the system domain and shared domain, create default app domain, load the metadata, create method tables and more (It's crazy to go too detail, more info about CLR intermal, read this), then create a process to execute the native code.
Early Binding
Every class, every method we write in C# are early binding. Early binding mean during the compile time, from your code, the compiler know what are the objects or the types that you have declared or created, what properties and methods you have in the class, knowing every method parameters and return type, etc. So, during compile time, it creates a "map" containing the information of your objects. And, this information is known as metadata.
Late Binding
Any object which is created after compile time is known as late binding. The Reflection can create or manipulate objects during runtime, it also can be used to inspect and retrieve any information of a class library. But, mind you, any object creation or manipulation during runtime is very slow in performance. The reason is these runtime created objects are not in any part of the "map" created by the compiler. It takes some time to locate the assembly and then load the metadata into CLR, etc.
Coding
Assuming you already have a class library that containing a class like this:
public class Cat
{
public int
ID { get; set;
}
public string
Name { get; set;
}
public int
Leg { get; set;
}
public int
Mouth { get; set;
}
public int
Eye { get; set;
}
public List<Fish>
EatenFish { get; set;
}
public
Cat()
{
this.EatenFish = new List<Fish>();
}
public void
Eat(Fish fish)
{
EatenFish.Add(fish);
}
}
You can use Reflection to load and create an instance of it into your application. Following code demonstrate how to create a new instance from the loaded assembly during runtime, but you have to know what class is available in the assembly.
Create Instance
Assembly
myAssembly= Assembly.LoadFrom(@"..\PuttyCat\bin\Debug\PuttyCat.dll");
//Late binding
//Must use fully qualified name (including namespace)
object cat =
myAssembly.CreateInstance("PuttyCat.Cat");
Above code is actually similar to:
//Early binding
Cat cat = new Cat();
Inspect Object
What if you do not know anything about the loaded assembly, you are still able to retrieve the field, property, method, parameter or any information that are available in the object by exploring the FieldInfo, PropertyInfo, MethodInfo, ParameterInfo and more by calling the method GetFields, GetProperties, GetMethods, GetParameters, etc.
Example below uses GetMethods method to get all the methods of an object with MethodInfo type and GetParameters to get all the parameters for a method with ParameterInfo type:
Example below uses GetMethods method to get all the methods of an object with MethodInfo type and GetParameters to get all the parameters for a method with ParameterInfo type:
Type[]
types = puttyCat.GetTypes();
foreach
(Type type in
types)
{
MethodInfo[]
methods = type.GetMethods();
Console.WriteLine("Type
Name: " + type.Name);
foreach
(MethodInfo method in
methods)
{
ParameterInfo[]
parameters = method.GetParameters();
Console.WriteLine("Method
Name: " + method.Name);
foreach
(ParameterInfo parameter in
parameters)
{
Console.WriteLine("Parameter
Name: " + parameter.Name);
Console.WriteLine("Parameter
Type: " + parameter.ParameterType.Name);
}
}
}
Invoke Method
After getting the MethodInfo and ParameterInfo, you may want to invoke the object method and passing in the parameter.
//Late binding
//Must use fully qualified name (including namespace)
//Get the Cat type, from there to get method info
//Late binding
//Must use fully qualified name (including namespace)
object
cat = myAssembly.CreateInstance("PuttyCat.Cat");
object
fish = myAssembly.CreateInstance("PuttyCat.Fish");
Type
catType = myAssembly.GetType("PuttyCat.Cat");
MethodInfo
eatMethod = catType.GetMethod("Eat");
//Invoke the method from cat object with parameter fish
eatMethod.Invoke(cat, new object[1]
{ fish });
Above code is actually similar to:
//Early binding
I have modified the Cat class into a generic class.
The type name for Cat is now different from the previous non generic type. If you use the Assembly.GetTypes() method to check what is the new type name for this generic class, you will notice it is now become PuttyCat.Cat`1.
//Late binding
Above code is actually similar to:
//Early binding
Above code is actually similar to:
//Early binding
//Early binding
Cat
cat = new Cat();
Fish
fish = new Fish();
cat.Eat(fish);
Generic Object
Instantiate Generic Object
Now you may wonder how about generic object? How to instantiate a generic object by using Reflection.I have modified the Cat class into a generic class.
public class Cat<T>
{
public int
ID { get; set;
}
public string
Name { get; set;
}
public int
Leg { get; set;
}
public int
Mouth { get; set;
}
public int
Eye { get; set;
}
public List<Fish>
EatenFish { get; set;
}
public
Cat()
{
this.EatenFish
= new List<Fish>();
}
public void
Eat<T>(Fish fish)
{
this.EatenFish.Add(fish);
}
}
The type name for Cat is now different from the previous non generic type. If you use the Assembly.GetTypes() method to check what is the new type name for this generic class, you will notice it is now become PuttyCat.Cat`1.
//Late binding
Type
catType = myAssembly.GetType("PuttyCat.Cat`1");
Type
pussyCatType = myAssembly.GetType("PuttyCat.PussyCat");
Type
pussyCatGenericType = catType.MakeGenericType(pussyCatType);
object pussyCat = Activator.CreateInstance(pussyCatGenericType);
Above code is actually similar to:
//Early binding
Cat<PussyCat>
pussyCat = new Cat<PussyCat>();
Invoke Generic Method
The following code is to invoke generic method:
//Late binding
MethodInfo
eatMethod = pussyCatGenericType.GetMethod("Eat");
MethodInfo
eatGenericMethod = eatMethod.MakeGenericMethod(pussyCatType);
eatGenericMethod.Invoke(pussyCat, new object[1]
{ fish });
Above code is actually similar to:
//Early binding
pussyCat.Eat<PussyCat>(fish);
Summary
Reflection give you the flexibility to load, use, inspect, manipulate any runtime loaded objects. But, this flexibility force you to sacrifice the application performance. It is advisable to avoid using it if possible. If you are creating an application that support assembly plugin which can dynamically load and use any assembly file from a specific location, then you have to use Reflection. The best you can do is avoid calling the Reflection method such as Load, GetType, GetProperty, etc frequently, design the code to load the required information once only and keep it somewhere in the memory for reuse.
There is one type call dynamic in C#. This is another one evil performance killer feature in C#. Imagine all the trouble you had done in retrieving object's PropertyInfo, MethodInfo, ParameterInfo and perform the method invoke with Reflection, now you can use the dynamic type to skip all these code.
For example, same scenario as above:
dynamic
cat = myAssembly.CreateInstance("PuttyCat.Cat");
dynamic
fish = myAssembly.CreateInstance("PuttyCat.Fish");
cat.Eat(fish);
In Visual Studio, when you use the dynamic type, you do not need to worry about getting the strong typed property name or method name error. Your code still compilable despite the property name or method name does not exist. But, if the property or method not found, error would occur during runtime.
No comments:
Post a Comment