Click here to Skip to main content
Click here to Skip to main content
 

Using reflection to extend .NET programs

By , 9 Jan 2002
Rate this:
Please Sign up or sign in to vote.
<!-- Download Links --> <!-- Add the rest of your HTML here -->

Introduction

Every book I've read on COM has always included an example where a theoretical program would query the system for all components that implemented certain interfaces. This technique could be used to let those components extend the base programs functionality or provide other new features.

I haven't seen much on doing the same with .NET though, so I set out to write this article.

For brevity very little try/catches will be used in my code, in your project you should expect any exception listed in the documentation to be thrown. I think I have included enough comments either in my code or in the article to make everything clear. If I haven't send me an e-mail or post in the comments section at the bottom.

What you will need

I will be using Visual Studio.NET RC0 (ie. PDC release) for this but everything I do should be possible from the command line tools as well. You'll have to refer to the documentation for the proper compiler switches to use though.

If you are using an earlier version of VS.NET/.NET Framework you will have to recompile the demo to use it; you also may have to re-create the projects yourself and use my files. Both the ExtendedType and the MainProgram should have a reference to the BaseType project.

You will need three different .NET assemblies for our project.

  1. The interfaces that will dictate how components and our program will interact
  2. The assembly based on #1
  3. The main program that will use #1 to interact with #2

Setting up the environment

In this solution you will have 3 projects. The main program, the interface that hooks your program to the add-ins, and the simple add-in. You could put the interface and the main program in the same project, but you would then be giving any add-ins more access to your inside code.

First create a blank solution. Place the solution in a directory by itself so its easier to find the 3 projects you will be using.

Now right click the solution name and choose "Add Project", now create a new Class Library project for the solution calling it "Reflection.BaseType". The name just sets up our root namespace as well as the directory name. This will be referred to as the BaseType project

Right click on the solution name and add another new Class Library project, call it "Reflection.ExtendedType". Right click the project name in the Solution Explorer and choose Add Reference. On the Projects tab you should see the BaseType project listed. Select that project and choose OK. Now when this project is compiled it will compile the BaseType project as well, ensuring you are referring to the latest code. This will be referred to as the add-in or the ExtendedType project.

Finally add a console application to your solution, call it "Reflection.MainProgram". Again, add a reference to the BaseType project. This will be the main program and will load the add-in at runtime.

Step 1: The interfaces

Since this is just an example you'll only use a very simple interface; in the real world a simple interface probably won't cut it. You'll need to use one or more interfaces to extend your program. One way to do it is to have a single interface that you search for which has properties for each other interface you will need. This keeps searching through assemblies down to a minimum.

ISayHello.cs
using System;

namespace Reflection.BaseType
{
    /// <summary>
    /// ISayHello is our gateway into the extended types from 
    /// our program.  Any communication that takes place will be through
    /// one of our interfaces.
    /// </summary>
    public interface ISayHello
    {
        /// <summary>
        /// Property:    Name
        /// Access:      ReadOnly
        /// Description: Returns the name of the class that implements this interface. 
        /// </summary>
        string Name { get; }

        /// <summary>
        /// Method:      SayHello
        /// Access:      Public
        /// Description: When called it will print a greeting to the console
        /// </summary>
        void SayHello();
    }
}

Like was mentioned before, this is a very simple interface. But through it you should be able to see how to do more complex operations.

Step 2: The extended type

Now you will create an assembly that will be loaded at runtime by the program to perform its deed. In this case it will say hello.

Select the BaseType project and rename Class1.cs to HelloFromMTU.cs. In this file you can create your class that will implement the base interface, ISayHello

HelloFromMTU.cs
using System;
using Reflection.BaseType;

namespace Reflection.ExtendedType
{
    /// <summary>
    /// Summary description for Class1.
    /// </summary>
    public class HelloFromMTU : Reflection.BaseType.ISayHello 
    {
        /// <summary>
        /// Default Constructor, nothing really needed
        /// </summary>
        public HelloFromMTU()
        {
        }

        /// <summary>
        /// ISayHello.Name
        /// 
        /// I could use the HelloFromMTU's Type object to get 
        /// the name, but this is simpler :)
        /// </summary>
        public string Name
        {
            get
            {
                return "HelloFromMTU";
            }
        }

        /// <summary>
        /// ISayHello.SayHello
        /// 
        /// Prints out a greeting to the console
        /// </summary>
        public void SayHello()
        {
            Console.WriteLine("Hello from Michigan Tech, land of the cold");
        }
    }
}

Step 3: Main program

Now you can get to writing the main program. This time the project type will be a Console Application, but we can just as easily use this technique with any of the other project types. Even an ASP.NET application could use this technique, but it may have to have special considerations given the stateless nature of ASP programming.

Loading assemblies from files

The easiest way of loading assemblies is using the Assembly.LoadFrom() method, this takes a string, the filename of the assembly. From the assembly we can find out virutally everything about its contents, including what types are stored in it and their base classes and what interfaces they implement.

One way to find out if an assembly is meant to be used with our program is to query each of the types in it to see if they implement our interface. You do this by first asking the assembly for all of its types, then asking each type if they implement your interface.

In the LoadTypes function, when it finds a type that implements the interface it prints out the full typename to show that it has been found.

Note that you have to catch the BadImageFormatException, this exception gets thrown from Assembly.LoadFrom when you try to load a file that isn't an assembly. If you have any other DLLs in the directory these will try to get loaded when you search the directory for DLLs.

public int LoadTypes()
{
    string currentDirectory = System.IO.Directory.GetCurrentDirectory();
    string [] dllFilenames = System.IO.Directory.GetFiles(currentDirectory, "*.dll");
    
    foreach(string filename in dllFilenames)
    {
        try
        {
            Assembly asm = Assembly.LoadFrom(filename);

            Type [] typesInAssembly = asm.GetTypes();
            foreach(Type type in typesInAssembly)
            {        
                if(null != type.GetInterface(typeof(Reflection.BaseType.ISayHello).FullName))
                {
                    // We found a type, load it
                    Console.WriteLine(string.Format("Assembly: {0} has a type {1} that " + 
                                                    "<a name="step3.1">implements ISayHello", 
                                                     asm.FullName, type.FullName));
                    
                    types.Add(type);
                }
            }
        }
        catch(BadImageFormatException e)
        {
            // Not a valid assembly, move on
        }
    }

    // Used to tell Main how many types we loaded
    return types.Count;
}

Using Type to create instances of objects

Now that you have an ArrayList of Type's, what can you do with them? Well, you probably want to create an instance of that type. Using Activator.CreateInstance you can create instances of anything, so long as you have a Type to go with it.

One of the overloads of CreateInstance that you can use accepts a Type as its only parameter and returns an object which you can cast back to your type. In this case you don't want to cast back to the extended type, just the interface that has been defined for interaction. This keeps you kosher with your agreement to use an interface in the first place.

Once you have an instance of your interface you can call to it just as if you had created the instance of the class with the new keyword.

public void SayHello()
{
    foreach(Type type in types)
    {
        ISayHello ish = (ISayHello) Activator.CreateInstance(type);

        Console.WriteLine(String.Format("{0} says:", ish.Name));
        ish.SayHello();
    }
}

Testing our program

You should now be able to compile the entire solution. Before you run the program you have one step left to do. You need to copy the extended type's dll into the same directory as our program because that is where we search for our types.

Once you have copied the dll over thats all you should have to do! Now you can run the program and see a simple case of reflection at work. I have already done this step in the demo zip file, however if you don't have RC0 of VS.NET you will have to recompile the project to get it to work as you won't have the same version of the system assembilies that I have.

Future uses of this technique

This is just a simple use of reflection. In your projects you would probably want to use something much more complex. In my current project I have one interface that is required, but it has properties that gives me the Type for other objects, including Form windows and Images.

If you wanted you could require that outside assemblies inherit from a base class you provide. This works as well, but I figured more people would be familiar with the interface technique used in COM.

To use the base class technique you need to use the Type's method, IsSubclassOf instead of searching for an interface.

Summary

In this article I have shown how easy it is to use reflection to extend your program in ways that can make your job as a developer easier and faster. This technique has been employed in COM programs for years, in tools like IE and Visual Studio. Now .NET developers get to do it as well, this time without having to muck with the registry.

If anyone finds any bugs or has any comments leave them below and I'll answer ASAP. Enjoy my first article

Updates

  • December 15, 2001 - Added explaination of project types used and changed lots of wording to clarify my writing. If you had problems reading it the first time, please read through it again.

License

This article, along with any associated source code and files, is licensed under The BSD License

About the Author

James T. Johnson
Software Developer (Senior) InfoPlanIT, LLC
United States United States
James has been programming in C/C++ since 1998, and grew fond of databases in 1999. His latest interest has been in C# and .NET where he has been having fun writing code starting when .NET v1.0 was in its first beta.
 
He is currently a senior developer and consultant for InfoPlanIT, a small international consulting company that focuses on custom solutions and business intelligence applications.
 
He was previously employed by ComponentOne where he was a Product Manager for the ActiveReports, Data Dynamics Reports, and ActiveAnalysis products.
 
Code contained in articles where he is the sole author is licensed via the new BSD license.
Follow on   LinkedIn

Comments and Discussions

 
Questionhow to load selectively PinmemberMember 159112730-Sep-09 1:29 
JokePlease Me Can U Hlp PinmemberSling Blade 2226-Aug-09 17:53 
GeneralHai can u plz help me Pinmembersiddabattini14-May-07 6:53 
Generalthank you pal PinmemberHunterInCom16-Feb-07 0:25 
GeneralReally excellent article Pinmemberlowmodelrs1-Dec-06 5:39 
Generalthank you! Pinmemberxreven6-Jun-06 19:24 
Generalloading 1.1 assembly in 2 Pinmemberpammans21-Mar-06 7:50 
Questionhow can i write a plug-in for internet explorer? Pinmemberpa_sa3-Jul-05 2:56 
GeneralExcellent Example but Assembly missing from Source code Pinmemberand_comp27-Apr-05 0:10 
GeneralRe: Excellent Example but Assembly missing from Source code Pinmemberbeernutty2-Nov-05 7:37 
Questiontypes? PinmemberChris Nevill25-Apr-05 21:57 
AnswerRe: types? Pinmemberharshilsurti15-Nov-07 1:01 
GeneralHelp me about reflections PinmemberAdeela Kamran4-Apr-05 1:03 
Generalreflection help PinmemberImmortal Fire3-Jan-05 0:36 
GeneralHelp on reflection and parameters Pinmembermichel st-louis25-Oct-04 6:13 
GeneralC# ok, C++ Not ok... PinsussHarold Blankenship18-Aug-04 9:27 
GeneralCurrent assembly implementations too PinmemberHugo Hallman22-Jul-04 15:26 
Generalthanks Pal PinsussPathik H Rawal6-Jul-04 1:41 
GeneralActivator.CreateInstance(t); PinmemberSHG7-Nov-03 3:37 
QuestionRe: Activator.CreateInstance(t); PinmemberGangisetti26-Jun-06 5:30 
GeneralRe: Activator.CreateInstance(t); Pinmemberjlac102428-Jan-08 10:47 
GeneralRe: Activator.CreateInstance(t); PinmemberMember 463394424-Feb-09 16:29 
GeneralThankx PinmemberJimmy M Joy8-Aug-03 21:12 
AnswerRe: What am I missing about INterfaces PinmemberJames T. Johnson10-Apr-03 8:01 
Generalhelp me,vertically input text Pinmemberjirigala3-Dec-02 19:27 

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

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

| Advertise | Privacy | Mobile
Web03 | 2.8.140421.2 | Last Updated 10 Jan 2002
Article Copyright 2001 by James T. Johnson
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid