…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
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);
}
}