Click here to Skip to main content
15,886,873 members
Articles / Programming Languages / C#
Article

Creating a Reflections Class Library and Implementation

Rate me:
Please Sign up or sign in to vote.
2.82/5 (4 votes)
23 Jun 2007CPOL5 min read 29.1K   235   23   3
Contains an example Reflection class that handles dynamic input/parsing parameters for finding/calling methods on the fly (dynamically)

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.

C#
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.

C#
/// <summary>
/// Runs target Method from target Class from target DLL.
/// </summary>
/// <param name="dllName">The DLL to load and use parse for available methods
/// </param>
/// <param name="className">The class to load from specific DLL</param>
/// <param name="methodName">The method to call from class</param>
public void RunClass(string dllName, string className, string methodName)
{
    // Create the assemblies from our current DLL.
    Assembly _Assemblies = Assembly.LoadFrom(dllName);
    // Get the type that we want from the assemblies.
    //  IE: This would be the fully qualified class name (including namespace)
    //  Example: "Reflectionism.Examples.Example1" or "Reflectionism.Examples.Example2"
    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;
    }
    // Get the desired method we want from the target type.
    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;
    }
        
    // The first parameter to pass into the Invoke Method coming up.
    Object _InvokeParam1 = Activator.CreateInstance(_Type);
    // This calls the target method ("DisplayMyself").
    //  NOTE: I'm not passing any arguments down to the method being invoked.
    //  Therefore, I'm passing null as my argument, otherwise Invoke takes an
    //  array of Objects.
    _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 (strings):

  1. The DLL from which we want to load the assemblies from
  2. The class name we want to parse from the assemblies loaded by the DLL
  3. 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.

C#
// Hardcoded DLL name for this current project's DLL.
private const string _ReflectionDLL = "DisplayMyself.dll";
public string ReflectionDLL
{
    get { return _ReflectionDLL; }
}
private ArrayList _CurDLLTypes = null;
public ArrayList CurDLLTypes
{
    get { return _CurDLLTypes; }
}
/// <summary>
/// Return all types loaded from desired DLL
/// </summary>
/// <param name="dllName">The DLL in which to parse and get the types from</param>
/// <returns>A filled ArrayList object containing all types within desired DLL 
/// (string format)</returns>
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;
}
/// <summary>
/// Returns all method names from desired DLL/Class
/// </summary>
/// <param name="dllName">The DLL in which to parse for desired class</param>
/// <param name="className">The class in which to parse for all methods</param>
/// <returns>An ArrayList filled with strings of each method from desired class
/// </returns>
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>

C#
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:

C#
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)
        {
            // New instance of our Main app's class.
            MainEntry _Main = new MainEntry();
            // Various strings that will be used for user input to determine
            //  what exactly we want to parse with our reflection methods.
            string _UserInputDLL = string.Empty;
            string _UserInputClass = string.Empty;
            string _UserInputMethod = string.Empty;
            // Will be set to all various types of classes within our app.
            ArrayList _TypesFromDLL = new ArrayList();
            // Will be set to all various types of methods within desired class.
            ArrayList _TypesFromClass = new ArrayList();
            ArrayList _ValidDLLs = new ArrayList();
            // Show us the file types of .dll extension within our current working DIR
            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: ");
            // Get user input for which DLL they want.
            _UserInputDLL = Console.ReadLine();
            
            // All your classes are belong to us!
            _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: ");
            // Get user input for which class they want to parse for a method.
            _UserInputClass = Console.ReadLine();
            // All your methods are too, belonging to us! :)
            _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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Electronic Arts
United States United States
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)

Comments and Discussions

 
Generalgood Pin
Moim Hossain23-Jun-07 17:50
Moim Hossain23-Jun-07 17:50 
Generalmethod parameters Pin
blahville23-Jun-07 9:40
blahville23-Jun-07 9:40 
GeneralRe: method parameters Pin
mariocatch23-Jun-07 10:05
mariocatch23-Jun-07 10:05 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.