understanding Delegates and Events

elsheepo

New member
Joined
Aug 13, 2017
Messages
3
Programming Experience
Beginner
So I've been studying C# on my own for about 3 months now. I'm going through the Murach's C# 2015 textbook. I've reached Chapter 13, which exposes me to indexers, delegates, events, and operators. I've followed along easily enough up to this point, but these delegates and events, more so the delegates confuse me terribly. I've literally been stuck on this topic for the past two weeks, and am just as confused about these things as the day I started. I've participated in several hours worth of IRC dialog on freenode's ##csharp channel, I have read about 10 different articles, watched several hours worth of youtube videos, and I am completely baffled. If anyone can please, please help me understand what in the heck, this delegate is doing, and how the event is related to it, any information or explanation at all, would be incredibly appreciated. Here's the code..

This is the CustomerList class, which defines the fields, properties, methods, etc of a CustomerList object, to be instantiated at some point in the future. I understand that the delegate defines a "signature" which is its return type, and parameters, and that the only methods that can be used by a delegate must have an identical "signature". I understand that when the Add() and Remove() methods are called, they raise the Changed event, which is somehow associated with the delegate....
C#:
using System.Collections.Generic;

namespace CustomerMaintenance
{
    public class CustomerList
	{
		private List<Customer> customers;

        public delegate void ChangeHandler(CustomerList customers);
        public event ChangeHandler Changed;

		public CustomerList()
		{
            customers = new List<Customer>();
		}

		public int Count => customers.Count;

		public Customer this[int i]
		{
			get
			{
				return customers[i];
			}
			set
			{
				customers[i] = value;
				Changed(this);
			}
		}

		public void Fill() => customers = CustomerDB.GetCustomers();

		public void Save() => CustomerDB.SaveCustomers(customers);

		public void Add(Customer customer)
		{
			customers.Add(customer);
			Changed(this);
		}

		public void Remove(Customer customer)
		{
			customers.Remove(customer);
			Changed(this);
		}
	}
}

Here is the code for the frmCustomers class, which when instantiated, is an object. This particular object, frmCustmers instantiates a CustomerList object. In it's _load Event Handler, it subscribes to the Changed event, defined in CustomerList... please omg help me.. O_O
C#:
using System;
using System.Windows.Forms;

namespace CustomerMaintenance
{
    public partial class frmCustomers : Form
    {
        public frmCustomers()
        {
            InitializeComponent();
        }

        private CustomerList customers = new CustomerList();

        private void frmCustomers_Load(object sender, EventArgs e)
        {
            customers.Changed += customers =>
            {
                customers.Save();
                FillCustomerListBox();
                MessageBox.Show("The 'Changed' event has been raised.", "Event Raised");
            };
            customers.Fill();
            FillCustomerListBox();
        }

        private void FillCustomerListBox()
        {
            lstCustomers.Items.Clear();
            for (int i = 0; i < customers.Count; i++)
            {
                Customer c = customers[i];
                lstCustomers.Items.Add(c.GetDisplayText());
            }
        }

        private void btnAdd_Click(object sender, EventArgs e)
        {
            frmAddCustomer addCustomerForm = new frmAddCustomer();
            Customer customer = addCustomerForm.GetNewCustomer();
            if (customer != null)
            {
                customers.Add(customer);
            }
        }

        private void btnDelete_Click(object sender, EventArgs e)
        {
            int i = lstCustomers.SelectedIndex;
            if (i != -1)
            {
                Customer customer = (Customer)customers[i];
                string message = "Are you sure you want to delete "
                    + customer.FirstName + " " + customer.LastName + "?";
                DialogResult button = MessageBox.Show(message, "Confirm Delete",
                    MessageBoxButtons.YesNo);
                if (button == DialogResult.Yes)
                {
                    customers.Remove(customer);
                }
            }
        }

        private void btnExit_Click(object sender, EventArgs e)
        {
            this.Close();
        }
    }
}
 
I haven't looked at your code snippets but I'll try a simple explanation of delegates and events and we'll see if we need to go further from there.

Firstly, it's important to understand that the word "delegate" is used in two different ways. I hope that you have a reasonable understanding of classes by now. A class is basically a template for a type of object. It describes what an object of that type has (data = properties) and does (behaviour = methods). You can instantiate that class to create an object of that type. Just like a class is a type, a delegate is also a type. Delegates are much simpler than classes but they are still first-class types in C#, just as structures and enumerations are. The word "delegate" is used to refer to a delegate type but also an instance of a delegate type. When you instantiate a delegate type, the object is also generally called a "delegate". This can be confusing so, when the word "delegate" is used, it's important to be clear which context it's being used in. It would be more accurate if we were to use "delegate" for the type and "delegate instance" for the object, just as we could use "class instance", but you'll rarely see that.

So, a delegate is a type. A delegate type is declared pretty much just like a method, i.e. it has a name, a return type and zero, one or more parameters of particular types. A delegate instance is basically an object that refers to a method that has the same signature, i.e. return type and number and type of parameters, as the delegate type. As we're talking about events, let's use the EventHandler delegate as an example. It is declared like this:
public delegate void EventHandler(Object sender, EventArgs e)

That should look rather familiar, given that many event handlers have that same signature. For example, if you create a handler for the Click event of a Button or a TextChanged event of a TextBox, the method generated has a return type of 'void' and has parameters 'sender' and 'e' of types Object and EventArgs. That means that an instance of the EventHandler delegate can refer to one of those methods.

Now, logically, a event is basically a notification to zero, one or more listeners that something has taken place. For instance, when the user clicks a Button, it raises its Click event and anyone listening can receive notification and perform an appropriate action. If you add a Button to a Form and add a Click event handler, when the user clicks the Button, you see that method in the form get executed, like the form is reacting to the notification from the Button.

Physically though, things are a bit different. Thanks to a bit of magic behind the scenes, what we see as an event is basically a collection of delegate instances. In the above example, what basically happens behind the scenes when you register a form method to handle the Click event of a Button is that an EventHandler delegate instance is created that refers to the form method and that object is then added to the collection of delegates in the Button's Click event. When the user clicks the Button, what actually happens is basically the Button loops through the collection of EventHandler delegates in its Click event and invokes each one. Invoking a delegate has the effect of executing the method it refers to. When the user clicks the Button, the Button invokes the EventHandler delegate instance stored in its Click event and that causes the form method to be executed.

Delegates are used in many other places too but they work in pretty much exactly the same way. Rather than being stored in the special collection that is an event, delegates can also simply be assigned to variables of there type, just like instances of a class, structure or enumeration. You can access the delegate instance from that variable at any time and invoke it, thus executing the method it refers to. As an example, consider the Array.Sort method and its overload that takes a Comparison<T> delegate. That delegate has two parameters of type T and returns an 'int' to indicate their relative order. When you call the Array.Sort method, it will repeatedly compare pairs of elements in an array in accordance with an algorithm and then move them as required until they are in the appropriate order.

One of the ways that it can perform that comparison is using a Comparison<T> delegate. It's up to you to write a method that compares two objects of the desired type and returns an 'int' that indicates their relative order. As an example of how this may be useful, let's say that you have a String array. If you were to perform the default sort then you'd get the Strings sorted alphabetically. What if you wanted them sorted by length though? Easy! Write a method that compares two Strings by Length, create a Comparison<string> delegate and pass it to Array.Sort, e.g.
private int CompareStringsByLength(string x, string y)
{
    return x.Length.CompareTo(y.Length);
}

Array.Sort(myStringArray, new Comparison<string>(CompareStringsByLength));

The Array.Sort method will now repeatedly invoke that delegate and rearrange the elements of the array based on the results it returns, causing the array to be sorted by the length of its elements.

Note that, in this case, you don't need to explicitly specify the type of the delegate because the compiler can infer it from usage:

Array.Sort(myStringArray, CompareStringsByLength);

In that case, the compiler knows that only certain types can be used as a second argument and the signature of the method specified matches the signature of a delegate type that is one of those allowed, so it is used implicitly.
 
I think it would help me a lot of if we could examine the code snippets I'm working with. To really grasp, what each piece of this puzzle is doing. To my understanding...

1. The frmCustomer object is instantiated, it instantiates a CustomerList object, named "customers".

2. The custmers object has a delegate (named ChangeHandler) declared within it, who's return type is void, and accepts one parameter of the CustmerList type, given a name "customers" (establishing it's "signiture").

3. An event, named "Changed", of the ChangeHandler type is also declared within the customers object. This event is "raised" whenever the add() or remove() methods are called.

Now here is where I get confused...

C#:
        private void frmCustomers_Load(object sender, EventArgs e)
        {
            customers.Changed += customers =>
            {
                customers.Save();
                FillCustomerListBox();
                MessageBox.Show("The 'Changed' event has been raised.", "Event Raised");
            };
            customers.Fill();
            FillCustomerListBox();
        }
I understand that when the frmCustmers object is "loaded", these statements are called.
I understand that we're using a Lambda Expression to "wire" the customers.Save(), FillCustomerListBox(), and MessageBox.Show() to the Changed event of the customers object.
But,

1. where is our delegate instantiated? I thought it needed to be instantiated at some point.

2. Why do none of these three methods [ customers.save(), FillCustomerListBox(), MessageBox.Show() ] which are called, share the ChangeHandler delegates signature? I thought in order for a method to be called by a delegate, it must have the same signature.
 
1. where is our delegate instantiated? I thought it needed to be instantiated at some point.
A lambda expression is a delegate. When you use a lambda expression, you create a delegate to an anonymous method, rather than to a named method. You could rewrite the code you just posted to use a named method like this:
C#:
private void frmCustomers_Load(object sender, EventArgs e)
{
    customers.Changed += customers_Changed;
    customers.Fill();
    FillCustomerListBox();
}

private void customers_Changed(CustomerList [COLOR="#FF0000"]customers[/COLOR])
[COLOR="#FF0000"]{
    customers.Save();
    FillCustomerListBox();
    MessageBox.Show("The 'Changed' event has been raised.", "Event Raised");
}[/COLOR]
I've highlighted the parts of the named method that are reproduced in the lambda expression. Everything else is inferred from the context.
2. Why do none of these three methods [ customers.save(), FillCustomerListBox(), MessageBox.Show() ] which are called, share the ChangeHandler delegates signature? I thought in order for a method to be called by a delegate, it must have the same signature.
Hopefully the answer to that is relatively clear from the previous answer. Those methods you mention have nothing to do with the delegate. They are simply methods called by another method. It's that other calling method, whether named or anonymous, that the created delegate refers to that has to have a signature that matches the delegate type, which it does in this case. The lambda expression in your original code does have one parameter and that parameter is of type CustomerList and it doesn't return anything.
 
To show that a lambda is a delegate, let's go back to my previous example of Array.Sort. In that case, I did this:
private int CompareStringsByLength(string x, string y)
{
    return x.Length.CompareTo(y.Length);
}

Array.Sort(myStringArray, CompareStringsByLength);

but I could have done this instead:
Array.Sort(myStringArray, (x, y) => x.Length.CompareTo(y.Length));

There I'm using a lambda expression to create a delegate that has two 'string' parameters and returns an 'int', just as the Comparison<string> requires. You can see that the body of the lambda matches the body of the named method used in my original example.
 
[solved]

Yes!!! I get it!!!! Thank you so much. Your help, along with some from IRC has cleared up my confusion.
C#:
customers.Changed += customers =>
{
    MessageBox.Show("The 'Changed' event has been raised.", "Event Raised");
};
"customers.Changed" is the event.
"customers =>" is the Lambda Expression (which creates our delegate object with a single parameter of 'customers', and has statements within which will be called. )
"+=" subscribes the Lambda Expression to the event.

The signature of the methods called within the lambda block are irrelevant, all that matters is the lambda is of the correct type. Which it is, because it's wired to the Changed event, which is of the ChangeHandler type.

I'm not sure if I explained it correctly, but I understand it, and it works! Moving on!! Again thank you so much!!! lambda syntax is crazy!!
 
lambda syntax is crazy!!

It can make code a bit dense, which can mean confusing to those starting out, but it can make things far more concise so you will likely find yourself using it a fair bit. Just don't get too carried away as you can make your code harder to read if you're not careful.
 
Back
Top Bottom