Reflection: One Of The Most Powerful Tools In A Programmer’s Arsenal

Previously, we’ve looked at simple multi-threading in C#. Today, we’re going to take a quick look at reflection, a feature of C# (and many other languages) that allows you to “look” at your code at run-time and do all sorts of tricky things with it.

OK, “tricky” isn’t that descriptive, so let’s go into a little more detail. The most often used example to demonstrate the usefulness of reflection is a plug-in system. Say you’re creating a program that accepts user-created extensions — there’s no way to know beforehand what methods these extensions will have or what their names will be, unless you can inspect the compiled assembly and extract the information at run-time. This is precisely what reflection does, among other things.

But I’m going to present another instance where reflection is a great tool: adding event-driven functionality where none previously exists.

I’m currently designing a game in Unity — a multi-platform game engine and IDE — where the user interface is “in-game”. What this means is that instead of using Unity’s provided API, which provides a fairly robust set of tools to create interactive GUIs, I’m placing 3D objects in the game scene that must respond to user input. Unity lets you detect mouse events, but there’s no way to code individual event handlers in a modular way.

To get this functionality, I had to extend Unity’s editor and inspect my own code for “exposed” handlers. This might sound complex, but the underlying code that locates these methods and subsequently invokes them is straightforward.

Step 1: Exposing The Methods You Need

Before we even touch C#’s reflection API, we need to create a new attribute. Attributes allow us to assign metadata to classes, methods, properties, etc. that can be read back by reflection. In this case, our attribute is going to be called “ExposeToEditor” and we’re going to use it to tag instance methods (and classes with static methods) that Unity’s editor should recognise as valid event handlers.

So, we create a new script file, making sure using System; is declared at the top. Then, we write the following:

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class ExposeToEditor : Attribute { }

This creates a barebones attribute that we can now assign to any method or class. For example:

[ExposeToEditor]
public void OnThisButtonClick(object sender) { };

Now we can put together the code that will track down methods tagged in this way.

[clear]

Step 2: Cutting Down The Work And Locating Our Exposed Methods

Click on the image on the right to expand it and look to the panel on far right — you’ll see a section called “Interface Script Handler”. This is an editor interface I’ve coded that allows me to select the specific object that contains the script and method I need. This image is more to provide context for what I’m about to explain, rather than being an important part of using reflection, so don’t worry if it doesn’t make much sense.

What is important is the logic behind this editor interface, which needs to locate the method in the script. Here’s what the critical portion:

var type = t.OnClickMonoBehaviour.GetType();

List methodNames = new List();
List methods = new List(type.GetMethods());

foreach (MethodInfo mi in methods)
{
    if (mi.IsDefined(typeof(ExposeToEditor), true))
    {
        methods.Add(mi);
        methodNames.Add(mi.Name);
    }
}

Let’s break it down. The first thing we do is get the type of the selected script, the latter of which is stored in the OnClickMonoBehaviour property of the t object. We then initialise some Generic lists to store the information about the methods we’re going to iterate over. The foreach loop goes through all the public methods contained within the script type, retrieved via Type.GetMethods().

GetMethods provides us with an array of MethodInfo objects. MethodInfo contains a great deal of information about each method, and we can even call the method using MethodInfo.Invoke. However, this is not the most efficient way to dynamically call a method, something we’ll cover later.

Now, we don’t want to store all of the methods, we just want those exposed via the attribute we created earlier. So, we use the MethodInfo.IsDefined method to check if ExposeToEditor is attached to the method. To be thorough, we tell IsDefined to check inherited types as well.

Once the loop is complete, we’ll have a list containing not only the names of the methods as strings, but the methods themselves, in the form of MethodInfo objects. The string list is used to populate the “On Click Method” dropdown in the last image, so we can easily select exactly what we need.

Step 3. Storing And Dynamically Invoking The Method

With the method name stored, all we need to do now is sort out the “on click” logic, which is used in-game. As I mentioned previously, the easiest way to do this is by using dynamic invocation via MethodInfo. For example:

MethodInfo mi = VariableContainingMethodInfo;
mi.Invoke();

And there’s nothing really wrong with this — it gets the job done. But, we can perform an easy optimisation by using the Action delegate and caching it.

First, we define the caching variable somewhere:

private Action CachedAction;

Then, when the interface element is interacted with by the player, we cache the method and use it in subsequent interactions:

CachedAction = (Action)Delegate.CreateDelegate(typeof(Action), OnClickMonoBehaviour, OnClickMethod);

Now, there are several overloads of Delegate.CreateDelegate. In this case, OnClickMonoBehaviour and OnClickMethod are strings. The delegate is created and stored in CachedAction so when we want to invoke it, we just do this:

CachedAction(object);

Where object is just a flexible argument where we can pass information to the event handler if needed.

Start Reflecting

Rather than serve as a tutorial, this article more shows one way in which reflection can be useful, providing functionality that would be impossible without it. I’m sure you can see countless way reflection can be used, beyond what’s shown here.

The best way to get more familiar with it is to check it out on MSDN. For more practical examples, Stack Exchange is a valuable resource, providing specific examples of how to get reflection doing what you want it to.


The Cheapest NBN 50 Plans

Here are the cheapest plans available for Australia’s most popular NBN speed tier.

At Lifehacker, we independently select and write about stuff we love and think you'll like too. We have affiliate and advertising partnerships, which means we may collect a share of sales or other compensation from the links on this page. BTW – prices are accurate and items in stock at the time of posting.

Comments


7 responses to “Reflection: One Of The Most Powerful Tools In A Programmer’s Arsenal”