informa
/
Programming
Featured Blog

Unity Trick #2 – Building delegates using reflection

This week's tip is pretty simple, and fairly esoteric. It's not something you'll need to use every day, but it is pretty cool, and who knows, maybe one day you'll suddenly find you need it. Today we're going to make a C# delegate out of thin air. Onward!

This article was originally posted on my blog, here. Check it out!

...Previously on Unity Tricks: Making an inspector from any ScriptableObject...

This week's tip is pretty simple, and fairly esoteric. It's not something you'll need to use every day, but it is pretty cool, and who knows, maybe one day you'll suddenly find you need it. Today we're going to make a C# delegate out of thin air. Onward!
 
If you're already familiar with delegates, skip ahead! There's some handy info for you in a few paragraphs. If you're not familiar, delegates are a really useful system that lets you pass around methods like variables and call them dynamically. A delegate is defined like this:

public delegate void MyDelegateDefinition(int myParameter);

This creates a delegate type called "MyDelegateDefinition" with a void return type and an integer type parameter. What this means is, if I want to pass around a method as a MyDelegateDefinition type, it must return void and have an integer as a parameter. Easy enough!

Next we must make a variable to hold our delegates. This variable will be a MyDelegateDefinition type:

// Our delegate definition:
public delegate void MyDelegateDefinition(int myParameter);

// Our delegate object. This will contain our delegate methods
public MyDelegateDefinition myDelegate;

This our delegate object. We'll assign methods that match our delegate definition to this variable. Lets go ahead and make a method and do that:

// Our delegate definition:
public delegate void MyDelegateDefinition(int myParameter);

// Our delegate object. This will contain our delegate methods
public MyDelegateDefinition myDelegate;

/*
** CallMe() takes an integer and returns void, making it compatible
** with our delegate definition. Let it be known that the author is 
** sorely tempted to make a certain pop-music joke here, but is resisting...
*/
public void CallMe(int myParameter){
   Debug.Log("My number is " + myParameter);
}

We'll also need to add logic to assign our method to our delegate variable. I'm using Unity's Start callback here.

// Our delegate definition:
public delegate void MyDelegateDefinition(int myParameter);

// Our delegate object. This will contain our delegate methods
public MyDelegateDefinition myDelegate;

/*
** CallMe() takes an integer and returns void, making it compatible
** with our delegate definition. Let it be known that the author is 
** sorely tempted to make a certain pop-music joke here, but is resisting...
*/
public void CallMe(int myParameter){//Maybe. Fine... I caved
   Debug.Log("My number is " + myParameter);
}

// Unity's Start callback. This is a Unity tutorial, after all!
public void Start(){
    // Going to assign the CallMe method to our delegate object
    myDelegate = CallMe;
}

We can then pass around our myDelegate object just like any regular object. We can call the method from anywhere as long as we have a reference to the object, ex:

public void SomeMethod(MyDelegateDefinition myDelegate){

    // Calling our delegate:
    myDelegate(42);

}

We can also pack multiple methods into our delegate definition. If you use the "=" operator to assign your method to the delegate object as we did above, whatever method that was contained in the delegate object previously will be replaced. However, by assigning using the "+=" operator instead, the method will be added to our delegate object alongside the other methods previously assigned to the object. When we then call the delegate, all of the methods contained in our delegate object will be called at once. Really, really useful, especially for more advanced coding patterns like publisher-subscriber.

 

Making delegates using reflection

This pattern works wonderfully for most use cases, but there's a much more fun way to make delegates. Using reflection, we can grab a method from an arbitrary class by name and dynamically build a delegate. Cool, no? Like a normal delegate, we first need to write our delegate definition and a variable to hold our delegates:

public delegate void MyDelegateDefinition();
public MyDelegateDefinition myDelegates;

Then, we need to grab the method we want to use as a delegate via reflection. Below I'm searching for a method called "SomeMethodName" in the object called "someObject". The binding flags refine how the method is matched. Here I'm matching public and private instance variables in someObject's class and it's superclasses.

MethodInfo method 
    = someObject.GetType().GetMethod("SomeMethodName", BindingFlags.Instance 
    | BindingFlags.Public 
    | BindingFlags.NonPublic 
    | BindingFlags.FlattenHierarchy);

Finally with that method in hand we can create our delegate using a call to System.Delegate.CreateDelegate():

myDelegates += (MyDelegateDefinition)System.Delegate.CreateDelegate(
    typeof(MyDelegateDefinition),arbitraryObject, method);

With our delegate created, we can call it like any other delegate:

myDelegates();

And that's it! The neat thing about this is now you don't have to hard code what methods you use as a delegate. You can decide at runtime and dynamically add any method as a delegate, as long as it matches the delegate definition.

In practice, there are very few instances where this method is necessary, but sometimes there's just no workaround to using reflection. Interestingly enough, I've read that Unity uses reflection to set up the Update, OnGUI, and other callbacks on Monobehaviours. Although I don't work for Unity and have never decompiled the engine to it's source, I suspect they are using similar methods to build their delegates for those callbacks.

Anyways, hope you find this trick useful. Below is some example source code for you all to chew on. As always, questions and comments are welcome!

Cheers,

-Mark

@mark_at_ABXY

StartTest.cs

using UnityEngine;
using System.Collections;
using System.Reflection;

// This class sets up a delegate using reflection and calls it once per frame
public class StartTest : MonoBehaviour {

    // The delegate definition
    public delegate void MyDelegateDefinition(int someInteger);

    // The delegate instance
    public MyDelegateDefinition myDelegates;

     /* This class makes an instance of Test, the class containing the method
     * we want to use as a delegate, and then sets up the delegate 
     * using reflection
     */
     void Start () {
        Test test = new Test();
        SetUpDelegate(test);
     }
	
     /* Update is called once per frame. If the delegate has been set up, 
     *  I'll call it here
     */
     void Update () {
        if (myDelegates != null) {
            myDelegates(42);
        }
     }

     /* This method sets up a delegate for the given object, searching for a 
     *  method called "PrintThisInt
     */
     public void SetUpDelegate(object arbitraryObject) {

         // Searching the given object for a method called "PrintThisInt"
         MethodInfo method 
             = arbitraryObject.GetType().GetMethod("PrintThisInt", 
             BindingFlags.Instance 
             | BindingFlags.Public 
             | BindingFlags.NonPublic 
             | BindingFlags.FlattenHierarchy);

         // Making the delegate
         myDelegates += (MyDelegateDefinition)System.Delegate
             .CreateDelegate(typeof(MyDelegateDefinition),
             arbitraryObject, method);
     }
}

Test.cs

using UnityEngine;
using System.Collections;

// Simple class containing a method matching MyDelegateDefinition
public class Test {

    /*
    *  Method to use as a delegate. Prints out whatever value someInteger 
    */ is set to
    public void PrintThisInt(int someInteger) {
        Debug.Log("LOOK AT THIS INTEGER: " + someInteger);
    }
}

 

Latest Jobs

Sucker Punch Productions

Bellevue, Washington
08.27.21
Combat Designer

Xbox Graphics

Redmond, Washington
08.27.21
Senior Software Engineer: GPU Compilers

Insomniac Games

Burbank, California
08.27.21
Systems Designer

Deep Silver Volition

Champaign, Illinois
08.27.21
Senior Environment Artist
More Jobs   

CONNECT WITH US

Register for a
Subscribe to
Follow us

Game Developer Account

Game Developer Newsletter

@gamedevdotcom

Register for a

Game Developer Account

Gain full access to resources (events, white paper, webinars, reports, etc)
Single sign-on to all Informa products

Register
Subscribe to

Game Developer Newsletter

Get daily Game Developer top stories every morning straight into your inbox

Subscribe
Follow us

@gamedevdotcom

Follow us @gamedevdotcom to stay up-to-date with the latest news & insider information about events & more