Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Using Reflection to load unreferenced assemblies at runtime in C#

0.00/5 (No votes)
24 Jan 2009 1  
Very useful for making plug-in or addon support in .NET applications.

Introduction

Sure, we can define our application's referenced assemblies easily, but how would we interact with types in assemblies at runtime that we don't directly reference? This can be quite a problem for someone who is creating plug-in or add-on support for their application, where the assemblies they must reference will be added post-build. One of the functions of the System.Reflection namespace is loading assemblies and accessing their contained types.

Reflecting on an assembly and its types

To demonstrate this, let's setup a hypothetical situation in which we have an application, and a .NET DLL unreferenced by our application whose types we need to access at runtime. The DLL will be called MyDLL.dll, and the application will be called MyApp.exe. How would we view the types contained inside MyDLL from MyApp at runtime when MyDLL isn't referenced by MyApp? The System.Runtime.Assembly class can be used to access MyDLL in the following manner:

System.Reflection.Assembly myDllAssembly = 
   System.Reflection.Assembly.LoadFile("%MyDLLPath%\\MyDLL.dll");

The myDllAssembly object can now be used to access the types contained inside MyDLL. To access a type contained inside MyDLL, System.Reflection.Assembly possesses a method called GetType(), which can return a System.Type that can be used to access all the members of the type we want to retrieve from inside MyDLL. Note that the System.Reflection.Assembly.GetType() method is not to be confused with the GetType() method possessed by all objects; they are related, but quite different, and we will discuss the other GetType() method later. Back to our example; let's say, there is a type inside MyDLL called MyDLLForm which inherits from System.Windows.Forms.Form and is contained in the namespace MyDLLNamespace. To obtain the System.Type of this, we could do the following:

System.Reflection.Assembly myDllAssembly = 
   System.Reflection.Assembly.LoadFile("%MyDLLPath%\\MyDLL.dll");
System.Type MyDLLFormType = myDllAssembly.GetType("MyDLLNamespace.MyDLLForm");

MyDLLFormType now holds the System.Type of MyDLLForm. We can now access all the members and contained types inside MyDLLForm from MyDLLFormType. However, using this GetType() method will not create an instance of MyDLLForm. To access a type contained inside MyDLL and create an instance of it, System.Reflection.Assembly possesses another method called CreateInstance(). Say, we wanted to create an instance of MyDLLForm, we could use something like this:

System.Reflection.Assembly myDllAssembly = 
  System.Reflection.Assembly.LoadFile("%MyDLLPath%\\MyDLL.dll");
Form MyDLLFormInstance = (Form)myDllAssembly.CreateInstance("MyDLLNamespace.MyDLLForm");

MyDLLFormInstance now holds an instance of MyDLLForm (remember that MyDLLForm inherits from Form). To create an instance of MyDLLForm, that method will invoke MyDLLForm's constructor. By way of that overload, CreateInstance is expecting a constructor that takes no arguments, as we only passed it the type name we want to create an instance of. However, let's say there is a constructor we want to use to create an instance of MyDLLForm that takes two arguments, a string and an int. To do that, and for example, to pass it a string of "Hi" and an integer value of 113 (one of my favorite numbers), we could use this:

System.Reflection.Assembly myDllAssembly = 
   System.Reflection.Assembly.LoadFile("%MyDLLPath%\\MyDLL.dll");

//the arguments we will pass
object[] argstopass = new object[] { (object)"Hi", (object)113 };

Form MyDLLFormInstance = (Form)myDllAssembly.CreateInstance("MyDLLNamespace.MyDLLForm",
     false, //do not ignore the case
     BindingFlags.CreateInstance, //specifies we want to call a ctor method
     null, //a null binder specifies the default binder will be used (works in most cases)
     argstopass, //the arguments (based on the arguments,
                 //CreateInstance() will decide which ctor to invoke)
     null, //CultureInfo is null so we will use 
           //the culture info from the current thread
     null //no activation attributes
     );

Read the comments, they should tell you what's happening pretty good.

Reflecting on a single type

Fields

The CreateInstance() method can easily create an instanced object of any type in an assembly we've loaded in a System.Reflection.Assembly object; however, you can see that in the example above, MyDLLFormInstance is of type System.Windows.Forms.Form, which is the inherited type of MyDLLForm. However, say there are custom members contained inside MyDLLForm that we need to access. MyDLLFormInstance is of type System.Windows.Forms.Form, so any member we have created inside MyDLLForm cannot be accessed directly by MyDLLFormInstance. This is where that other GetType() methods that all objects possess comes in handy. Let's say, for an example, there is a public string variable contained inside MyDLLForm called StringVariable. To retrieve the value of that variable from MyDLLFormInstance, the following code can be used:

string StringVariableValue = (String)MyDLLFormInstance
    .GetType() //Get the type of MyDLLForm
    .GetField("StringVariable") //Get a System.Reflection.FieldInfo
                                          //object representing StringVariable
    .GetValue(MyDLLFormInstance); //Get the actual value of StringVariable

That will assign StringVariableValue to the value of StringValue contained in MyDLLFormInstance. The GetType() method is used to get the System.Type of MyDLLFormInstance. It will return a type that represents MyDLLForm, because even though MyDLLFormInstance is declared as a Form, its actual value is MyDLLForm. We can then use the GetField() method of that System.Type to return the value of the StringVariable field in MyDLLFormInstance.

Say, however, we want to set StringVariable in that same instance instead of retrieving its value. We can use almost the same method, except instead of calling the GetValue() method of the FieldInfo that represents StringValue, we can use the SetValue() method, like so:

MyDLLFormInstance
    .GetType() //Get the type of MyDLLForm
    .GetField("StringVariable") //Get a System.Reflection.FieldInfo 
                                          //object representing StringVariable
    .SetValue(MyDLLFormInstance, //the object whose StringVariable 
                                 //field we want to set (MyDLLFormInstance)
        (object)"This string will be the new value of StringVariable"
        //the new value of StringVariable
        );

That will then set the StringVariable field in MyDLLFormInstance.

Properties

But, what if there is a property in MyDLLForm that we want to retrieve in MyDLLFormInstance, called StringProperty, for example. We can use this method to get the property's value:

MyDLLFormInstance
    .GetType() //Get the type of MyDLLForm
    .GetProperty("StringProperty") //Gets a System.Reflection.PropertyInfo 
                                             //object representing StringProperty
    .GetValue(MyDLLFormInstance, null); //Gets the property's value

Now, if we wanted to set the value of the same property (if the property isn't read-only):

MyDLLFormInstance
    .GetType() //Get the type of MyDLLForm
    .GetProperty("StringProperty") //Gets a System.Reflection.PropertyInfo 
                                             //object representing StringProperty
    .SetValue(MyDLLFormInstance, 
      (object)"This will be the new value of StringProperty", null);
      //Sets the value of StringProperty

Methods

Now you know how to modify and retrieve the value of public fields and properties contained inside assemblies loaded in a System.Reflection.Assembly object. However, probably the most important is knowing how to invoke methods. So, using the same MyDLL scenario, let's say there is a method in MyDLLForm that returns void and takes no arguments, called SomeMethod. This will be quite easy to invoke, as there is no argument to pass, and we will not need to retrieve the value. To invoke that method, we can easily use, on the same MyDLLFormInstance object:

MyDLLFormInstance
    .GetType() //Get the type of MyDLLForm
    .GetMethod("SomeMethod") //Gets a System.Reflection.MethodInfo 
                                       //object representing SomeMethod
    .Invoke(MyDLLFormInstance, null); //here we invoke SomeMethod. The arguments list 
                                      //is null because it takes no arguments

Let's make this situation a bit more complex. Let's say SomeMethod takes two arguments, a string and an int. Let's use the same two example values from our CreateInstance example; "Hi" as the string and 113 as the integer. Remember, SomeMethod takes arguments, but it still returns void, so we don't need to retrieve the value. To invoke SomeMethod this time, we could use:

//This is the arguments list. "Hi" is the string and 113 is the int
object[] argstopass = new object[] { (object)"Hi", (object)113 };

MyDLLFormInstance
    .GetType() //Get the type of MyDLLForm
    .GetMethod("SomeMethod") //Gets a System.Reflection.MethodInfo 
                                       //object representing SomeMethod
    .Invoke(MyDLLFormInstance, argstopass);
    //here we invoke SimpleMethod. We also pass ArgsToPass as the argument list

Now, let's make this as complicated as we can get it. Let's say that SomeMethod takes those same arguments, and returns a string which you need to retrieve when you invoke SomeMethod. This is really quite easy, because the Invoke() method we used in the past two examples returns a type of object which will hold the value returned by the method it invoked. So, to get the string returned by SomeMethod, we can use:

//This is the arguments list. "Hi" is the string and 113 is the int
object[] argstopass = new object[] { (object)"Hi", (object)113 };

string ReturnValue = (string)MyDLLFormInstance
    .GetType() //Get the type of MyDLLForm
    .GetMethod("SomeMethod") //Gets a System.Reflection.MethodInfo 
                                       //object representing Some
    .Invoke(MyDLLFormInstance, argstopass)
    //here we invoke SomeMethod. We also pass ArgsToPass as the argument list 

In this example, ReturnValue will hold the value returned by SomeMethod when we invoked it.

Events

Now, what about events? Let's say we have an event in MyDLLForm called SomeEvent that we want to access in MyDLLFormInstance. To obtain a System.Reflection.EventInfo object representing that event, simply use:

MyDLLFormInstance
    .GetType() //Get the type of MyDLLForm
    .GetEvent("SomeEvent");
    //Gets a System.Reflection.EventInfo object representing SomeEvent

Now, let's pretend that event uses a simple EventHandler delegate. To add an event handler for that event, use:

MyDLLFormInstance
    .GetType() //Get the type of MyDLLForm
    .GetEvent("SomeEvent") //Gets a System.Reflection.EventInfo 
                                     //object representing SomeEvent
    .AddEventHandler(MyDLLFormInstance, new EventHandler(SomeEventHandler));
    //Adds SomeEventHandler as an event handler for SomeEvent

//--------------------------------------------------------------------------

//This method will be the event handler for SomeEvent in MyDLLFormInstance
public void SomeEventHandler(object sender, EventArgs e)
{

}

And, to remove that same event handler:

MyDLLFormInstance
    .GetType()
    .GetEvent("SomeEvent") //Gets a System.Reflection.EventInfo 
                                     //object representing SomeEvent
    .RemoveEventHandler(MyDLLFormInstance, new EventHandler(SomeEventHandler));
    //Removes SomeEventHandler as an event handler for SomeEvent

//--------------------------------------------------------------------------

//This method will be removed as the event handler 
//for SomeEvent in MyDLLFormInstance
public void SomeEventHandler(object sender, EventArgs e)
{


}

Nested Types

What if there are types contained inside a type in an assembly? That is quite easy. Going along with our example, let's say MyDLLForm contains a nested class called MyDLLFormNestedType. To access that, use:

myDllAssembly
    .GetType("MyDLLForm") //Gets a type representing MyDLLForm
    .GetNestedType("MyDLLFormNestedType")
    //Gets a type representing MyDLLFormNestedType, which is nested inside MyDLLForm

Notice we are not accessing MyDLLFormNestedType through MyDLLFormInstance, but instead, we are accessing the assembly with MyDllAssembly, and then retrieving the System.Type of MyDLLForm, and from that, retrieving the System.Type of the nested type MyDLLFormNestedType. You can then interact with that nested type the way you can interact with the type containing it (creating instances of it, accessing the members contained in it, etc.).

Conclusion

Though this example we used throughout this tutorial was pertaining to loading a class inheriting from a form contained inside a DLL, don't for a second think that I'm implying that you can only access classes contained inside an assembly. You can access any type, class, struct, interface, or anything. Not only can System.Reflection be used to load unreferenced assemblies, as demonstrated in this tutorial, but it can be used to make powerful decompilers and code analyzers. .NET Reflector, a powerful .NET decompiler, would not exist if it wasn't for the System.Reflection namespace and the reflective capabilities of the CLR.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here