Introduction
Suppose in your application you need some sort of way to dynamically create objects on the fly. In my couple of years programming, I never had to do such a thing until now. To do this, I chose to use what is referred to in C# as Reflections (part of the System.Reflection
namespace). Reflection in computer programming is a process that allows your application to dynamically parse through DLLs in assembly, and decide at run time how to take action on the rest of your program's life time. Pretty cool stuff right? Let me give you a real world example of what this means.
Suppose you're writing a class library called "ClassSelector
" whose main purpose is to call other class' methods. Well, this is quite simple if you know the type of class you need to create an object of, and then call its method. Now suppose you have 300 classes, all with a method called "ModifyValue
". Your ClassSelector
will have no idea which class to make an instance of. You may not even know as the programmer. This is where Reflection comes in.
With Reflection, at run-time you can dynamically parse through a DLL (one that possibly contains all 300 classes that have the method "ModifyValue
"), and choose based on a parameter to your application/input from a user, which class to make a new object of. Well, that is exactly what we need! Let's explain how this works.
Background
It is expected that the reader has a basic knowledge of C#, DLL/Class Library creation/purpose.
Using the Code
Let's begin by creating a new Reflection
class library. First, create a new project in your solution called Reflectionism
. Once made, change the name of the class to ReflectionMain
(to avoid naming conflicts). And change the name of your file to ReflectionMain.cs as well. Now that this is done, let's add our typical namespace/class declaration information.
using System;
using System.Collections;
using System.Text;
using System.Reflection;
namespace Reflectionism
{
public class ReflectionMain
{
}
}
Cool, now that we have this in-place, we need to add our core method which will be called from our main entry point, passing down some parameters to decide what we want to do with our Reflection logic.
public void RunClass(string dllName, string className, string methodName)
{
Assembly _Assemblies = Assembly.LoadFrom(dllName);
Type _Type = null;
try
{
_Type = _Assemblies.GetType(className);
}
catch (Exception ex)
{
Console.WriteLine("\n\nError - couldn't obtain classrd from " + className);
Console.WriteLine("EXCEPTION OUTPUT\n" + ex.Message + "\n" + ex.InnerException);
return;
}
MethodInfo _MethodInfo = null;
try
{
_MethodInfo = _Type.GetMethod(methodName);
}
catch (Exception ex)
{
Console.WriteLine("\n\nError - couldn't obtain method " +
methodName + " from " + className);
Console.WriteLine("EXCEPTION OUTPUT\n" + ex.Message + "\n" + ex.InnerException);
return;
}
Object _InvokeParam1 = Activator.CreateInstance(_Type);
_MethodInfo.Invoke(_InvokeParam1, null);
}
Very cool! So this method is quite simple if you take time and look at it. It just takes 3 parameters (string
s):
- The DLL from which we want to load the assemblies from
- The class name we want to parse from the assemblies loaded by the DLL
- The method name we want to call from the newly created object from the
className string
.
Now I'm going to add some extra helper methods here, which are used within the MainEntry.cs file shown later in this tutorial. All you need to know about these is that they are gathering information from specified DLLs/ClassNames
so I can better display information to the user of this app when run.
private const string _ReflectionDLL = "DisplayMyself.dll";
public string ReflectionDLL
{
get { return _ReflectionDLL; }
}
private ArrayList _CurDLLTypes = null;
public ArrayList CurDLLTypes
{
get { return _CurDLLTypes; }
}
public ArrayList GetAllTypesFromDLLstring(string dllName)
{
Assembly _Assemblies = null;
try
{
_Assemblies = Assembly.LoadFrom(dllName);
}
catch (Exception ex)
{
Console.WriteLine("\n\nError - couldn't obtain assemblies from " + dllName);
Console.WriteLine("EXCEPTION OUTPUT\n" + ex.Message + "\n" + ex.InnerException);
ArrayList _Quit = new ArrayList(1);
_Quit.Add("QUIT");
return _Quit;
}
Type[] _AllTypes = _Assemblies.GetTypes();
ArrayList _Temp = new ArrayList();
foreach (Type t in _AllTypes)
{
_Temp.Add(t.ToString());
}
return _Temp;
}
public ArrayList GetAllTypesFromClass(string dllName, string className)
{
Assembly _Assemblies = Assembly.LoadFrom(dllName);
Type _Type = _Assemblies.GetType(className);
ArrayList _Temp = new ArrayList();
try
{
MethodInfo[] _Methods = _Type.GetMethods();
foreach (MethodInfo meth in _Methods)
{
_Temp.Add(meth.ToString());
}
}
catch (Exception ex)
{
Console.WriteLine("\n\nError - couldn't obtain methods from " + dllName);
Console.WriteLine("EXCEPTION OUTPUT\n" + ex.Message + "\n" + ex.InnerException);
_Temp.Clear();
_Temp.Capacity = 1;
_Temp.Add("QUIT");
}
return _Temp;
}
Now this class is great and all, but it's nothing without being put to use! So let's do that.
Create a new project called "DisplayMyself
" of type class Library. Once done, add 2 classes to it, first being DisplayMyselfOne
, and second DisplayMyselfTwo
. These will just be our test classes that will have one method within them, which will just print out a string
telling us who they are (to ensure our Reflection
class is truly creating new objects on the fly).
Add the following simple code to your test classes.
<FILE DisplayMyselfOne>
using System;
using System.Collections.Generic;
using System.Text;
using Reflectionism;
namespace Reflectionism.DisplayMyself
{
public class DisplayMyselfOne
{
public void DisplayMyself()
{
Console.WriteLine("Hello! I am " + this.ToString());
}
}
}
Do the same for DisplayMyselfTwo
, replacing all instances of "One
" with "Two
" (which is just the class name).
Also note the namespace. Both DisplayMyself
's are from the same namespace, which will make it easier for the reflection/userinput naming convention when looking for this specific class/method.
Now that we've added these two test classes, we still can't do anything! Our application needs a main entry point.
So, go ahead and add a new project called "MainEntryPoint
" of type Console Application. Name the class "EntryPoint
" and the file EntryPoint.cs.
Add the following code to the class:
using System;
using System.Collections;
using System.Text;
using System.IO;
using Reflectionism;
namespace MainEntryPoint
{
class MainEntry
{
ReflectionMain _ReflectMain = new ReflectionMain();
static void Main(string[] args)
{
MainEntry _Main = new MainEntry();
string _UserInputDLL = string.Empty;
string _UserInputClass = string.Empty;
string _UserInputMethod = string.Empty;
ArrayList _TypesFromDLL = new ArrayList();
ArrayList _TypesFromClass = new ArrayList();
ArrayList _ValidDLLs = new ArrayList();
foreach(string s in Directory.GetFiles(Directory.GetCurrentDirectory()))
{
if (s.EndsWith(".dll"))
{
_ValidDLLs.Add(s);
}
}
if (_ValidDLLs.Count <= 0)
{
Console.WriteLine("Couldn't find any DLL's Quitting");
Console.WriteLine("\nPress enter/return to quit");
Console.Read();
}
else
{
Console.WriteLine("Valid DLL's found at root path:");
Console.Write("\n");
foreach (string s in _ValidDLLs)
{
Console.WriteLine(s);
}
}
Console.WriteLine("\n\nEnter the DLL you wish to parse");
Console.WriteLine(" Note: The DLL for this app is - " +
_Main._ReflectMain.ReflectionDLL);
Console.Write("\n\nInput: ");
_UserInputDLL = Console.ReadLine();
_TypesFromDLL = _Main._ReflectMain.GetAllTypesFromDLLstring(_UserInputDLL);
if (_TypesFromDLL[0].ToString() == "QUIT")
{
Console.WriteLine("\n\nError: QUIT return code catch-
all error system in use");
Console.WriteLine("Refer to previous error for information as to why");
Console.WriteLine("\nPress enter/return to quit");
Console.Read();
return;
}
else
{
Console.WriteLine("\n\nAll available types within " +
_UserInputDLL + " are:");
foreach (string s in _TypesFromDLL)
{
Console.WriteLine("- " + s);
}
}
Console.WriteLine("\n");
Console.WriteLine("Which fully qualified class name
would you like to parse for a desired method?");
Console.Write("\n\nInput: ");
_UserInputClass = Console.ReadLine();
_TypesFromClass = _Main._ReflectMain.GetAllTypesFromClass
(_UserInputDLL, _UserInputClass);
if (_TypesFromClass[0].ToString() == "QUIT")
{
Console.WriteLine("\n\nError: QUIT return code
catch-all error system in use");
Console.WriteLine("Refer to previous error for information as to why");
Console.WriteLine("\nPress enter/return to quit");
Console.Read();
return;
}
else
{
Console.WriteLine("\n\nAll available methods within " +
_UserInputClass + " are:");
foreach (string s in _TypesFromClass)
{
if(s.Contains("DisplayMyself"))
Console.WriteLine("- " + s);
else
continue;
}
}
Console.WriteLine("\n");
Console.WriteLine("Enter method name you wish to use
(IE: \"DisplayMyself\")");
Console.WriteLine("NOTE: Currently this only supports
methods with no parameters.");
Console.Write("\n\nInput: ");
_UserInputMethod = Console.ReadLine();
try
{
_Main._ReflectMain.RunClass
(_UserInputDLL, _UserInputClass, _UserInputMethod);
}
catch (Exception ex)
{
Console.WriteLine("\n\nError in RunClass from MainEntry:
Possibly invalid dll/class/method input\n");
Console.WriteLine("EXCEPTION OUTPUT:\n" + ex.Message +
"\n" + ex.InnerException + "\n\n");
}
Console.Write("\n\nReflectionism Demo Complete -
Press enter/return to quit");
Console.Read();
}
}
}
This may seem like a lot, but it's actually quite simple in its implementation. All it's doing is displaying to the user a list of DLLs found at the current working directory. Then, it asks for user input. The input would be to enter one of those DLLs. Once entered, that DLL (if found) is parsed and all the underlying classes are displayed to the console output window. It then asks the user for another input, this time a fully qualified (namespace included) class name. After the user enters the class name, it returns back a list of all the methods (with the text "DisplayMyself
") in it. It then asks the user once again, for input. This time, the input is sent to our reflection core method "RunClass
", passing down the DLL string
, class string
, and method string
. Reflection then occurs on the given information, and a new class object of type ClassName
is created. And the MethodString
is invoked and called by the newly created object. Awesome stuff! :)
A couple of important steps. Currently, unless you put the full path to the DLLs, they are only going to work for DLLs within your app's working directory (debug or release depending on current project output) folder. It is suggested that you keep a working version of the DisplayMyself.DLL and Reflectionism.DLL and pass those around to apps that need them (This is the whole purpose of DLLs after all).
Also, you will have to add references to your implementation projects, to Reflectionism.DLL.:)
Wrap-Up
Thanks for reading!
History
- 23rd June, 2007: Initial post
I graduated from Fullsail University with a degree in Game Design and Development (Computer Science).
After college I got a job at Electronic Arts as a QA Tester. During the QA Testing job I off-tasked and wrote some tools to help the QA Department. This work was noticed and I interviewed for a job with an automation team and got the job.
My day in/out work consists of tasks such as:
Working with C#/ASP.NET/SQL/WPF/C++.
I have a deep passion for Managed languages, notably the .NET Framework.
I have a desire to learn WPF inside and out as much as some of the experts that have offered their wisdom and insight here on codeproject.
I was a Lead Programmer for a Half-Life 2 Mod named Goldeneye: Source (www.goldeneyesource.com)