Delegates are very similar to events and, in fact, events can be thought of as a particular kind of delegate. They are a very simple tool to use to pass a signal or message from one place to another in the program. Unlike when creating custom events, we are not forced to use particular input parameters, for example, some form of the EventArgs class. We are totally unconstrained when creating custom delegates and are able to define our own method signatures, including both input and output parameter types.
As most of you will already be familiar with events and event handling, you'll already inadvertently know how to use delegates too. Let's look at a simple example. Imagine that we have a parent View Model that spawns child View Models and that one of these child View Models is paired with a View that enables administrative users to select security permissions. Now, let's imagine that the parent View that relates to the parent View Model has a menu that needs to be updated depending on the user's selection in the child View. How do we notify the parent View Model upon selection?
This is where delegates save the day. Keeping this example simple initially, let's say that we just need to notify the parent View Model that a particular change has been made so that it can refresh the current user's security permissions from a database. In this case, we only need to pass a signal, so we can create a delegate with no input or output parameters. We can declare it in the View Model that will be sending the signal, in this case, the child View Model:
public delegate void Signal();
Note that we define it in the same way that we define an abstract method, except that the abstract keyword is replaced with the delegate keyword after the access modifier. In short, a delegate defines a type that references a method with a particular signature. Now that we have defined our signaling delegate, we need to create a way for elements outside the View Model to use it. For this, we can simply create a property of the type of our new delegate in the same View Model:
public Signal OnSecurityPermissionChanged { get; set; }
As we don't need any property change notifications for this property, we can save ourselves some typing and take advantage of the .NET Auto-Implemented Property syntax. Bear in mind that delegates work in a multicast way like events, meaning that we can attach more than one handler to each one. In order to do this, we need to use the += operator to add handlers for the delegate, and in this example, we would want to do that in the parent View Model when the child View is instantiated:
ChildViewModel viewModel = new ChildViewModel(); viewModel.OnSecurityPermissionChanged += RefreshSecurityPermissions;
Here, we have assigned the RefreshSecurityPermissions method in the parent View Model to be the handler for this delegate. Note that we omit the parenthesis and the input parameters if there were any when attaching the handler. Now, you may be wondering, "What does the method signature of this handler look like?", but you already have the answer to this. If you remember, we declared the delegate with the signature of the method that we want to handle it. Therefore, any method that shares this signature can be a handler for this type of delegate:
private void RefreshSecurityPermissions() { // Refresh user's security permissions when alerted by the signal }
Note that the name used is irrelevant and all that matters when matching the delegate signature are the input and output parameters. So, we now have our delegate declared and hooked up to a handler in the parent View Model, but it's still not going to do anything because we haven't actually called it yet. In our example, it's the child View Model that is going to call the delegate because that's the object that needs to send out the information or signal in this case.
When calling delegates, we must always remember to check for null before trying to use them because there may be no handlers attached. In our example, we'd call our Signal delegate via the OnSecurityPermissionChanged property at the point that we need to send the signal from the child View Model, let's say in reaction to a user changing their own security permissions:
if (OnSecurityPermissionChanged != null) OnSecurityPermissionChanged();
Alternatively, we could do so using the more concise null conditional operator in C# Version 6.0, which calls the delegate's Invoke method if it is not null:
OnSecurityPermissionChanged?.Invoke();
Note that we do need to include the parenthesis when calling the delegate in the first example even though OnSecurityPermissionChanged is a property. This is because the delegate type of the property relates to a method and it is that method that we are calling. Please bear in mind that the first of these methods is not thread safe, so if thread safety is important for your application, then you will need to use the latter way.
We now have the complete picture, but while it is common to have a signal-sending delegate such as this, it is not overly useful because it only passes a signal with no other information. In many real-world scenarios, we would typically want to have some sort of input parameter so that we could pass some information, rather than just a signal.
For example, if we wanted to be notified with details each time a user selected a different item from a collection control in the UI, we could add a CurrentItem property into a generic BaseCollection class in our application and data bind it to the data bound collection control's SelectedItem property. This CurrentItem property would then be called by the WPF Framework each time a user makes a new selection, and so we can call our new delegate from its property setter:
protected T currentItem; public virtual CurrentItemChange CurrentItemChanged { get; set; } public virtual T CurrentItem { get { return currentItem; } set { T oldCurrentItem = currentItem; currentItem = value; CurrentItemChanged?.Invoke(oldCurrentItem, currentItem); NotifyPropertyChanged(); } }
Delegates can be used to communicate between any related classes as long as they have access to the class that exposes the delegate so that they can attach a handler. They are commonly used to send information between child Views or View Models and their parents, or even between Views and View Models, but they can also be used to pass data between any two connected parts of the application.