Events in C#

Eventi in C#

What Are Events In C#?1

In general terms, an event is something special that is going to happen. For example, Microsoft launches events for developers, to make them aware about the features of new or existing products. Microsoft notifies the developers about the event by email or other advertisement options. So in this case, Microsoft is a publisher who launches (raises) an event and notifies the developers about it and developers are the subscribers of the event and attend (handle) the event.

Events in C# follow a similar concept. An event has a publisher, subscriber, notification and a handler. Generally, UI controls use events extensively. For example, the button control in a Windows form has multiple events such as click, mouseover, etc. A custom class can also have an event to notify other subscriber classes about something that has happened or is going to happen. Let’s see how you can define an event and notify other classes that have event handlers.

An event is nothing but an encapsulated delegate. As we have learned in the previous section, a delegate is a reference type data type. You can declare the delegate as shown below:

An event can be used to provide notifications. You can subscribe to an event if you are interested in those notifications. You can also create your own events and raise them to provide notifications when something interesting happens. The .NET Framework offers built-in types that you can use to create events. By using delegates, lambda expressions, and anonymous methods, you can create and use events in a comfortable way.

A popular design pattern is application development is that of publish-subscribe: you can subscribe to an event and then you are notified when the publisher of the event raises a new event. This is used to establish loose coupling between components in an application. Delegate form the basis for the event system in C#

An event is a special kind of delegate that facilitates event-driven programming. Events are class members that cannot be called outside of the class regardless of its access specifier. So, for example, an event declared to be public would allow other classes the use of += and -= on the event, but firing the event (i.e. invoking the delegate) is only allowed in the class containing the event. Let’s see an example, To declare an event, use the event keyword before declaring a variable of delegate type, as below:

1public delegate void someEvent();
2
3public event someEvent someEvent;

Thus, a delegate becomes an event using the event keyword.

First example

  Ottieni il codice

 1namespace DemoEventsSimple
 2{
 3    //Define publisher class as Pub
 4    public class Pub
 5    {
 6        //OnChange property containing all the 
 7        //list of subscribers callback methods
 8        public event Action OnChange = delegate { };
 9        public event Action<int> OnGetInput = delegate { };
10
11        public void Raise()
12        {
13            //Invoke OnChange Action
14            OnChange();
15        }
16
17        //un esempio che prende in input un parametro di tipo intero
18        public void RaiseWithInput(int p)
19        {
20            //Invoke OnGetInput Action
21            OnGetInput(p);
22        }
23    }
24
25    internal class Program
26    {
27        static void Main(string[] args)
28        {
29            //Initialize pub class object
30            Pub p = new();
31
32            //register for OnChange event - Subscriber 1
33            p.OnChange += () => Console.WriteLine("Subscriber 1!");
34            //register for OnChange event - Subscriber 2
35            p.OnChange += () => Console.WriteLine("Subscriber 2!");
36
37            //raise the event
38            p.Raise();
39            //After this Raise() method is called
40            //all subscribers callback methods will get invoked
41
42            //definiamo una variabile locale per dimostrare il concetto di closure
43            int x = 5;
44
45            //subscriber all'evento OnGetInput: accetta in input un numero intero
46            p.OnGetInput += (q) => {
47                Console.WriteLine("OnGetInput fired");
48                q = x + 1;
49                Console.WriteLine($"parametro locale q = {q}");
50                Console.WriteLine($"riferimento esterno alla lambda expression x = {x}");
51            };
52
53            Console.WriteLine("Invochiamo RaiseWithInput");
54            //qui passiamo un valore intero che corrisponderà alla q
55            p.RaiseWithInput(6);
56        }
57    }
58
59}

Also, you may have noticed special syntax of initializing the OnChange field to an empty delegate like this delegate { }. This ensures that our OnChange field will never be null. Hence, we can remove the null check before raising the event, if no other members of the class making it null. When you run the above program, your code creates a new instance of Pub, subscribes to the event with two different methods and raises the event by calling p.Raise or p.RaiseWithInput. The Pub class is completely unaware of any subscribers. It just raises the event.

A more complex example

  Ottieni il codice

Nota: questo esempio è riportato a solo scopo informativo.

 1namespace DemoEvents
 2{
 3    public class Student
 4    {
 5        public int Id { get; set; }
 6        public string? Name { get; set; }
 7        public int Age { get; set; }
 8    }
 9
10    public class Exam
11    {
12        public int Id { get; set; }
13        public string? Name { get; set; }
14        public int Score { get; set; }
15    }
16
17    public class StudentTutor
18    {
19        public event Action<Student>? OnStudentReceived;
20        public event Func<Student, Exam, int>? OnStudentTakeExam;
21
22        //metodo che solleva l'evento StudentReceived
23        public void StudentReceived(Student s)
24        {
25            OnStudentReceived?.Invoke(s);
26        }
27
28        //metodo che solleva l'evento StudentTakeExam
29        public void StudentTakeExam(Student s, Exam e)
30        {
31            //callback function che restituisce il punteggio dell'esame
32            int? score = OnStudentTakeExam?.Invoke(s, e);
33            Console.WriteLine($"Lo studente {s.Name} ha sostenuto l'esame {e.Name} con un punteggio di {score}");
34        }
35    }
36
37    internal class Program
38    {
39        static Random gen = new Random();
40        static void Main(string[] args)
41        {
42            //creazione di un oggetto Publisher
43            StudentTutor tutor = new StudentTutor();
44            //creazione di un oggetto Publisher
45            tutor.OnStudentReceived += (s) => Console.WriteLine($"Benvenuto {s.Name}");
46            tutor.OnStudentTakeExam += Tutor_OnStudentTakeExam;
47
48            //creazione di studenti ed esami
49            Student marioRossi = new() { Id = 1, Name = "Mario Rossi", Age = 21 };
50            Exam fisica1 = new() { Id = 1, Name = "Fisica 1" };
51            Exam analisi1 = new() { Id = 2, Name = "Analisi 1" };
52
53            //attivazione eventi
54            tutor.StudentReceived(marioRossi);
55            tutor.StudentTakeExam(marioRossi, fisica1);
56            tutor.StudentTakeExam(marioRossi, analisi1);
57            tutor.StudentTakeExam(marioRossi, fisica1);
58            tutor.StudentTakeExam(marioRossi, analisi1);
59            tutor.StudentTakeExam(marioRossi, fisica1);
60            tutor.StudentTakeExam(marioRossi, analisi1);
61        }
62
63        //implementazione callback function corrispondenti agli eventi
64        private static int Tutor_OnStudentTakeExam(Student s, Exam e)
65        {
66            return gen.Next(15, 31);
67        }
68        private static void Tutor_OnStudentReceived(Student s)
69        {
70            Console.WriteLine($"Benvenuto {s.Name}");
71        }
72    }
73}

All the subscribers must provide a handler function, which is going to be called when a publisher raises an event.
The following image illustrates an event model:

Subscriber Publisher Model

Event publisher-Subscriber

Points to Remember

  1. Use event keyword with delegate type to declare an event.
  2. Check event is null or not before raising an event.
  3. Subscribe to events using += operator. Unsubscribe it using -= operator.
  4. Function that handles the event is called event handler. Event handler must have same signature as declared by event delegate.
  5. Events can have arguments which will be passed to handler function.
  6. Events can also be declared static, virtual, sealed and abstract.
  7. An Interface can include event as a member.
  8. Events will not be raised if there is no subscriber
  9. Event handlers are invoked synchronously if there are multiple subscribers
  10. .NET uses an EventHandler delegate and an EventArgs base class.