Delegates
C#‘s delegates are already in use inside DI. Say I define a func type called A and then call A() below. I didn’t explicitly write the delegate keyword, but that’s already a delegate, because C# defines three wrapped delegate types: Func<T>, Action<T> and Predicate<T>. Those three are the one with a return value, the one without, and the one that returns bool. The last one doesn’t get used much.
The most common use of the delegate keyword is this kind of situation. Say I have a complicated method that takes five values. Writing that into a func is a pain, so it’s better to define a delegate up front, call it A, and then I can write A a instead of a func declaration that runs on forever.
// Without a custom one
Func<string, int, DateTime, bool, MyConfig, MyResult> handler = ...;
// After defining a custom delegate
delegate MyResult DataHandler(string name, int count, DateTime time, bool flag, MyConfig config);
DataHandler handler = ...; // much cleaner
Another benefit is that it carries meaning, so you can tell what the type is for just by reading it. Otherwise Func with a long string of parameters after it needs a comment before anyone can make sense of it. Then there’s event, which needs a delegate type before you can redeclare it as an event.
/// First declare a delegate
delegate void OnPlayerDead(Player player, string reason);
/// Then declare that this is an event
event OnPlayerDead PlayerDied;
The situation for events is this. You can run several methods dynamically, all at once, whenever you need to. Say a queue with ten method calls in it, then after some event happens you += or -=, and in the end you invoke that delegate variable. Even though I wrote ten to begin with, it might end up running five times or fifteen. That’s handy anywhere execution needs to be flexible, and it’s the classic publish-subscribe mechanism. The publisher doesn’t care what the subscribers do, it only cares about controlling when the published result runs.
Events are often a replacement for multicast delegates. A multicast delegate means a delegate can easily be reassigned with = anywhere, =null for instance, which on the face of it is very risky. Like inheritance, you can’t predict when it will get reassigned, and once it happens even one time, everything downstream is done for. That’s why events exist, to limit what you can do to a delegate. An event can only be added to or subtracted from, never reassigned. A controlled multicast delegate.
// Plain multicast delegate. Outside code can do whatever it likes
// Most of the time just declaring one of the official delegate types is enough
public Action OnDead;
player.OnDead = null; // ✅ wipes it, everything other modules attached is gone
player.OnDead(); // ✅ outside code fires it directly, which it shouldn't
player.OnDead += MyMethod; // ✅
// event. Outside code can only +/-
public event Action OnDead;
player.OnDead = null; // ❌ error
player.OnDead(); // ❌ error
player.OnDead += MyMethod; // ✅
Simply put, a delegate is a delegation of a method. I use a variable in place of the original method and treat it as the original method’s representative. From the original method’s point of view, I delegated myself to the object I was delegated to. It shows up most when it’s used as an element, since then I have to find a variable to stand for the original method or I can’t work with it at all. Beyond that it shows up when method execution needs to be dynamic and flexible, especially when you want to run an indefinite number of methods dynamically. One situation I can think of is a run method that takes an enum from outside to decide which one to run in which case. There I could use a delegate directly, += when the event happens, -= when it isn’t needed, and then invoke that delegated variable at the end. One line, and no methods needed at all.
The setup I have in mind isn’t written all in one place but scattered around. Say I expose one method specifically as the delegate variable for outside use, define it up front (when you += a delegate, doesn’t the type have to match what I defined?), and then invoke it all in one place. As the upper layer I don’t care how many you handed in, you add as many as your situation calls for (the dynamic event). This has a bit of an IoC flavor to it, the concrete execution is inverted outward, and the method’s insides focus on their own logic without caring about the count. That’s the publish-subscribe from above, and at bottom it’s still decoupling.
Translation note. I wrote this in Chinese. This English version is an LLM translation, so the wording is not mine even though the thinking is. Original: 委托.