What are the Delegates?
Delegates are function pointer in C#. It can only point to functions which are matching to the signature of the delegate. Delegates are very useful to write loosely coupled codes, also it is used to code event-based actions. When we write code, imagine there are some core conditions that determine the behavior of the code. We can simply hard code conditions, but to make it flexible to different users we want the users to write those core logic. By using Delegate we can achieve this functionality so that we can write less, modular, and loosely coupled code.
Delegates in C# with Example
Delegate Declaration
public delegate bool IsPassedExam(int totalMarks);
The above code declares the delegate which can be pointed to methods that accept 1 integer argument and return a boolean value. Delegates are type-safe. So the above delegate can’t be used to point to methods that do not have method signatures the same as the delegate declared.
Use of Delegates with Example Code
Let’s see how to use delegates in C#. The below line shows the declaration and usage of delegate with an example.
using System; using System.Collections.Generic; namespace Learn { //Delegate declaration, this delegate //points to a function which accepts integer & returns boolean public delegate bool IsPassedExam(int totalMarks); public class Student { public readonly int totalMarks; public string Name { get; set; } public Student(string name, int totalMarks) { this.Name = name; this.totalMarks = totalMarks; } //Below method expects the condition to determine //whether a student pass or fails from the user calling it. public void CheckIfPassed(IsPassedExam isPassedExam) { //isPassedExam calls the user provided function. if (isPassedExam(this.totalMarks)) { Console.WriteLine($"Student {this.Name} passed in exam"); } else { Console.WriteLine($"Student {this.Name} failed in exam"); } } } class Learn { //user provided function to determin //whether a student pass or fail. public static bool ConditionForPassing(int marks) { return marks > 50; } public static void Main(string[] args) { //sample list of students List<Student> students = new List<Student> { new Student("Hari",90), new Student("Tom",98), new Student("John",46), new Student("James",49) }; IsPassedExam isPassedExam = new IsPassedExam(ConditionForPassing); foreach (Student student in students) { student.CheckIfPassed(isPassedExam); } } } }
In the above example, we have a method named ConditionForPassing
which determines the passing criteria of a student. if the score is greater than 50 then the student is passed otherwise failed.
In the calling Main method, we are creating the instance of the delegate & to its constructor, we are passing the custom implemented function matching the signature expected by the delegate.
We can still reduce the code by using an anonymous function or by using Lamda expression, see below.
public static void Main(string[] args) { //sample list of students List<Student> students = new List<Student> { new Student("Hari",90), new Student("Tom",98), new Student("John",46), new Student("James",49) }; foreach (Student student in students) { student.CheckIfPassed((m) => { return m > 50; }); } }
Delegates Pointing to Instance Methods
A delegate can point both static & instance methods with matching signature.The below code explains this.
public class Logic { //user provided function to determin //whether a student pass or fail. public bool ConditioForPassing(int marks) { return marks > 50; } } class Learn { public static void Main(string[] args) { //sample list of students List<Student> students = new List<Student> { new Student("Hari",90), new Student("Tom",98), new Student("John",46), new Student("James",49) }; Logic logic = new Logic(); IsPassedExam isPassedExam = new IsPassedExam(logic.ConditioForPassing); foreach (Student student in students) { student.CheckIfPassed(isPassedExam); } } }
Multicast Delegates in C#
We can combine many delegates of the same signature to one delegate, when called it invokes all the functions pointed by these delegates. To combine the delegates we can use the “+” operator.
using System; using System.Collections.Generic; namespace Learn { public delegate void Notify(string message); class Learn { public static void Notify_1(string message) { Console.WriteLine("From Notify _1 " + message); } public static void Notify_2(string message) { Console.WriteLine("From Notify _2 " + message); } public static void Notify_3(string message) { Console.WriteLine("From Notify _3 " + message); } public static void Notify_4(string message) { Console.WriteLine("From Notify _4 " + message); } public static void Main(string[] args) { Notify n1, n2, n3, n4, m; n1 = Notify_1; n2 = Notify_2; n3 = Notify_3; n4 = Notify_4; //Calling delegates individually n1("Hello"); n2("Hello"); n3("Hello"); n4("Hello"); //m=>multicast delegate calls all other delegates when //invoked m = n1 + n2 + n3 + n4; m(" From master"); } } }
Events in C#
Events in C# can be used to notify the occurrence or completion of something that is useful to some other subscribers. The event can be something like completion of a task, button click, etc. Code which raises an event called the producer. The code that subscribes to get notified is known as the subscriber.
Delegates and Events in C# related. The event in c# is encapsulated over delegates. Method subscribed to events using the operator “+=” also methods can be unsubscribed using the operator “-=“.
C# Delegate Event
Let us see how we can create an event by explicitly creating a delegate. The below example shows the basic example which explains working of it. The Producer
class has an operation ProduceMessage
, on completion of this task, it calls NotifySubscribers
to notify the completion of the task. NotifySubscribers
method checks for the subscribers & notifies all the subscribers. In this example, subscriber1 and subscriber2 are the subscribers, So these methods are notified.
using System; using System.Collections.Generic; namespace Learn { public delegate void OnComplete(); public class Producer { public event OnComplete OnCompleted; //producer operation public void ProdcueMessage(string Message) { Console.WriteLine(Message); //Producer tells to Notify the completion. NotifySubscribers(Message); } protected virtual void NotifySubscribers(string message) { //Notify all the subscribers OnCompleted?.Invoke(); } } public class Subscriber { public static void subscriber1() { Console.WriteLine("Subscriber 1 called"); } public static void subscriber2() { Console.WriteLine("Subscriber 2 called"); } } class Practice { public static void Main(string[] args) { Producer producer = new Producer(); //subscribe to the event OnCompleted producer.OnCompleted += Subscriber.subscriber1; producer.OnCompleted += Subscriber.subscriber2; producer.ProdcueMessage("Hello"); } } }
The below code declares an event
public event OnComplete OnCompleted;
raise event so that subscribers subscribed to this event can take actions
OnCompleted?.Invoke();
Below is the code which subscribes the subscribers to the event.
producer.OnCompleted += Subscriber.subscriber1; producer.OnCompleted += Subscriber.subscriber2;
C# EventHandler
For creating events in C#, we don’t have to create delegates separately. There is a built-in event handler delegate EventHandler or EventHandler<TEventArgs> in C# to help us here. The event handler method in C# does not return any value but it has two arguments.
The first one is the event source instance which raised this event and the second one is event-related data.
EventHandler delegate in C# can be used when we don’t have any additional event data. Let’s modify the previous example to use this built-in event handler.
using System; using System.Collections.Generic; namespace Learn { public class Producer { public event EventHandler OnCompleted; //producer operation public void ProdcueMessage(string Message) { Console.WriteLine(Message); //Producer tells to Notify the completion. NotifySubscribers(Message); } protected virtual void NotifySubscribers(string message) { //Notify all the subscribers OnCompleted?.Invoke(this, EventArgs.Empty); } } public class Subscriber { public static void Subscriber1(Object Obj, EventArgs e) { Console.WriteLine("Subscriber 1 called"); } public static void Subscriber2(Object Obj, EventArgs e) { Console.WriteLine("Subscriber 2 called"); } } class Practice { public static void Main(string[] args) { Producer producer = new Producer(); //subscribe to the event OnCompleted producer.OnCompleted += Subscriber.Subscriber1; producer.OnCompleted += Subscriber.Subscriber2; producer.ProdcueMessage("Hello"); } } }
We can also pass custom objects as event data, to do that we need to inherit a new custom event data class from EventArgs.Example is below.
public class EventData:EventArgs { public string Message { get; set; } public bool Success { get; set; } }
Lets modify the previous example to use custom event data class.
using System; using System.Collections.Generic; namespace Learn { public class EventData:EventArgs { public string Message { get; set; } public bool Success { get; set; } } public class Producer { public event EventHandler<EventData> OnCompleted; //producer operation public void ProdcueMessage(string Message) { Console.WriteLine(Message); //Producer tells to Notify the completion. NotifySubscribers(Message); } protected virtual void NotifySubscribers(string message) { //Notify all the subscribers EventData eventData = new EventData(); eventData.Message = message; eventData.Success = true; OnCompleted?.Invoke(this, eventData); } } public class Subscriber { public static void Subscriber1(Object Obj, EventData e) { Console.WriteLine($"Subscriber 1 called,Success={e.Success},Messgae={e.Message}"); } public static void Subscriber2(Object Obj, EventData e) { Console.WriteLine($"Subscriber 2 called,Success={e.Success},Messgae={e.Message}"); } } class Practice { public static void Main(string[] args) { Producer producer = new Producer(); //subscribe to the event OnCompleted producer.OnCompleted += Subscriber.Subscriber1; producer.OnCompleted += Subscriber.Subscriber2; producer.ProdcueMessage("Hello"); } } }