Callbacks… are awesome. Really awesome. You just won’t believe how vastly hugely mindboggingly awesome they are…
Ahem.
Anyway, I get asked a lot how to do callbacks, or how to do things which scream callbacks to me, so today I’m going to try go from beginner to midlevel in one post.
So to start, what is a callback?
In the most basic terms, it’s a reference to some form of executable code. A basic example would be calling a function when a coroutine has finished, or a user has pressed a button. If you want your coroutine or button code to be reusable, you can’t call the function by name, you’ll need to tell it which function you want to call from whatever starts the coroutine or button.
And a delegate?
A delegate is a type which holds such a callback, or, heck, even multiple callbacks.
So let’s start with a few simple examples of how to declare your own delegates, as the rest of this article I’m going to use built in ones.
delegate void MyDelegate(); delegate void MyOtherDelegate(bool result); delegate bool MyReturningDelegate();
So, let’s look at them in order.
The first one is probably the most simple delegate possible – it wants an empty method which doesn’t return anything.
The second example isn’t much harder – it wants a method which takes a bool as a parameter and doesn’t return anything.
The last one is similar to the first one with one exception – it doesn’t take parameters, but it needs to return a bool value.
In all three cases you’re simply defining the structure of the delegate. Think enum or even class – you define the type, then later you create instances of it, and that’s exactly what you’re doing here.
Now, a simple example using one of these:
void Start() { MyOtherDelegate callback = MyCallbackMethod; callback(true); } void MyCallbackMethod(bool result) { Debug.Log(result); }
This assigns the MyCallbackMethod signature to the callback (Which will resolve to be the second example delegate above) and then calls it, passing true. This will call MyCallbackMethod, causing “true” to be printed by the Debug.Log.
Since this is a simple snippet, it’ll look a little daft – why not just call MyCallbackMethod(true) ?
The reason is that this may not be in the same class. The actual method that is called in the end may not even be known until runtime. Or, even worse, it may need to be called after a button is pressed in a completely different function which doesn’t know about your own code.
We’ll get to those points later, but first, the next step – anonymous functions (the delegate way).
Anonymous functions are basically a way of writing function code inline in the place you’re using it, instead of the function being located elsewhere.
A quick example, similar to the last one:
void Start() { MyDelegate callback = delegate(bool result) { Debug.Log(result); }; callback(true); }
It’s shorter than the last example, and does the exact same thing, calling Debug.Log(result). The format for anonymous functions like that is delegate(parameters) { code, with a return when necessary}.
There is also another type of anonymous function called a lambda, but I’m going to leave that for the next (more advanced) post.
So now onto my original promise of using built in delegates!
There are two very useful delegates to learn, Action and Func. Both of these take from 0 to 16 parameters, the types of which you specify with generic types. Func also takes another generic type which is the return type.
The most basic form of Action and Func are defined as such:
delegate void Action(); delegate TResult Func();
The Action one should be extremely easy to pick up, it’s identical to the original MyDelegate above. Func should be fairly easy if you know generics, otherwise it’s just this – its return type is whatever you pass in as the generic type.
Here’s Action in action (and Func thrown in for good measure):
void Start() { Action callback = delegate() { Debug.Log("Called"); }; callback(); Func callbackFunc = delegate() { return false; }; Debug.Log(callbackFunc()); }
The benefit of this is you no longer have to go looking in the code to find out what the delegate parameters and return type are – it’s given to you right there and then.
So, onto a practical(ish) example:
using System; using UnityEngine; public class Callbacks : MonoBehaviour { //this is the delegate we'll be using. Action<bool> callback; //these two are to show the result after we've clicked bool hadCallback = false; bool callbackResult; void Start() { //assign the OnConfirm function to the delegate callback += OnConfirm; } void OnGUI() { //this is to draw the result of the confirm after clicking if (hadCallback) { GUILayout.Label("Last result of confirm: " + callbackResult); } GUILayout.BeginHorizontal(GUI.skin.box); if (GUILayout.Button("Ok", GUILayout.Width(100))) { //call the callback function with true to say we clicked ok callback(true); } if (GUILayout.Button("Cancel", GUILayout.Width(100))) { //call the callback function with false to say we clicked cancel callback(false); } GUILayout.EndHorizontal(); } //this will be called when ok or cancel is clicked void OnConfirm(bool result) { callbackResult = result; hadCallback = true; } }
The above example pulls together everything I’ve mentioned so far. It has an Action
Again, this isn’t really necessary in this example as I’m doing it all in a way which would be easy to hardcode, but that’s not the point of examples; If I were to put the code into a more abstracted popup class which creates a popup, waits for input then notifies you when the user clicks a button, it would be perfect.
That wraps up part one of this miniseries, next time: Lambdas, events, the basics of functional programming and more delegatey goodness!
good tutorial, hope to see the part2.
Pingback: Fun with Generics and Lists in C# - technical architecture.
Hey Mike! Jmpp from #Unity3D here! I’m finally reading your articles and just finished this one. I have a small question with regard to the Action & Func built-in delegates, however…
From my understanding, these “built-in” delegates are basically just some sort of (language/runtime)-provided helping shortcut to spare you writing down over and over again your own delegate signatures for as many arguments (and for whatever type of arguments, thanks to genetics!) as these built-in ones can take, is that correct? Or is there something more to them that I’m missing?
Yup, that’s pretty much it. They’re just predefined delegates in the System namespace, with lots of different ones for each number of parameters (I think it goes up to 16)
And, of course, forgot to say: thanks a bunch for your constant and abundant help and explanations all over the place, much appreciated!
Regards,
– jmpp