Eventi in C#
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.
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}
event
keyword compiler protects our field from unwanted access. And, it does not permit the use of =
(direct assignment of a delegate). Hence, your code is now safe from the risk of removing previous subscribers by using =
instead of +=
.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:
Event publisher-Subscriber
+=
operator. Unsubscribe it using -=
operator.