Reflection in .Net – Develop & Use Dynamically Pluggable Components (Part I)
One of the highlights of .Net and its Common Language Runtime (CLR) is its support for metadata. What does it actually mean is that with each assembly (.exe or .dll), .Net compilers store information about the types it contains. Using this metadata you can load an assembly dynamically (at runtime), get information about its containing types, instantiate these types and can even generate, compile and run some code at runtime!
This is an amazing power that .Net gives you through its reflection technology, which provides the ground to develop and use the pluggable components during the execution of application. Before we go on to further details, lets have some brief review of .Net assemblies, modules, types and how CLR manages them.
Brief overview of Assembly & Modules
An assembly is a physical collection of related modules and types used for deployment. There are two important points in this definition:
- While namespace is a logical collection of related types, an assembly is a physical collection of related types. Strictly speaking, an assembly exists in file system in the form of .exe or .dll file.
- An assembly is a deployment unit of your code. We deploy our application in the form of assemblies.
At this point, I think the concept of assembly is quite clear to you; but one thing that might be pondering in your mind is what actually the module mean then? In fact, an assembly may contain one or more modules. A module is again a physical collection of types.
You can compile some part of application or component in module (which gets stored in the file system as .netmodule file) and later combine all the related modules to make an assembly. So what exactly is the difference between a module and assembly? An assembly essentially contains something called ‘Manifest’ while a module does not. Finally, the manifest of an assembly contains the metadata with following information:
- Versioning information of an assembly
- An entry for each file that constitutes the assembly
- An entry for each type in the assembly
- An entry for each resources or other assemblies referenced (used) by this assembly
Most of the times, developers do not use multiple modules to pack their types but just a single module per assembly. Hence, an assembly usually contain types (classes, interfaces, structures, events) while types contain members (like variables, methods, properties).
Reflection
The dictionary meaning of the word ‘Reflection’ is ‘to look back to itself’. This is exactly what is meant by the term reflection in programming world. Reflection is an ability of application to look into the structure of itself. Using the reflection technology, we can retrieve the information about the contents of an assembly (constituting classes and interfaces).
Similarly we can drill down to get the members of these types like fields, methods, and properties. Not only this, but we can also instantiate these types and invoke the methods on these. We can even prepare, compile, and load assemblies at runtime!!!
===== Author’s Note: ===== The concept of reflection in .Net (C# & VB.Net) is similar to that in Java. The standard C++ does not support the reflection but there are a number of third party libraries available that make reflection possible in C++
Reflection in .Net
The System.Reflection is the root namespace that contains classes to implement the reflection. The Assembly class is the one which is used to represent the assembly in .Net environment. A simple program that loads the assembly and prints the names of the types that it contains is presented below:
using System;
using System.Reflection;
namespace ReflectionTest
{
class Test
{
[STAThread]
static void Main(string[] args)
{
// Load an assembly from file
Assembly myAssembly = Assembly.LoadFrom("ReflectionTestAssembly.dll");
// Get the types contained in the assembly and print their names
Type []types = myAssembly.GetTypes();
foreach(Type type in types)
{
Console.WriteLine(type.FullName);
}
}
}
}
Here we have used the static method of the Assembly class ‘LoadFrom()’ which accepts the complete path and name of the assembly to load in string format. The method ‘LoadFrom()’ loads an assembly into CLR and returns an instance of type ‘Assembly’ which is an abstraction of the loaded assembly. Using this instance of type ‘Assembly’, we can do a lot of things.
In the above example, we have called the ‘GetTypes’ method of the ‘Assembly’ class which gives an array of all the types contained in an assembly. The types are represented by the ‘System.Type’ class. We then iterated the array and printed the names of all the types in the assembly on Console. When the above program is run, it produces the following output on console
ReflectionTestAssembly.MyInterface
ReflectionTestAssembly.MyClass
ReflectionTestAssembly.OurClass
Press any key to continue
Some important methods of the Assembly class are:
| Method | Description |
|---|
| CreateInstance(string) | Creates an instance of the specified type in the assembly and returns its object |
| GetAssembly(Type) static | This static method returns the assembly that contains the specified type. |
| GetCallingAssembly() static | This static method returns the assembly of the method from which the method containing this code has been called. |
| GetExecuingAssembly() static | This static method returns the assembly that contains this code.For example, if we call a method in assembly B from assembly A, and the method in assembly B contains both GetCallingAssembly() and GetExecutingAssembly() then, GetCallingAssembly() will return Assembly A while GetExecutingAssembly() will return Assembly B |
| GetModule(string) | Returns the specified module that is part of this assembly |
| GetModules() | Returns an array of all the modules of this assembly |
| GetReferencedAssemblies() | Returns an array of all the assemblies referenced by this assembly. The type of array is AssemblyName |
| Load(AssemblyName) overloaded static | This static method loads an assembly into CLR provided its AssemblyName |
| LoadFrom(string) static | This static method load an assembly into CLR from the specified file |
Similarly, some important properties and methods of the Type class are presented in the following table
| Property | Description |
|---|
| Assembly | Returns the assembly of this type |
| Namespace | Returns the namespace of this type |
| Attriutes | Returns the attributes associated with this type |
| BaseType | Returns the direct base class of the type |
| FullName | Returns the fully qualified name of the type including namespace |
| IsClass | Returns a Boolean value representing whether a type is a class or not |
| IsInterface | Returns a Boolean value representing whether a type is an interface or not |
| IsEnum | Returns a Boolean value representing whether a type is an enumeration or not |
| IsCOMObject | Returns a Boolean value representing whether a type is a COM object or not |
| IsAbstract | Returns a Boolean value representing whether a type is an abstract class or not |
| IsByRef | Returns a Boolean value representing whether a type is referenced type or not (i.e., passed by reference) |
| IsValueType | Returns a Boolean value representing whether a type is value type or not (i.e., passed by value) |
| IsPublic | Returns a Boolean value representing whether a type is public or not |
| IsSealed | Returns a Boolean value representing whether a type is sealed (can not be inherited) or not |
| IsSerializable | Returns a Boolean value representing whether a type is serializable (can be passed to a stream) or not |
And then some of the important methods of Type class are:
| Method | Description |
|---|
| GetInterfaces() | Returns all the interfaces implemented or inherited by the type |
| IsSubclassOf(Type) | Returns Boolean representing whether this type is derived from the type supplied in the parameter |
| GetNestedTypes() | Returns all the types nested in this type| GetConstructor(Type[])overloaded | Returns an object of type ConstructorInfo representing the constructor of the type which takes the parameter types matching the supplied types | | GetConstructors() | Returns an array of ConstructorInfo object representing the constructors of the type. | | GetMembers() | Returns an array of MemberInfo object representing the members (fields, properties, methods) of this type | | GetFields() | Returns an array of FieldInfo object representing the fields of this type | | GetProperties() | Returns an array of PropertyInfo object representing the properties of this type | | GetMethods() | Returns an array of MethodInfo object representing the methods of this type | | GetEvents() | Returns an array of EventInfo object representing the events of this type | | InvokeMember() | Invoke a member (constructor, field, property and method) on the specified object type represented by this instance | | IsInstanceOfType() | Returns a boolean representing whether the current type is derived from the specified type or not | |
Now as we have seen the few key methods and properties of these classes; lets see some sample code to perform these tasks.
Exploring the hierarchy of types in an assembly
In the following sample code, we will demonstrate how we can explore the hierarchy of types in an assembly. We will get all the types contained in an assembly, list these types along with their parent class and implementing interfaces. The source code of the program looks like:
using System;
using System.Reflection;
namespace ReflectionTest
{
class Test
{
[STAThread]
static void Main(string[] args)
{
// Load an assembly from file
Assembly myAssembly = Assembly.LoadFrom("ReflectionTestAssembly.dll");
// Get the types contained in the assembly and print their names
Type []types = myAssembly.GetTypes();
foreach(Type type in types)
{
if(type.IsInterface)
{
Console.WriteLine("\r\n" + type.FullName + " is an interface");
}
else if(type.IsClass)
{
// Get and print the list of its parent class and interfaces
Console.WriteLine("\r\n" + type.FullName +
" is a class whose base class is " +
type.BaseType.FullName);
Console.WriteLine("\t It implements the following interfaces");
foreach(Type interfaceType in type.GetInterfaces())
{
Console.WriteLine("\t\t {0}", interfaceType.FullName);
}
}
}
Console.ReadLine();
}
}
}
Here we got the list of types in the assembly just as we did in the previous example. We iterated through the types, checked whether it is an interface or a class using the IsInterface and IsClass properties of the Type class
if(type.IsInterface)
{
...
}
else if(type.IsClass)
{
...
}
If the type is a class, we retrieved the name of its immediate parent class. Since all classes in .Net are implicitly inherited from the Object class and no class can have more than one immediate parent class, hence we are sure that the property BaseType will return exactly one type instance.
Console.WriteLine("\r\n" + type.FullName + " is a class whose base class is "
+ type.BaseType.FullName);
On the contrary, a class may or may not implement interfaces, so we iterated through the list of implemented interfaces (if any) using the GetInterfaces() method which returns an array of Type
foreach(Type interfaceType in type.GetInterfaces())
{
...
}
The complete source code of the above example can be downloaded from
here
Exploring the contents of types
Now when we have learnt how to explore the contents of assembly, lets learn how to get the contents of types. The following sample will get the list of members (fields, properties and methods) of a class ‘MyClass’ which is defined in the assembly ‘ReflectionTestAssembly’ as
using System;
using System.Reflection;
namespace ReflectionTestAssembly
{
public interface MyInterface
{
void Fun(int iParam);
}
public class MyClass : MyInterface
{
private int number;
public readonly double PI = 3.14;
public MyClass()
{
Console.WriteLine("MyClass instantiated");
number = 6;
}
public int Number
{
get { return number; }
set { number = value; }
}
public int GenerateRandom()
{
Random r = new Random();
return r.Next();
}
public void Fun(int iParam)
{
Console.WriteLine("The parameter supplied is fortunately {0}", iParam);
}
public void SayGreeting()
{
Console.WriteLine("Hello World!");
}
}
}
Next