Tuesday, June 24, 2014

C# - Reflection

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 FieldInfoPropertyInfoMethodInfo, 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:

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)
object cat = myAssembly.CreateInstance("PuttyCat.Cat");
object fish = myAssembly.CreateInstance("PuttyCat.Fish");

//Get the Cat type, from there to get method info
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
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

Send Transactional SMS with API

This post cover how to send transactional SMS using the Alibaba Cloud Short Message Service API. Transactional SMS usually come with One Tim...