In the last chapter, we learned about enumerations and saw how they allow us to assign meaningful names to constants. In their basic form, the first name will represent the value
0 and each subsequent name will have a value of one more, but it is possible to assign different values to the names and thereby change the default values. Enumerations, we learned, help make code more readable, and we can use methods of the Enum class to get the name or value of an item or items in the enumeration. Finally, we also saw that an enumeration is a “special” data type, which is defined by the developer.
In this chapter we will look at delegates, which are also a type, but they are a reference type that holds a reference to a method.
The Microsoft site explains delegates as follows:
A delegate is a type that represents references to methods with a particular parameterlist and return type. When you instantiate a delegate, you can associate its instance with any method with a compatible signature and return type. You can invoke (or call) the method through the delegate instance.
Put in more simple terms, we should think of a delegate as being a type that is used to represent a method with a return type and method signature. We read in Chapter 14 with interface methods
, we wrote them as a line of code showing the return type followed by the method signature. Examples of method signatures we looked at are
VatCalculation(double itemPrice)
CalculateTax(double itemPrice, int quantity)
CalculateTax(int quantity, double itemPrice)
When we added the return type in front of the method signature, we had the interface method:
Now, also thinking back Chapter 12, we saw that we could pass arguments to a method, and the method accepted the arguments as its parameters. The arguments
and parameters
are types, for example, an int or a string or a float. However, we never read about passing a method as an argument to another method, which would accept the method as its parameter. Well, this is where we could use a delegate, to pass a method to another method. The Microsoft site
also says
Delegates are used to pass methods as
arguments to other methods.
The ability to pass methods as arguments makes the delegate a perfect candidate when defining what are referred to as callback methods, as we will see throughout the examples we code. When we develop code that needs to work with a delegate, we will use three stages
to achieve this:
Declare the delegate with its return type and method signature.
Then set a target method for the delegate, having created the instance of the delegate.
Then invoke the delegate because it has been defined and points to a method.
When we declare a delegate, it will have a similar format to declaring an abstract method, a return type followed by the method signature. Some examples of delegate declarations
are
public delegate int VATCalculation(double itemPrice);
private delegate int CalculationTax(double itemPrice, int quantity);
internal delegate int CalculateTax(int quantity, double itemPrice);
Even just on what we have read so far, we can think of delegates as
Having an access modifier of public, private, or internal
Allowing methods to be passed as arguments to a method that accepts them as parameters
Being used when we wish to use callback methods
But they can also
Be joined, chained, so that multiple methods can be called from a single delegate, and this is referred to as multicast delegates
Be assigned to any method that matches the delegate’s signature, for example, if the delegate declaration
is
then either or both of these methods can be assigned to an instance of Calculation
public static double CalculateTax(int quantity, double itemPrice)
{
return quantity * itemPrice * 0.20;
}
public static double CalculateTotalBeforeVAT(int quantity, double itemPrice)
{
return quantity * itemPrice;
}
since both methods match the delegate signature.
On the other hand, the method shown in the following cannot be assigned to the Calculation delegate as the method signature
is different; it has a double followed by an int rather than an int followed by a double:
public static double calculateTotalBeforeVAT(double
itemPrice, int quantity)
{
return quantity * itemPrice;
}
Let’s code some C# and build our programming muscle.
Add a new project to hold the code for this chapter.
1.
Right-click the solution CoreCSharp
.
2.
Choose Add.
3.
Choose New Project.
4.
Choose Console App from the listed templates that appear.
5.
Click the Next button.
6.
Name the project Chapter21 and leave it in the same location.
7.
Click the Next button.
8.
Choose the framework to be used, which in our projects will be .NET 6.0 or higher.
9.
Click the Create button.
Now we should see the Chapter21 project within the solution called CoreCSharp.
10.
Right-click the Chapter21 project in the Solution Explorer panel.
11.
Click the Set as Startup Project option.
Notice how the Chapter21 project name has been made to have bold text, indicating that it is the new startup project and that it is the Program.cs file within it that will be executed when we run the debugging.
Amend the name of the Program.cs file, remembering the coding principle of “self-documenting
” code.
12.
Right-click the Program.cs file in the Solution Explorer window.
13.
Choose Rename.
14.
Change the name to Delegates.cs.
15.
Press the Enter key.
Single Delegate
Looking back to Chapter 13 on classes
, we had two methods as shown in the following:
Console.WriteLine($"The DetermineVATAmount method is executing using the parameter {totalValueOfClaimsPassedIn}
and the output produced is ${vatAmount} which represents the VAT amount
");
return vatAmount;
} // End of method DetermineVATAmount()
} // End of Delegates class
} // End of Chapter21 namespace
Listing 21-2
Add the method that will be the target for the delegate
We will now instantiate the delegate and then set the target method for the delegate instance to be the method we have just added, DetermineVATAmount()
, but without using the ().
Point the delegate object myCalculationDelegate to
the method DetermineVATAmount
We are making a reference to the method
*/
myCalculationDelegate = DetermineVATAmount;
} // End of Main() method
Listing 21-3
Instantiate the delegate and set the target method
Invoke the Delegate
We will now invoke the delegate using our delegate instance. The call will be made and the returned value will be assigned to a local variable
. We will then display the returned value from within a WriteLine method
.
Console.WriteLine($"Invoked delegate returned VAT of ${vatAmount}");
} // End of Main() method
Listing 21-4
Invoke the delegate
20.
Click the File menu.
21.
Choose Save All.
22.
Click the Debug menu.
23.
Choose Start Without Debugging.
The console output will be as shown in Figure 21-1, and we can see that the method, DetermineVATAmount()
, has taken the 120 and the 0.00 and calculated that 20 of this 120 is VAT so the 0.00 was the VAT amount going in and it comes out of the method as 20.
24.
Press the Enter key to close the console window.
Very good, we might be thinking. But couldn't we just have called the method directly as we did in Chapter 12 and as we do with methods like the WriteLine()
? Yes, we could. So why use a delegate then? Simply put, because we can use a delegate to pass a method to another method, and we will see this later when we code an example related to filtering policies. This example was just used to get us started with delegates.
Multicast Delegates
At the start of the chapter, we read that delegates can be joined or chained, so that multiple methods can be called from a single delegate, and this is also referred to as multicast delegates. If we think of it another way, we could say a multicast delegate is a delegate that has references
to more than one method. This means that when we invoke a multicast delegate, all the methods it is pointing to will be invoked in the order they have been declared.
25.
Amend the code, as in Listing 21-5, to add a local variable that the method will use.
static void Main(string[] args)
{
// Create the variables we need
double totalOfAllClaims;
Listing 21-5
Add a local level variable
to be used by the new method
26.
Amend the code, as in Listing 21-6, to add another method that will become the target for the delegate – this was the “old” method 12.
} // End of method DetermineVATAmount()
/*
Create the method that will be the target of the
delegate, this was Method 12
*/
public static double AccumulateClaimAmount(double claimAmountPassedIn, double totalOfAllClaims)
{
totalOfAllClaims += claimAmountPassedIn;
Console.WriteLine($"The AccumulateClaimAmount method is executing using the parameter {claimAmountPassedIn}
and the output produced is ${totalOfAllClaims} which represents the total claims
");
return totalOfAllClaims;
} // End of AccumulateClaimAmount method
} // End of Delegates class
} // End of Chapter21 namespace
Listing 21-6
Add a second method that will become a target for the delegate
Instantiate the Delegate Again and Set the New Instances’ Target Method
27.
Amend the code, as in Listing 21-7, to instantiate the delegate again
for two more instances calling them myAccumulateDelegate and myMulticastDelegate:
Amend the code, as in Listing 21-10, to invoke the multicast delegate
through our delegate instance, passing it the value 1500, which is the claim amount, and the value 0.00, which will be used in the first method as the vatAmount
and in the second method as the totalOfAllClaims
.
The console output will be as shown in Figure 21-2.
35.
Press the Enter key to close the console window.
Great, we have an understanding on what a delegate is, how to declare a delegate, how to set the target for the delegate instance, and how to invoke the delegate. We have also coded a multicast delegate that invokes multiple methods. Yes, this example could be improved, but it has helped us understand the concept of multicast delegates.
More
Complex Example
Now we will look at a more complex example
, which
Uses a Policy class
.
Creates six instances of the Policy class, each of which passes different values to the constructor.
Adds the six instances to a list that holds objects of type Policy.
Creates a delegate that accepts a Policy.
Creates a method that accepts the delegate as one of its parameters and assigns it to a method. The first time we use the method, we pass it the HardwareType method. The second time, it will be the PolicyDueForRenewal method
, and lastly it will be the PremiumGreaterThanTwenty method
:
Inside the method the delegate is invoked and therefore calls the HardwareType() method
or the PolicyDueForRenewal() method
or the PremiumGreaterThanTwenty() method
.
When any of the three methods are called, they are passed a Policy.
The HardwareType method returns those items in the list that contain the string Laptop, as their PolicyType, while the other methods check for a date or a value.
So, when we asked the question, "Why use a delegate?", this example will show that it allows us to pass a method to a method.
Create aPolicy class.
1.
Right-click the Chapter21 project
in the Solution Explorer panel.
2.
Choose Add.
3.
Choose Class.
4.
Name the class Policy.cs.
5.
Amend the Policy.cs class to have members, a constructor, and a property for each member containing a get and a set accessor for the private members, as in Listing 21-11.
namespace Chapter21
{
internal class Policy
{
private int policyNumber;
private String policyType;
private double monthlyPremium;
private String policyEndDate;
public Policy(int policyNumber, string policyType,
double monthlyPremium, string policyEndDate)
{
this.PolicyNumber = policyNumber;
this.PolicyType = policyType;
this.MonthlyPremium = monthlyPremium;
this.PolicyEndDate = policyEndDate;
} // End of user constructor
public int PolicyNumber
{
get => policyNumber;
set => policyNumber = value;
}
public string PolicyType
{ get => policyType;
set => policyType = value;
}
public double MonthlyPremium
{ get => monthlyPremium;
set => monthlyPremium = value;
}
public string PolicyEndDate
{ get => policyEndDate;
set => policyEndDate = value;
}
} // End of Policy class
} // End of Chapter21 namespace
Listing 21-
11
Policy class with constructor, members, and property accessors
6.
Right-click the Chapter21 project in the Solution Explorer panel.
7.
Choose Add.
8.
Choose Class.
9.
Name the class DelegatePolicy.cs.
10.
Amend the DelegatePolicy.cs class to have a Main() method and the imports, as in Listing 21-12.
using System.Collections.Generic;
namespace Chapter21
{
internal class DelegatePolicy
{
static void Main(string[] args)
{
} // End of Main() method
} // End of DelegatePolicy class
} // End of Chapter21 namespace
Listing 21-12
DelegatePolicy class
with the Main() method
We will now, at the class level, declare a delegate with the return type bool, which accepts a Policy as its parameter in the method signature.
11.
Amend the DelegatePolicy.cs class, as in Listing 21-13.
internal class DelegatePolicy
{
/*
Declare the delegate we will use.
In this case we have a return type of bool and the method
signature states that the method must accept a Policy object
Create 6 objects of type Policy - Policy is a separate class
The Policy class has 4 members - policy_number, policyType
monthlyPremium and the policyEndDate;
*/
// Laptops
Policy myPolicyOne = new Policy(123456, "Laptop", 19.99, "31/12/2021");
Policy myPolicyFive = new Policy(156790, "Laptop", 18.99, "30/12/2021");
Policy myPolicySix = new Policy(123456, "Laptop", 15.99, "15/12/2021");
// Need renewed
Policy myPolicyTwo = new Policy(267890, "Printer", 15.99, "01/11/2021");
Policy myPolicyThree = new Policy(345908, "Small_Screen", 9.99, "01/10/2021");
// Monthly premium greater than $20
Policy myPolicyFour = new Policy(455666, "Large_Screen", 29.99, "01/12/2021");
} // End of Main() method
Listing 21-14
Create six instances of the Policy class
We will now continue adding code within the Main() method to declare and create a list that will hold the six instances of the Policy class. The list can be thought of as a collection of a specific type.
Policy myPolicyFour = new Policy(455666, "Large_Screen", 29.99, "01/12/2021");
/*
Create a strongly typed List to hold the six Policy objects.
The List of policies will be iterated later
*/
List<Policy> policies = new List<Policy>()
{ myPolicyOne, myPolicyTwo, myPolicyThree,
myPolicyFour, myPolicyFive, myPolicySix
};
} // End of Main() method
Listing 21-15
Create a list to hold the six Policy instances
We will now create a method that will find the list of policies based on the delegate passed to the method. First, as we are inside the Main() method
, we will call the method we will be creating, three times, passing in different delegates in each call.
14.
Amend the class, as in Listing 21-16, to call the not-yet-created method.
List<Policy> policies = new List<Policy>()
{ myPolicyOne, myPolicyTwo, myPolicyThree,
myPolicyFour, myPolicyFive, myPolicySix
};
/*
Call the FindPolicesByGivenDelegate method passing
it the delegate to be used
*/
FindPolicesByGivenDelegate("The list of Laptops policies are", policies, HardwareType);
FindPolicesByGivenDelegate("The list of policies due for renewal are", policies, PolicyIsDueForRenewal);
FindPolicesByGivenDelegate("The list of policies with a premium greater than $20.00 are", policies, PremiumGreaterThanTwenty);
} // End of Main() method
Listing 21-16
Call a method, passing it the Policy list and delegate
Now we need to create the method that will
Accept a string value to be used as a heading.
Accept the list of policies that will be used
Accept the delegate that is to be used. This will be in the form of an instantiation – FindByDelegate filterMethodToBeUsed – so the filterMethodToBeUsed will be one of the methods. It will be the method HardwareType() or the method PolicyIsDueForRenewal() or the method PremiumGreaterThanTwenty().
Iterate the list of policies, six policies in our example.
Invoke the method referred to by the delegate, which will pass back a true or false, and this calling method then displays the details for the policy if the returned value was true.
Use a “divider
” at the end of the method before the next call is made.
15.
Amend the class, as in Listing 21-17, to create the method, outside the Main() method but inside the class.
} // End of Main() method
/*
In this method we use the delegate, FindByDelegate to
invoke the method it is pointing to.
The delegate method name is passed to this method.
Create the method that accepts the Policy list
and delegate
Now we need to create the method to find those policies that are Laptops when the delegate passed in is HardwareType
. Remember the delegate is pointing to a method; it refers to a method.
16.
Amend the class to create this method called HardwareType, outside the Main() method, as in Listing 21-18.
Now we need to create the method to find those policies whose renewal date is less than or equal to a given date. In this case we have hard-coded the data as 30/11/2021, when the delegate passed in is PolicyIsDueForRenewal
.
17.
Amend the class to create this method called PolicyIsDueForRenewal, outside the Main() method, as in Listing 21-19.
} // End of HardwareType method
/*
Check if policy is due for renewal and return true or false
Right-click the Chapter21 project in the Solution Explorer panel.
20.
Choose Properties from the pop-up menu.
21.
Choose the DelegatePolicy class in the Startup object drop-down list.
22.
Close the Properties window.
23.
Click the File menu.
24.
Choose Save All.
25.
Click the Debug menu.
26.
Choose Start Without Debugging.
The console window will appear, as shown in Figure 21-3, displaying the details from the invoked methods as referred to by the delegate.
27.
Press the Enter key to close the console window.
Now that really was a complex application using delegates, so we will probably need to read the code carefully, several times, to fully appreciate what is going on.
Chapter Summary
So, finishing this chapter on delegates, we should see that we have extended our knowledge of methods and that we can use delegates to pass methods to methods. We have seen that when we declare a delegate, it has a similar format to declaring an abstract method, a return type followed by the method signature. We also learned how delegates could be chained, so that multiple methods could be called from a single delegate. This is referred to as multicast delegates.
Wow, what an achievement. This is definitely not basic coding. We are seriously doing some elaborate things with our C# code. We should be immensely proud of the learning to date. In finishing this chapter, we have increased our knowledge further. We are getting very close to our target, which once seemed so far away.