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");
object[] argstopass = new object[] { (object)"Hi", (object)113 };
Form MyDLLFormInstance = (Form)myDllAssembly.CreateInstance("MyDLLNamespace.MyDLLForm",
false,
BindingFlags.CreateInstance,
null,
argstopass,
null,
null
);
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()
.GetField("StringVariable")
.GetValue(MyDLLFormInstance);
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()
.GetField("StringVariable")
.SetValue(MyDLLFormInstance,
(object)"This string will be 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()
.GetProperty("StringProperty")
.GetValue(MyDLLFormInstance, null);
Now, if we wanted to set the value of the same property (if the property isn't read-only):
MyDLLFormInstance
.GetType()
.GetProperty("StringProperty")
.SetValue(MyDLLFormInstance,
(object)"This will be the new value of StringProperty", null);
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()
.GetMethod("SomeMethod")
.Invoke(MyDLLFormInstance, null);
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:
object[] argstopass = new object[] { (object)"Hi", (object)113 };
MyDLLFormInstance
.GetType()
.GetMethod("SomeMethod")
.Invoke(MyDLLFormInstance, argstopass);
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:
object[] argstopass = new object[] { (object)"Hi", (object)113 };
string ReturnValue = (string)MyDLLFormInstance
.GetType()
.GetMethod("SomeMethod")
.Invoke(MyDLLFormInstance, argstopass)
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()
.GetEvent("SomeEvent");
Now, let's pretend that event uses a simple EventHandler
delegate. To add an event handler for that event, use:
MyDLLFormInstance
.GetType()
.GetEvent("SomeEvent")
.AddEventHandler(MyDLLFormInstance, new EventHandler(SomeEventHandler));
public void SomeEventHandler(object sender, EventArgs e)
{
}
And, to remove that same event handler:
MyDLLFormInstance
.GetType()
.GetEvent("SomeEvent")
.RemoveEventHandler(MyDLLFormInstance, new EventHandler(SomeEventHandler));
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")
.GetNestedType("MyDLLFormNestedType")
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.