Unity’s garbage collector can be disastrous to our games’ framrates when it runs so we’d best not incur its wrath. We’ve seen that foreach loops usually create garbage, so the natural followup question is “what other language features create garbage?” Events and delegates are extremely handy features of C#. They serve as the function pointers and Function objects of the language. They replace signals and slots and allow for flexible callbacks. But a lot of what they do is behind the scenes. Are they creating garbage back there? Today’s article puts them to the test to see if creating and calling delegates and events creates any garbage. Read on to find out!

Delegates come in a few forms. First, you declare a delegate type like this:

// Takes no parameters, returns void
delegate void MyDelegate();

Then you can make one with a lambda:

MyDelegate del = () => {};

Or an anonymous delegate function:

MyDelegate del = delegate() {};

Or use an existing function:

void Foo() {}
MyDelegate del = Foo;

Or use one of the many overloads of Delegate.CreateDelegate:

// This overload takes a delegate type, instance, and function name
// It returns a Delegate, so you have to cast to your particular type of delegate
MyDelegate del = (MyDelegate)Delegate.CreateDelegate(typeof(MyDelegate), this, "Foo");

In any case, you get an instance of your delegate type that you can call like a function:

del();

You can also add more functions, lambdas, anonymous delegate functions, and other delegates on to your delegate instance:

// Add a lambda
del += () => {};

Then when you call the delegate, it calls the original function plus all the ones you’ve added on:

MyDelegate del = () => { Debug.Log("original"); };
del += () => { Debug.Log("added"); };
del(); // prints "original" then "added"

Events are very similar to delegates. You declare one using a delegate type and the event keyword:

event MyDelegate Event;

Then you can add on to it, just like a delegate:

Event += () => {};

One crucial difference is that the event can only be dispatched and set from the class that declares it:

class MyClass
{
	public event MyDelegate Event;
}
 
MyClass mc = new MyClass();
mc.Event(); // compiler error, tried to call from outside of MyClass
mc.Event = () => {}; // compiler error, tried to set from outside of MyClass

You can also customize how the event has delegates added and removed:

class MyClass
{
	private int numListeners;
	private MyDelegate _event;
	public event MyDelegate Event
	{
		add { _event += value; numListeners++; }
		remove { _event -= value; numListeners--; }
	}
}
 
MyClass mc = new MyClass(); // numListeners == 0
mc.Event += () => {}; // numListeners == 1
mc.Event -= () => {}; // numListeners == 0

That’s delegates and events in a nutshell! With that in mind, let’s set up a test script to create, add to, and call delegates and events. We’ll then run the script in Unity and use the profiler to see which actions caused garbage to be created/allocated. Because Unity displays this information on a per-function basis, we have to create a lot of duplicate functions to make sure we don’t lump together the garbage from multiple actions. Here’s the script I came up with:

using System;
 
using UnityEngine;
 
delegate void MyDelegate();
 
class EmptyClass
{
}
 
class ClassWithEvent
{
	public event MyDelegate Event;
 
	public void Dispatch()
	{
		Event();
	}
}
 
class TestScript : MonoBehaviour
{
	void Start()
	{
		var lambda = CreateLambda();
		var anonymousDelegate = CreateAnonymousDelegate();
		var functionAsDelegate = ReturnFunctionAsDelegate();
		var delegateCreateDelegate = DelegateCreateDelegate();
 
		CallLambda(lambda);
		CallAnonymousDelegate(anonymousDelegate);
		CallFunctionAsDelegate(functionAsDelegate);
		CallDelegateCreateDelegate(delegateCreateDelegate);
 
		InstantiateEmptyClass();
		var classWithEvent = InstantiateClassWithEvent();
 
		AddFirstDelegateToEvent(classWithEvent, lambda);
		AddSecondDelegateToEvent(classWithEvent, lambda);
		AddThirdDelegateToEvent(classWithEvent, lambda);
		AddFourthDelegateToEvent(classWithEvent, lambda);
		AddFifthDelegateToEvent(classWithEvent, lambda);
		AddSixthDelegateToEvent(classWithEvent, lambda);
		AddSeventhDelegateToEvent(classWithEvent, lambda);
		AddEighthDelegateToEvent(classWithEvent, lambda);
		AddNinthDelegateToEvent(classWithEvent, lambda);
		AddTenthDelegateToEvent(classWithEvent, lambda);
		DispatchEvent(classWithEvent);
 
		AddFirstDelegateToDelegate(lambda, anonymousDelegate);
		AddSecondDelegateToDelegate(lambda, anonymousDelegate);
		AddThirdDelegateToDelegate(lambda, anonymousDelegate);
		AddFourthDelegateToDelegate(lambda, anonymousDelegate);
		AddFifthDelegateToDelegate(lambda, anonymousDelegate);
		AddSixthDelegateToDelegate(lambda, anonymousDelegate);
		AddSeventhDelegateToDelegate(lambda, anonymousDelegate);
		AddEighthDelegateToDelegate(lambda, anonymousDelegate);
		AddNinthDelegateToDelegate(lambda, anonymousDelegate);
		AddTenthDelegateToDelegate(lambda, anonymousDelegate);
	}
 
	MyDelegate CreateLambda()
	{
		return () => {};
	}
 
	MyDelegate CreateAnonymousDelegate()
	{
		return delegate() {};
	}
 
	MyDelegate ReturnFunctionAsDelegate()
	{
		return Foo;
	}
 
	MyDelegate DelegateCreateDelegate()
	{
		return (MyDelegate)Delegate.CreateDelegate(typeof(MyDelegate), this, "Foo");
	}
 
	void Foo() {}
 
	void CallLambda(MyDelegate lambda)
	{
		lambda();
	}
 
	void CallAnonymousDelegate(MyDelegate anonymousDelegate)
	{
		anonymousDelegate();
	}
 
	void CallFunctionAsDelegate(MyDelegate functionAsDelegate)
	{
		functionAsDelegate();
	}
 
	void CallDelegateCreateDelegate(MyDelegate delegateCreateDelegate)
	{
		delegateCreateDelegate();
	}
 
	void InstantiateEmptyClass()
	{
		new EmptyClass();
	}
 
	ClassWithEvent InstantiateClassWithEvent()
	{
		return new ClassWithEvent();
	}
 
	void AddFirstDelegateToEvent(ClassWithEvent classWithEvent, MyDelegate del)
	{
		classWithEvent.Event += del;
	}
 
	void AddSecondDelegateToEvent(ClassWithEvent classWithEvent, MyDelegate del)
	{
		classWithEvent.Event += del;
	}
 
	void AddThirdDelegateToEvent(ClassWithEvent classWithEvent, MyDelegate del)
	{
		classWithEvent.Event += del;
	}
 
	void AddFourthDelegateToEvent(ClassWithEvent classWithEvent, MyDelegate del)
	{
		classWithEvent.Event += del;
	}
 
	void AddFifthDelegateToEvent(ClassWithEvent classWithEvent, MyDelegate del)
	{
		classWithEvent.Event += del;
	}
 
	void AddSixthDelegateToEvent(ClassWithEvent classWithEvent, MyDelegate del)
	{
		classWithEvent.Event += del;
	}
 
	void AddSeventhDelegateToEvent(ClassWithEvent classWithEvent, MyDelegate del)
	{
		classWithEvent.Event += del;
	}
 
	void AddEighthDelegateToEvent(ClassWithEvent classWithEvent, MyDelegate del)
	{
		classWithEvent.Event += del;
	}
 
	void AddNinthDelegateToEvent(ClassWithEvent classWithEvent, MyDelegate del)
	{
		classWithEvent.Event += del;
	}
 
	void AddTenthDelegateToEvent(ClassWithEvent classWithEvent, MyDelegate del)
	{
		classWithEvent.Event += del;
	}
 
	void DispatchEvent(ClassWithEvent classWithEvent)
	{
		classWithEvent.Dispatch();
	}
 
	void AddFirstDelegateToDelegate(MyDelegate del, MyDelegate toAdd)
	{
		del += toAdd;
	}
 
	void AddSecondDelegateToDelegate(MyDelegate del, MyDelegate toAdd)
	{
		del += toAdd;
	}
 
	void AddThirdDelegateToDelegate(MyDelegate del, MyDelegate toAdd)
	{
		del += toAdd;
	}
 
	void AddFourthDelegateToDelegate(MyDelegate del, MyDelegate toAdd)
	{
		del += toAdd;
	}
 
	void AddFifthDelegateToDelegate(MyDelegate del, MyDelegate toAdd)
	{
		del += toAdd;
	}
 
	void AddSixthDelegateToDelegate(MyDelegate del, MyDelegate toAdd)
	{
		del += toAdd;
	}
 
	void AddSeventhDelegateToDelegate(MyDelegate del, MyDelegate toAdd)
	{
		del += toAdd;
	}
 
	void AddEighthDelegateToDelegate(MyDelegate del, MyDelegate toAdd)
	{
		del += toAdd;
	}
 
	void AddNinthDelegateToDelegate(MyDelegate del, MyDelegate toAdd)
	{
		del += toAdd;
	}
 
	void AddTenthDelegateToDelegate(MyDelegate del, MyDelegate toAdd)
	{
		del += toAdd;
	}
}

Simply paste the above code into a TestScript.cs file in your Unity project’s Assets directory and attach it to the main camera game object in a new, empty project. Then check for garbage collector allocations like this:

  1. Open the Profiler editor pane
  2. Select the Deep Profile button
  3. Click the Clear button
  4. Click the Play button in the main editor window
  5. Click the Play button again shortly after the app starts
  6. In the Profiler editor pane, select the CPU Usage section
  7. Click in the timeline on the first frame or anywhere to the left of it
  8. Find TestScript.Start() in the bottom left section and click its triangle
  9. Click the triangles for all the test functions under TestScript.Start()
  10. Look at the values in the GC Alloc column for each function call

I followed these steps on Mac OS X 10.11 with Unity 5.2.2f1 and found the following results:

Action Garbage Created
Create Lambda 104 bytes
Create Anonymous Delegate 104 bytes
Return Function as Delegate 104 bytes
Delegate.CreateDelegate 0.5 KB
Call Lambda 0 bytes
Call Anonymous Delegate 0 bytes
Call Function as Delegate 0 bytes
Call Delegate.CreateDelegate result 0 bytes
Add First Delegate to Delegate 0 bytes
Add Second Delegate to Delegate 208 bytes
Add Third Delegate to Delegate 208 bytes
Add Fourth Delegate to Delegate 208 bytes
Add Fifth Delegate to Delegate 208 bytes
Add Sixth Delegate to Delegate 208 bytes
Add Seventh Delegate to Delegate 208 bytes
Add Eighth Delegate to Delegate 208 bytes
Add Ninth Delegate to Delegate 208 bytes
Add Tenth Delegate to Delegate 208 bytes
Instantiate Empty Class 16 bytes
Instantiate Class with Event 24 bytes
Dispatch Event 0 bytes
Add First Delegate to Event 0 bytes
Add Second Delegate to Event 208 bytes
Add Third Delegate to Event 312 bytes
Add Fourth Delegate to Event 416 bytes
Add Fifth Delegate to Event 0.5 KB
Add Sixth Delegate to Event 0.6 KB
Add Seventh Delegate to Event 0.7 KB
Add Eighth Delegate to Event 0.8 KB
Add Ninth Delegate to Event 0.9 KB
Add Tenth Delegate to Event 1.0 KB

Creating a delegate seems to allocate 104 bytes of garbage. That’s true for lambdas, anonymous delegate functions, and even returning a regular function as a delegate. The Delegate.CreateDelegate function, however, creates 0.5 KB of garbage. This makes sense since it’s clearly using reflection to get the function by a string name and create the delegate from there.

While it’s a shame that creating the delegate creates garbage too, calling the delegate doesn’t create any. It doesn’t seem to matter how the delegate was created, even if you use Delegate.CreateDelegate.

Adding the first delegate to an existing delegate (with +=) creates no garbage. Once you add another delegate, it creates 208 bytes of garbage. This holds for each delegate you add on, all the way up to where I stopped at 10.

Event creation is a bit tricky to test. Events are, by definition, part of a class. So you need to instantiate a class in order to instantiate the event. The test script instantiates an empty class and a class that just has an event. The empty class created 16 bytes of garbage and the class with just an event created 24. That’s an extra 8 bytes for the event field.

Dispatching the event, like dispatching a delegate, creates no garbage.

Adding delegates to an event is the most painful part. The first one creates no garbage, but each subsequent one creates 104 bytes more. This means that the second is 208, the third is 312, the fourth is 416, then 0.5 KB, 0.6 KB, 0.7 KB, 0.8 KB, 0.9 KB, and 1.0 KB for the tenth.

In summary, garbage will be created when you use either delegates or events. Importantly, the garbage is created as part of the setup process: creating and adding. Dispatching and calling never creates any garbage though, so you can use them every frame without any consequences on the GC front.

Feel free to share your thoughts on delegates, events, and garbage collection in the comments section below!