Why pass a delegate to a function instead of calling a function inside a function?

I'm trying to understand the reason of delegates, but I find really hard to see how could be useful passing a method's ref to a function instead of calling the function inside.

For example

class Program
{
    public static void Print(int num) 
        => Console.WriteLine(num);

    public static void Sum(int a, int b)
        => Print(a + b);
    
    public static void Sum(int a, int b, DelExample del) 
        => del(a + b);
    
    public delegate void DelExample(int num);

    static void Main(string[] args)
    {
        Sum(1, 2);

        DelExample del = Print;
        Sum(1,2, del);
    }
}

notice that Sum and its overload works the same way. Is there any reason why using delegate for this scenery would be better than calling a function inside? Is there any scenery where I need a delegate because using a function won't work the same way? I know that delegates are necessary for events, but when it is useful for this kind of scenarios?

I also appreciate a lot if you could give me some examples about it (or other useful uses like events, callbacks, threads), or some resources to understand it like if I am five, since I feel that I learn pretty slow. Thanks in advance.


Solution 1:

Ok, you know "delegates are necessary for events" but I wonder do you really appreciate the extent of that?

A delegate is simply a way of enabling passing a method around in the same way we pass data around. It is necessary because sometimes the creator of some code (eg Microsoft) cannot possibly imagine all the different things you will do with the code. There has to be some bit missing, some piece of code they cannot write because they just don't know what you will use C# for

Microsoft created the Button class. They give it all sorts of nice properties that control its appearance, and they let you specify where it is on screen, how big, what color and what font. They cannot reasonably know though, what you want it to do when you click on it. What do you want it to do? Open a file? Save a Person to a DB? Shut down the computer? The possibilities are endless..

So there are gaps, in the code of the Button, that cannot be filled by the person who writes Button class - what happens when you click it? What happens when you mouse over it? What happens when..

You can think of lots of scenarios for different gaps, but there is only one way to plug that gap; let the end user provide a bit of code that does something. The only thing that Microsoft, the Button creator, requires is that the bit of code supplied by you has some particular signature. Suppose they want a reliable way of passing to you the number of times a button was clicked (single or double), which mouse button was used (left or right), where was the mouse pointer (on coordinates) etc

What Microsoft need is to be able to say "you can pass any method you like as the method that will be called when the button is clicked, so long as it takes 3 parameters; an int for the number of times, an enum for was it left or right mouse button, and a Point for where the mouse cursor was. We'll call your supplied method with those 3 things every time the user clicks"

And that's how you come to need delegates; so that other people can supply methods that you will call, to bridge the gap of "actions you cannot possibly know they will want to carry out" when you create your class.


Another example: doing things based on criteria you don't know, like finding elements of a list

You're writing a custom collection of Person objects, you want to search for a Person according to some criteria.

foreach(var p in people)
  if(p.Firstname == "John")
    return p;

That does the job, but it's kinda inflexible. We could build that into our PersonCollection, but then it would only know how to search for a person with a firstname of John. We could make the name a string parameter but it's still inflexible because it only searches by firstname. It would end up in a method like:

public void FindByFirstname(string n){
  foreach(var p in people)
    if(p.Firstname == n)
      return p;
}

Microsoft wouldn't do the John one because it's so limited; nearly no one would use it. They might do the Firstname one, because it at least allows the user to specify the name that is ubknown to Microsoft.

What if though, Microsoft could provide us a way to specify the p.Firstname == "John" bit so we could vary the Firstname part too; we could provide some nugget of code that embodies what we want, they write the loop etc and for every list item they this nugget that we supply. If the nugget returns true, then they return the item from the list. They don't need to know or care what our method does; it could return true if it's Tuesday, it just needs to follow the rules:

  • it's a method that takes one parameter, which is an object from the list so it needs a single argument of type Person
  • it returns a boolean and we return the list item if the bool is true

The delegate can enforce this; a delegate enables us to pass around a method with a particular signature just like we pass around data of a particular type. A delegate is simply a declaration of a method signature, just like a Person is a declaration of a data signature (two names, an age, a social security number..).

delegate bool PersonSelector(Person p);

Any method that takes a Person and returns a bool may act as this delegate

Thus the Find method is born. It's a method that takes another method as an argument. The other method must accept a person and return a bool

Person Find(PersonSelector perSel)

The collection can do the loopy bit that calls the delegated method; Microsoft can write this bit:

foreach(var p in people)
  if(perSel(p))   //execute the delegate and use its return value 
    return p;

And we write this bit, because we know we want to search on first name of John:

bool IsCalledJohn(Person p){
  return p.Firstname == "John";
}

peopleCollection.Find(IsCalledJohn);

Or if it's Tuesday (we don't have to use the person argument, we just have to accept it and return a bool):

bool IsTuesday(Person p){
  return DateTime.Now.DayOfWeek == DayOfWeek.Tuesday;
}


peopleCollection.Find(IsTuesday);

Gap bridged! Microsoft can get on with writing their super fast search algorithm, and we just plug the bit in that they can't know: what the search criteria is. Yes; it is hard to see "what's the point of a delegate" if you're writing the list and making use of the list - there isn't a gap there because you know what you're using it for. But as soon as you put yourself in the shoes of someone who just "writes the tools yet doesn't use them" you start seeing the gaps where "the user will have to supply this bit"