Using Reflection with C# .NET

Abstract

Assemblies are the core unit of deployment. At design time, we can examine the CIL code in a set of reference assemblies with a couple of external tools such as Reflector and ildasm to peek into underlying metadata, MSIL code and the manifest.

But many times it is necessary to discover types at runtime, and this is the situation where .NET Reflection can be used. This article will also explain late binding that is very related to reflection.

What is Reflection?

Reflection typically is the process of runtime type discovery to inspect metadata, CIL code, late binding and self-generating code. At run time by using reflection, we can access the same "type" information as displayed by the ildasm utility at design time. The reflection is analogous to reverse engineering in which we can break an existing *.exe or *.dll assembly to explore defined significant contents information, including methods, fields, events and properties.

You can dynamically discover the set of interfaces supported by a given type using the System.Reflection namespace. This namespace contains numerous related types as follows:

Types

Description

Assembly

This static class allows you to load, investigate and manipulate an assembly.

AssemblyName

Allows to exploration of abundant details behind an assembly.

EventInfo

Information about a given event.

PropertyInfo

Holds information of a specified property.

MethodInfo

Contains information about a specified method.

Reflection typically is used to dump out the loaded assemblies list, their reference to inspect methods, properties etcetera. Reflection is also used in the external disassembling tools such Reflector, Fxcop and NUnit because .NET tools don't need to parse the source code similar to C++.

Metadata Investigation

The following program depicts the process of reflection by creating a console based application. This program will display the details of the fields, methods, properties and interfaces for any type within the mscorlib.dll assembly. Before proceeeding, it is mandatory to import "System.Reflection".

Here, we are defining a number of static methods in the program class to enumerate fields, methods and interfaces in the specified type. The static method takes a single "System.Type" parameter and returns void.

static void FieldInvestigation(Type t)
{
    Console.WriteLine("*********Fields*********");
    FieldInfo [] fld= t.GetFields();
    foreach(FieldInfo f in fld)
    {
        Console.WriteLine("-->{0}", f.Name);
    }
}

static void MethodInvestigation(Type t)
{
    Console.WriteLine("*********Methods*********");
    MethodInfo [] mth = t.GetMethods();
    foreach (MethodInfo m in mth)
    {
        Console.WriteLine("-->{0}", m.Name);
    }
}

Now we are done with the static method. It is time to call them in the main() method that first prompts the user for the fully-qualified name of the type. Once we obtain the type in string format, we pass it into the "Type.GetType()" method and send the extracted Type object into each of the static methods as:

static void Main(string[] args)
{
    Console.Write("Enter the Name to Explore:");
    string typName = Console.ReadLine();

    Type t = Type.GetType(typName);
    FieldInvestigation(t);
    MethodInvestigation(t);
 
    Console.ReadKey(); 
}


Finally run the application and examine the content of the System.Math type when the screen prompts as:

Reflection1.jpg

Dynamic Assembly Loading

Technically, the act of loading external assemblies on demand is known as Dynamic Loading. Using the Assembly class, we can dynamically load both private and shared assemblies from the local location to a remote location as well as, explore its properties.

To illustrate dynamic loading, we are creating a console based application that loads an external TestLib.dll assembly. During the execution, the application asks the user to specify the dynamic loading assembly name and that reference is passed to the helper method that is responsible for loading the assembly as:

using System;
using System.Reflection;

namespace Reflection
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.Write("Enter External Assembly:");
            string typName = Console.ReadLine();
            try
            {
                Assembly asm = Assembly.Load(typName);
                DispalyAssembly(asm);
            }
            catch
            {
                Console.WriteLine("Can't Load Assembly"); 
            }
            Console.ReadKey(); 
        }

        static void DispalyAssembly(Assembly a)
        {
            Console.WriteLine("*******Contents in Assembly*********");
            Console.WriteLine("Information:{0}",a.FullName);
            Type[] asm = a.GetTypes();
            foreach (Type tp in asm)
            {
                Console.WriteLine("Type:{0}", tp);
            }
        }
    }
}


After successfully compiling the application, the important point to remember is that you need to copy the dynamically loading TestLib.dll binary into the solution Bin\Debug folder to run this program properly. Here, the output is as follows:

Reflection2.jpg

If you wish to make this program more flexible then you can update your code to load the external assembly using the LoadFrom() method. In such a situation you don't need to place the external assembly in the Bin\Debug folder.

static void Main(string[] args)
{
    try
    {
        Assembly asm = Assembly.LoadFrom(@"E:\TestLib.dll");
        DispalyAssembly(asm);
    }
    catch
    {
        Console.WriteLine("Can't Load Assembly");
    }
}

We can also implement reflection on shared assemblies. We must pass sets of information that identify an assembly, such as assembly name, version, publicKeyToken as in the following:

class Program
{
    static void Main(string[] args)
    {
        try
        {
            string info = @"System.Windows.Forms," + "Version=4.0.0.0," +
                          "PublicKeyToken=B77A5C561934E089," +
                          @"Culture=""";
            Assembly asm = Assembly.Load(info);
            DispalyAssembly(asm);
        }
        catch
        {
            Console.WriteLine("Can't Load Assembly");
        }
        Console.ReadKey();
    }

    static void DispalyAssembly(Assembly a)
    {

        Console.WriteLine("Name:{0}", a.GetName().Name);
        Console.WriteLine("Version:{0}", a.GetName().Version);
        Console.WriteLine("Culture:{0}", a.GetName().CultureInfo.DisplayName);
        Console.WriteLine("Loaded from GAC?:{0}", a.GlobalAssemblyCache);
    }
}


The output of that program will be as follows:

Reflection3.jpg

Late Binding

The .NET framework can create an instance of a given type using early binding or late binding. In early binding, we typically set the external assembly reference in the project and allocate the type using the new operator. Early binding allows us to determine errors at compile time rather than at runtime.

Whereas in late binding, you can create an instance of a given type and invoke its methods at runtime without having knowledge at compile time. There is no provision to set an external assembly reference in this construct.

We can create a late binding instance of an external assembly using the CreateInstance() method of the System.Activator static class. Here, we are dynamically instantiating a utility class of the TestLib.dll; the code is refreshingly simple, as in the following:

using System;
using System.Reflection;

namespace Reflection
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                Console.WriteLine("*******Assembly Late Binding*********");

                Type t = Type.GetType("TestLib.utility,TestLib");
                object obj = Activator.CreateInstance(t);
                Console.WriteLine("Create a {0} using late binding", obj);

                MethodInfo mth = t.GetMethod("Test");
                mth.Invoke(obj, null);
                Console.WriteLine("Method Invoked");
            }
            catch
            {
                Console.WriteLine("Can't Create Assembly Instance");
            }
            Console.ReadKey();
        }
    }
}


Here you don't need to put the external assembly binary copy in the solution Bin/Debug folder like earlier. Once the code is complete and you compile this application, you obtain this output as:

Reflection4.jpg