How to make Delegates/Events survive assembly reload?

Hi, I’m trying to make usage of C# Events in Unity, however, they don’t survive Assembly reloads so they stop working, forcing me to restart the game (in the editor). So I would like to know: is it possible to make Delegates or Events survive assembly reloads?

Thanks in advance!

EDIT: For a complete serializable/inspectable delegate solution, see my uFAction.


I have been working on this lately - To make a delegate survive, you need to either serialize the delegate itself or rebuild it. Serializing a delegate = serializing all its targets and methods, rebuilding a delegate = serializing its targets and methods separately, and creating the delegate via Delegate.CreateDelegate.

If you want your delegates to target regular System.Objects (anything but a UnityEngine.Object) then you could manually serialize/deserialize the delegate itself (no need to rebuild) - But if you’re targeting UnityEngine.Objects (for ex you need to notify Components, MonoBehaviours, etc) - then you can rebuild the delegate because the target (which is a UnityEngine.Object) can be serialized by Unity’s serialization system.

The reason for this: when you target a System.Object, you can’t recreate the delegate because to recreate it, you need to know its Target and the Method and since you’re targeting a System.Object, the target is not serialized by Unity.

But when you target a UnityEngine.Object, it’s serialized so you could recreate the delegate.

Here’s an example of a SerializedAction that targets System.Objects - I’m serializing it normally via BinaryFormatter - this is tested and the delegate persists between reloads.

using UnityEngine;
using System.Collections.Generic;
using System;
using System.Linq;
using System.IO;
using System.Reflection;

[Serializable]
public class SerializedAction
{
	private Action action;
	[SerializeField] private string serializedData;

	public object[] Targets { get { return Get().GetInvocationList().Select(d => d.Target).ToArray(); } }

	public void Invoke()
	{
	    var a = Get();
	    if (a != null) 
	        a.Invoke();
	}

	public void Add(Action handler)
	{
		ChangeAction(() => action = Get() + handler);
	}

	public void Remove(Action handler)
	{
		ChangeAction(() => action = Get() - handler);
	}

	public Action Get()
	{
		if (action == null)
			action = Utils.DeserializeFromString<Action>(serializedData);
		return action;
	}

	private void ChangeAction(Action change)
	{
		change();
		serializedData = Utils.SerializeToString(action);
	}
}

In your utils:

	/// <summary>
	/// Serializes 'value' to a string, using BinaryFormatter
	/// </summary>
	public static string SerializeToString<T>(T value)
	{
		using (var stream = new MemoryStream()) {
			(new BinaryFormatter()).Serialize(stream, value);
			stream.Flush();
			return Convert.ToBase64String(stream.ToArray());
		}
	}

	/// <summary>
	/// Deserializes an object of type T from the string 'data'
	/// </summary>
	public static T DeserializeFromString<T>(string data)
	{
		byte[] bytes = Convert.FromBase64String(data);
		using (var stream = new MemoryStream(bytes)) {
			return (T)(new BinaryFormatter()).Deserialize(stream);
		}
	}

Now, with this you can’t target UnityEngine.Objects, because they’re not marked with System.Serializable to be serialized via BinaryFormatter.

This means a new system have to exist for targeting UnityEngine.Objects:

using UnityEngine;
using System.Collections.Generic;
using System;
using System.Linq;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Reflection;
using bf = System.Reflection.BindingFlags;
using Object = UnityEngine.Object;

[Serializable]
public class SerializedUnityAction
{
	[Serializable]
	private class Entry
	{
		public Object target;
		public List<string> methodsNames = new List<string>();
		public Entry() { }
		public Entry(Object target, string methodName)
		{
			this.target = target;
			methodsNames.Add(methodName);
		}
	}

	private Action action;
	[SerializeField] private List<Entry> entries = new List<Entry>();

	public Object[] Targets { get { return entries.Select(e => e.target).ToArray(); } }

	public SerializedUnityAction() { }
	public SerializedUnityAction(Action handler) { Add(handler); }

	public void Invoke()
	{
		Get().Invoke();
	}
	
	private bool IsValidHandler(Action handler)
	{
		return handler != null && handler.Target is MonoBehaviour;
	}
	
	public void Add(Action handler)
	{
		if (!IsValidHandler(handler)) return;

		var target = handler.Target as Object;

		if (!Targets.Contains(target)) { // if we're targeting a new Object
			entries.Add(new Entry(target, handler.Method.Name));
		}
		else entries.First(e => e.target == target).methodsNames.Add(handler.Method.Name);

		action = Get() + handler;
	}
	
	public void Remove(Action handler)
	{
		if (!IsValidHandler(handler)) return;

		var target = handler.Target as Object;
		int index = Targets.ToList().IndexOf(target);
		if (index == -1) return; // handler target doesn't exist
		entries[index].methodsNames.Remove(handler.Method.Name);
		if (entries[index].methodsNames.IsEmpty()) // no more handler for that target
			entries.RemoveAt(index); // so just remove the entry

		action = Get() - handler;
	}
	
	private Action Get()
	{
		if (action == null) {
			// re-build the whole invocation list! :)
			foreach (var entry in entries) {
				var target = entry.target;
				if (target == null) continue;
				foreach (var method in entry.methodsNames) {
					action += Delegate.CreateDelegate(typeof(Action), target, method) as Action;
				}
			}
		}
		return action;
	}
}

The idea as I mentioned before, is to rebuild the delegate when it’s null (when an assembly reloads happen). Now to rebuild it, you need to have the target and the method names available to you (so target and methodsNames MUST serialize and survive) - you might ask, why save the methodsNames instead of the methodsInfos directly? well, cause strings serialize, while MethodInfos don’t - and for this simple delegate, strings are enough - complexity is added as and when needed and not when you think you might need it :slight_smile:

Unity has serialisable delegates from the box.

Ok, Unity has (semi-) serialisable delegates from the box. It’s not directly a serialisation of delegates, rather of observers, a notifier+observer pattern. Unity’s UnityEvent, that’s used for Inspector-driven UI event listening (when you hang a game object and its specific method to call on a button’s Click event e.g.) is serialised multicast delegates. It’s slow (there was an article somewhere comparing native C# delegates and Unity’s delegates and it was magnitude 3 difference, but I can’t seem to find it). But it works. You can also declare your own events/delegates as either public or [SerilizeField] private fields of type UnityEvent (or one of it’s generic variations, up to 4 arguments, but in this case you’d need to extend the generic class, as Unity can’t serialise generics) and they become inspector-injectable. These events have API for adding and removing listeners (and obviously invoking) from script. But script-added/removed listeners are non persistent. So happy mouse-programming:)

This feature enable developers to expose events for hooking up listeners to non-programmer folks. I’d say quiet a niche tool for event-driven visual level editing. I can imagine creating a set of actor game objects and a “protocol” of event cross-linking and vuala, a game designer can create little sandbox simulations with no coding help. Correct me if I’m wrong, this is how the GameMaker works in their visual scripting tool.

That having been said, I think you should keep programmer-for-programmer delegates in pure C# and unexposed for inspector.

The biggest problem of these serialised events is that they remain completely unadvertised outside of new UI system by Unity, although they’re completely UI agnostic.

Here is a little video, where a guy uses these events (without the UI system):

public OrcWasCreatedEvent OrcWasCreated; // public class OrcWasCreatedEvent : UnityEvent public UnityEvent AllOrcsWereDestroyed;
// later

OrcWasCreated.Invoke(myOrc);
AllOrcsWereDestroyed.Invoke();

The answer is: it’s not possible :wink: An “assembly reaload” sounds like some kind of quick update check but in fact the whole scripting environment reloads. This will destroy everything in the managed land. Unity can recover from this by using it’s serialization system. Unity serializes the whole scene before the reload, then recreates everything and deserializing the whold scene. Of course only things which can be serialized will “survive” this process.

Unfortunately there is no ingame event / notification when the serializing / deserializing takes place. So you can’t react to those actiobs.

What you could do is to backup all delegate assignments with a component + function-name pair stored in serialized variables so in start you might be able to reconstruct the delegate. Multicast delegates are very hard and anonymous delegates impossible to serialize.

If you want this “feature” to work, only use serializable types / variables. However i have to say i don’t need that feature :wink: It’s just there to simplify debugging a bit. However there’s always a chance that certain states can’t be reverted to it’s last state (coroutines for example). So what happens after a “reload” is in most cases kind of random.

This would never happen in the build, so i wouldn’t rely on that feature. It’s better to provide some debug initialization which allows you to start your game with a well defined state.