Introduction
This article is in response to a request I've received to give an introduction to Reflection in .NET as well as some examples of how it can be used.
So what is Reflection? Personally, I think of Reflection as the use of meta-data which describes our code; or more specifically, describes objects in our code.
Most database systems provide "system tables" that hold information about all the objects (tables, views, Stored Procedures, etc.) in the database. As an exercise, run the following on SQL-Server and see what you get back:
SELECT *
FROM sys.objects
In much the same way, the .NET Framework provides a series of classes which, collectively, can be thought of as performing much the same function as system tables in a relational database. They provide data about the different objects (classes, structs, enums, delegates, etc.) used in our code as well as functionality to perform actions on those objects (e.g.: instantiating objects, invoking methods, etc.). Together, these classes form the System.Reflection
namespace.
Reflection Fundamentals
In the System.Reflection
namespace, there are two classes which are key to all Reflection operations in .NET. They are the Assembly
and Type
classes and you are likely to end up using either or both of these in any Reflection code you write.
The Assembly Class
The Assembly
class represents a single .NET assembly. In most .NET solutions, this can be thought of as the equivalent of a single .dll or a single Visual Studio project. The are several ways of obtaining an Assembly
object:
Assembly theAssembly = Assembly.Load(
"System.Core, Version=4.0.0.0, PublicKeyToken=b77a5c561934e089");
Assembly theAssembly = Assembly.ReflectionOnlyLoad(
"System.Core, Version=4.0.0.0, PublicKeyToken=b77a5c561934e089");
Assembly theAssembly = Assembly.GetExecutingAssembly();
Assembly theAssembly = Assembly.GetCallingAssembly();
Assembly theAssembly = Assembly.GetEntryAssembly();
Dim theAssembly As Assembly = _
Assembly.Load("System.Core, Version=4.0.0.0, PublicKeyToken=b77a5c561934e089")
Dim theAssembly As Assembly = _
Assembly.ReflectionOnlyLoad("System.Core, Version=4.0.0.0, PublicKeyToken=b77a5c561934e089")
Dim theAssembly As Assembly = Assembly.GetExecutingAssembly()
Dim theAssembly As Assembly = Assembly.GetCallingAssembly()
Dim theAssembly As Assembly = Assembly.GetEntryAssembly()
As you can see, when using the Load()
and ReflectionOnlyLoad()
methods, we can optionally include the version, culture, and public-key token to pinpoint the assembly we are interested in more precisely.
The Type Class
The Type
class represents the actual type declarations (classes, structs, interfaces, enums, etc.) in a .NET assembly. There are three common ways to obtain a Type
object for a type:
String theString = "Foo";
Type theType = theString.GetType();
Type theType = typeof(string);
Assembly theAssembly = Assembly.Load("mscorlib");
Type theType = theAssembly.GetType("System.String");
Dim theString As String = "Foo"
Dim theType As Type = theString.GetType()
Dim theType As Type = GetType(String)
Dim theAssembly As Assembly = Assembly.Load("mscorlib")
Dim theType As Type = theAssembly.GetType("System.String")
Once we have our Type
object, we then have access to any of its constituent members (properties, methods, constructors, etc.) via the methods of the Type
class. The list is quite extensive, so I am not going to go through it here, but you can find the complete reference on MSDN.
Instead, we are going to look at a simple example which, I hope, will act as a good primer for getting into Reflection with .NET.
The Reflection Farmyard
The Reflection Farmyard is a one-page web application. The user selects an animal from the drop-down, clicks the 'Make Sound' button, and a textual representation of that animal's sound appears on the page. Here is the ASP.NET markup for the page:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>My Farm Yard</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<h1>
My Farm Yard</h1>
<div>
<asp:DropDownList ID="animalDropDown"
runat="server" AppendDataBoundItems="true" />
<asp:Button ID="makeSoundButton" runat="server"
Text="Make Sound" OnClick="makeSoundButton_Click" />
</div>
<div>
<asp:Label ID="soundLabel" runat="server" ForeColor="Red" />
</div>
</div>
</form>
</body>
</html>
In a separate assembly (Class Library project), we have a series of classes (one for each animal) each exposing a single MakeSound()
method which returns the animal's sound as a string
, for example:
public class Cat
{
public string MakeSound()
{
return "Meow!";
}
}
Public Class Cat
Public Function MakeSound() As String
Return "Meow!"
End Function
End Class
Note that I am deliberately not following good OO practice by not having an abstract Animal
from which my concrete animal classes will inherit. The reason for this will become obvious later on.
Now let's look at the code-behind for the web-page. This is where the interesting Reflection stuff happens. Firstly, we want to populate the drop down list with the name of each animal. To do this, we are going to use the name of each of the animal classes in our animal assembly. The code for this is as follows:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
animalDropDown.DataSource = GetAnimals();
animalDropDown.DataBind();
}
}
private string[] GetAnimals()
{
Assembly assembly = Assembly.ReflectionOnlyLoad(
"Ovineware.CodeSamples.ReflectionFarmyard.Animals.CSharp");
IEnumerable<Type> animalTypes = assembly.GetTypes().Where(
x => x.Namespace ==
"Ovineware.CodeSamples.ReflectionFarmyard.Animals.CSharp").OrderBy(x => x.Name);
string[] animals = new string [animalTypes.Count()];
for (int i = 0; i < animalTypes.Count(); i++)
{
Type animalType = animalTypes.ElementAt(i);
animals[i] = animalType.Name;
}
return animals;
}
Protected Sub Page_Load(ByVal sender As Object, _
ByVal e As EventArgs) Handles Me.Load
If Not IsPostBack Then
animalDropDown.DataSource = GetAnimals()
animalDropDown.DataBind()
End If
End Sub
Private Function GetAnimals() As String()
Dim _assembly As Assembly = Assembly.ReflectionOnlyLoad(_
"Ovineware.CodeSamples.ReflectionFarmyard.Animals.VisualBasic")
Dim animalTypes As IEnumerable(Of Type) =
_assembly.GetTypes().Where(Function(x) x.Namespace = _
"Ovineware.CodeSamples.ReflectionFarmyard.Animals.VisualBasic").OrderBy(_
Function(x) x.Name)
Dim animals(animalTypes.Count() - 1) As String
For i As Integer = 0 To animalTypes.Count() - 1 Step 1
Dim animalType As Type = animalTypes.ElementAt(i)
animals(i) = animalType.Name
Next
Return animals
End Function
Firstly, we load the assembly which contains our animal
classes. At this stage, we can do a Reflection-only load as we are not actually going to execute any code in this assembly yet. We then call the GetTypes()
method of the Assembly
class to return all the types in the assembly. We use a LINQ query to filter the types from the namespace we are interested in and sort them in alphabetical order. For each of our Type
objects, we then call its Name
property to get its name which is put into an array, which in turn is used to populate the drop-down list.
When the user clicks the 'Make Sound' button, we use Reflection once again to initialize the correct animal
object and call its MakeSound()
method:
protected void makeSoundButton_Click(object sender, EventArgs e)
{
Assembly assembly =
Assembly.Load("Ovineware.CodeSamples.ReflectionFarmyard.Animals.CSharp");
string typeName =
String.Format("Ovineware.CodeSamples.ReflectionFarmyard.Animals.CSharp.{0}",
animalDropDown.SelectedValue);
Type animalType = assembly.GetType(typeName);
ConstructorInfo constructor = animalType.GetConstructor(Type.EmptyTypes);
object animal = constructor.Invoke(null);
MethodInfo makeSoundMethod = animalType.GetMethod("MakeSound",
BindingFlags.Public | BindingFlags.Instance);
string sound = (string)makeSoundMethod.Invoke(animal, null);
soundLabel.Text = sound;
}
Protected Sub makeSoundButton_Click(ByVal sender As Object, _
ByVal e As EventArgs) Handles makeSoundButton.Click
Dim _assembly As Assembly = _
Assembly.Load("Ovineware.CodeSamples.ReflectionFarmyard.Animals.VisualBasic")
Dim typeName As String = _
String.Format("Ovineware.CodeSamples.ReflectionFarmyard.Animals.VisualBasic.{0}", _
animalDropDown.SelectedValue)
Dim animalType As Type = _assembly.GetType(typeName)
Dim constructor As ConstructorInfo = animalType.GetConstructor(Type.EmptyTypes)
Dim animal As Object = constructor.Invoke(Nothing)
Dim makeSoundMethod = animalType.GetMethod("MakeSound", _
BindingFlags.Public Or BindingFlags.Instance)
Dim sound As String = DirectCast(makeSoundMethod.Invoke(animal, Nothing), String)
soundLabel.Text = sound
End Sub
Here, we load the assembly again but this time for execution. We then call the GetType()
method on our Assembly
object to get the relevant Type
object as specified by the user's selection in the drop-down list. Once we have our Type
object, we get its default constructor by calling its GetConstructor()
method. This method takes an array of Type
objects which match the parameter types of the constructor overload we are interested in. In this example, we want the default constructor so we pass in an empty array. The method returns a ConstructorInfo
object.
Next, we instantiate an instance of our animal
class by calling the Invoke()
method on the ConstructorInfo
object. This method takes an array of objects which are passed in as parameters to the constructor. As we are invoking the default constructor, we can just pass in a null
reference.
We then need to get the MakeSound()
method of our animal
class. We do this by calling the GetMethod()
method on our Type
object. This method has several overloads, but in this instance, the one we are using simply takes the name of the method and a combination of BindingFlags
to specify the method, i.e., we are asking for the method with the name "MakeSound
" which is a public
instance method. The GetMethod()
method returns a MethodInfo
object.
Once we have our MethodInfo
object, we invoke it by calling its Invoke()
method. This method takes as parameters the object we want to invoke the method on, in this case, the object stored in the animal
variable, and an array of objects which are passed as parameters to the method. As our MakeSound()
method is parameterless, we again pass in a null
reference.
We cast the result as a String
and use it to set the Text
property of our label.
So Why Didn't I Use An Abstract Class?
As I said earlier, I deliberately didn't use an abstract
class in my design. If I had, once we had invoked the constructor to get an instance of the object, we could simply cast it to the base class and call the MakeSound()
method at compile time:
Animal animal = (Animal)constructor.Invoke(null);
string sound = animal.MakeSound();
Dim animal As Animal = DirectCast(constructor.Invoke(Nothing), Animal)
Dim sound As String = animal.MakeSound()
However, as this is an article about Reflection and I wanted to demonstrate calling a method dynamically at run-time, using an abstract
class would have somewhat defeated the object of exercise!
Gotcha: Getting the Correct Type
A common gotcha when using Reflection is getting an unexpected Type
object. This is often as a result of the differences between the GetType()
method of System.Object
and the typeof/GetType
functions.
The GetType()
method will always return the concrete type of an object, regardless of the type it is declared as; whereas the typeof/GetType
functions will return the type that has been declared. It is especially easy to get caught out when using Reflection with Generics.
Consider a scenario where the Cat
class is a sub-class of Animal
and we have a generic method as follows:
public static void SomeGenericMethod<T>(T obj)
{
Type v = obj.GetType();
Type w = typeof(T);
}
Public Shared Sub SomeGenericMethod(Of T)(ByVal obj As T)
Dim v As Type = obj.GetType()
Dim w As Type = GetType(T)
End Sub
Now take a look at the following examples:
Cat cat = new Cat();
Animal animal = new Cat();
Type t = cat.GetType();
Type u = animal.GetType();
SomeGenericMethod(cat);
SomeGenericMethod(animal);
SomeGenericMethod<Animal>(cat);
Dim _cat As Cat = New Cat()
Dim _animal As Animal = New Cat()
Dim t As Type = _cat.GetType()
Dim u As Type = _animal.GetType()
SomeGenericMethod(_cat)
SomeGenericMethod(_animal)
SomeGenericMethod(Of Animal)(_cat)
Summary
That concludes this whistle-stop introduction to Reflection. For more information, take a look at the MSDN documentation for the System.Reflection
namespace.