Learning C# (Day 10) - Events In C# (A Practical Approach)

Introduction

This article of the “Diving into OOP” series will explain all about events in C#. The article focuses more on practical implementations and less on theory.


Image source - https://pixabay.com/en/surprised-salaried-worker-computer-1184889/

Events (The definition)

Let’s start with the definition taken from MSDN.

“Events enable a class or object to notify other classes or objects when something of interest occurs. The class that sends (or raises) the event is called the publisher and the classes that receive (or handle) the event are called subscribers.”

Other Articles of the series 

Following is the list of all the other articles of the OOP series.

Events 

To explain the concept in detail, I have created a solution in Visual Studio 2015 with a console project named DelegatesAndEvents. A new class named EventExercises is created along with another class named Program that contains Main() method as an entry point for execution.

Lab1 
  1. using System;  
  2. namespace DelegatesAndEvents {  
  3.     class Program {  
  4.         public static void Main() {  
  5.             EventExercises eventExercises = new EventExercises();  
  6.             eventExercises.Method1();  
  7.             Console.ReadLine();  
  8.         }  
  9.     }  
  10.     public class EventExercises {  
  11.         public void Method1() {  
  12.             System.Console.WriteLine("Howdy");  
  13.         }  
  14.     }  
  15. }   

Output


The above mentioned implementation is very common and very straightforward. There is a class named EventExercises that contains a method named Method1 which writes something to the console. This method is called in the Main method through an instance of EventExercises class named eventExercises directly. Simple enough. Now, let’s try to call this method indirectly, in the following example. 

  1. using System;  
  2. namespace DelegatesAndEvents {  
  3.     public delegate void MyDelegate();  
  4.     class Program {  
  5.         public static void Main() {  
  6.             EventExercises eventExercises = new EventExercises();  
  7.             eventExercises.myDelegate += new MyDelegate(eventExercises.Method1);  
  8.             eventExercises.Method2();  
  9.             Console.ReadLine();  
  10.         }  
  11.     }  
  12.     public class EventExercises {  
  13.         public event MyDelegate myDelegate;  
  14.         public void Method2() {  
  15.             if (myDelegate != null) Method1();  
  16.         }  
  17.         public void Method1() {  
  18.             System.Console.WriteLine("Howdy");  
  19.         }  
  20.     }  
  21. }   

Output


So, we did the same thing but in a different way. A new delegate named MyDelegate is created, then an object was created for MyDelegate and we passed the name of the method as a parameter to it i.e. Method1. Since Method1 is located in EventExercises class and we are in Main() method of Program class, so we gave it the full name i.e. eventExercises. Here, we see that the instance myDelegate is an event and not a delegate and we use the syntax += instead of = else it would result in an error, as shown below.


i.e. The event “EventExercises.myDelegate” can only appear on the left hand side of += or -+= (except when used from within the type 'EventExercises')

The instance myDelegate’s definition is somewhat different. In the former example, we basically kept the return value of the object of delegate, but here we are appending a new keyword named “event” to that instance. And the Method2 is invoked from Main. We check the value of the myDelegate in Method2 and then we call Method1() if the value is not null. Therefore if we remove the line new MyDelegate, then myDelegate doesn’t gets initialized and will always be null and so Method1 will not be called.

Lab2 
  1. using System;  
  2. namespace DelegatesAndEvents {  
  3.     public delegate void MyDelegate();  
  4.     class Program {  
  5.         public static void Main() {  
  6.             EventExercises eventExercises = new EventExercises();  
  7.             eventExercises.myDelegate += new MyDelegate(eventExercises.Method1);  
  8.             eventExercises.Method2();  
  9.             eventExercises.myDelegate -= new MyDelegate(eventExercises.Method1);  
  10.             eventExercises.Method2();  
  11.         }  
  12.     }  
  13.     public class EventExercises {  
  14.         public event MyDelegate myDelegate;  
  15.         public void Method2() {  
  16.             if (myDelegate != null) Method1();  
  17.         }  
  18.         public void Method1() {  
  19.             System.Console.WriteLine("Howdy");  
  20.             Console.ReadLine();  
  21.         }  
  22.     }  
  23. }  

Output


So, Method2 is executed twice in the Main() method. For the first time, the instance myDelegate is not null, and then we do -= in the next line that means we basically subtract MyDelegate from the event myDelegate, but in both the cases the delegate is attached to Method1. When we subtract, the common method is dropped and event instance myDelegate is null, therefore we first added Method1 and then removed it.

Lab3 
  1. using System;  
  2. namespace DelegatesAndEvents {  
  3.     public delegate void MyDelegate();  
  4.     class Program {  
  5.         public static void Main() {  
  6.             EventExercises eventExercises = new EventExercises();  
  7.             eventExercises.myDelegate += new MyDelegate(eventExercises.Method1);  
  8.             eventExercises.myDelegate();  
  9.         }  
  10.     }  
  11.     public class EventExercises {  
  12.         public event MyDelegate myDelegate;  
  13.         public void Method1() {  
  14.             System.Console.WriteLine("Howdy");  
  15.         }  
  16.     }  
  17. }   

Output


Error    CS0070  The event 'EventExercises.myDelegate' can only appear on the left hand side of += or -= (except when used from within the type 'EventExercises').

So , we see here that like delegates, event also cannot be used directly.

Lab4 
  1. using System;  
  2. namespace DelegatesAndEvents {  
  3.     public delegate void MyDelegate();  
  4.     class Program {  
  5.         public static void Main() {  
  6.             EventExercises eventExercises = new EventExercises();  
  7.             eventExercises.myDelegate += new MyDelegate(eventExercises.Method1);  
  8.             eventExercises.pqr();  
  9.         }  
  10.     }  
  11.     public class EventExercises {  
  12.         public event MyDelegate myDelegate;  
  13.         public void pqr() {  
  14.             myDelegate();  
  15.         }  
  16.         public void Method1() {  
  17.             System.Console.WriteLine("Method1");  
  18.             Console.ReadLine();  
  19.         }  
  20.     }  
  21. }   

Output


We get the output as Method1.

So, we see here that whatever result we were trying to achieve could also be achieved without events as well.

In the code above, we attached an event myDelegate to a method Method1 in class EventExercises. Then method pqr was called by calling myDelegate() from inside it. This eventually invokes the event object myDelegate with a method Method1 in EventExercises class.

Lab5

This Lab explains how we can call more and more methods. 

  1. using System;  
  2. namespace DelegatesAndEvents {  
  3.     public delegate void MyDelegate();  
  4.     class Program {  
  5.         public static void Main() {  
  6.             EventExercises eventExercises = new EventExercises();  
  7.             eventExercises.myDelegate += new MyDelegate(eventExercises.Method1);  
  8.             eventExercises.myDelegate += new MyDelegate(eventExercises.xyz);  
  9.             eventExercises.pqr();  
  10.             eventExercises.myDelegate -= new MyDelegate(eventExercises.xyz);  
  11.             eventExercises.pqr();  
  12.             eventExercises.myDelegate -= new MyDelegate(eventExercises.Method1);  
  13.             eventExercises.pqr();  
  14.             Console.ReadLine();  
  15.         }  
  16.     }  
  17.     public class EventExercises {  
  18.         public event MyDelegate myDelegate;  
  19.         public void pqr() {  
  20.             myDelegate();  
  21.         }  
  22.         public void Method1() {  
  23.             System.Console.WriteLine("Method1");  
  24.         }  
  25.         public void xyz() {  
  26.             System.Console.WriteLine("xyz");  
  27.         }  
  28.     }  
  29. }  

Output




Image source - https://pixabay.com/en/road-sign-usa-trouble-problem-1274312/


So, we see that executing the code above, we first get the output as method1 xyz Method1 written on console followed by an exception in the code i.e. System.NullReferenceException.

This scenario is more or less like delegates. We add two methods to myDelegate event, therefore firstly we call method pqr in which we execute the myDelegate event, and then we call Method1 and method pqr as if they are associated to the myDelegate event. When we do +=, it adds a method and when we do -= it eliminates the method from the list of event, and when we invoke myDelegate event, only Method1 is called as method xyz is eliminated from the list. At the end we also remove Method1 from the list and event value will be null. So when we execute an event having no methods to notify, we end up having a runtime exception, therefore a null check for the event is always required when we use it.

Lab6 
  1. using System;  
  2. namespace DelegatesAndEvents {  
  3.     public delegate void MyDelegate();  
  4.     class Program {  
  5.         public static void Main() {  
  6.             EventExercises eventExercises = new xxx();  
  7.             Console.ReadLine();  
  8.         }  
  9.     }  
  10.     public class EventExercises {  
  11.         public event MyDelegate myDelegate;  
  12.         public void Method1() {  
  13.             System.Console.WriteLine("Method1");  
  14.         }  
  15.     }  
  16.     public class xxx: EventExercises {  
  17.         public void pqr() {  
  18.             myDelegate();  
  19.         }  
  20.     }  
  21. }   

Output

Compile time error

The event 'EventExercises.myDelegate' can only appear on the left hand side of += or -= (except when used from within the type 'EventExercises')

So, basically to call a method of the same class, we use an event. EventExercises is the base class to xxx, and it is clear that they are not in the same class. Therefore, an event in EventExercises class cannot be used in any other class. Usually, derived classes inherit the base class things, but events are exceptional and show the error at compile time rather than on run time.

Lab7 
  1. using System;  
  2. namespace DelegatesAndEvents {  
  3.     public delegate void MyDelegate();  
  4.     class Program {  
  5.         public static void Main() {  
  6.             EventExercises eventExercises = new EventExercises();  
  7.             eventExercises.myDelegate += new MyDelegate(eventExercises.Method1);  
  8.             xxx x = new xxx();  
  9.             eventExercises.myDelegate += new MyDelegate(x.xyz);  
  10.             eventExercises.pqr();  
  11.             Console.ReadLine();  
  12.         }  
  13.     }  
  14.     public class EventExercises {  
  15.         public event MyDelegate myDelegate;  
  16.         public void pqr() {  
  17.             myDelegate();  
  18.         }  
  19.         public void Method1() {  
  20.             System.Console.WriteLine("Method1");  
  21.         }  
  22.     }  
  23.     public class xxx {  
  24.         public void xyz() {  
  25.             System.Console.WriteLine("xyz");  
  26.         }  
  27.     }  
  28. }   

Output


Here, we see actual strength of events. We primarily used the same code as in previous example and named the method as xyz.

EventExercises does not contain tis message but it is picked from xxx. The event invocation works accordingly. We already saw this example for delegates in the previous article on delegates.

Lab8 
  1. using System;  
  2. namespace DelegatesAndEvents {  
  3.     public delegate void MyDelegate();  
  4.     public class Program {  
  5.         public event MyDelegate myDelegate;  
  6.         //public MyDelegate myDelegate;  
  7.         public void add_myDelegate(MyDelegate a) {  
  8.             myDelegate += a;  
  9.         }  
  10.         public void remove_myDelegate(MyDelegate a) {  
  11.             myDelegate -= a;  
  12.         }  
  13.     }  
  14. }   

Output

Compile time error

Type 'Program' already reserves a member called 'add_myDelegate' with the same parameter types

Type 'Program' already reserves a member called 'remove_myDelegate' with the same parameter types 

Here, at every instance when we try to create an event object, there are two methods created automatically in the class. The two methods are the event names prefixed by add_ and remove_. Compiler automatically adds code for these methods. That clearly states that a class could not contain methods with the same name that has an event, on the other hand it also means that the code of the event overwrites the method code. Something similar to C++, happens here, where C++ code was converted to C code by the compiler, which then was executed by the compiler as a C code. The methods that are invoked by the events are named as event handlers and they are responsible for providing notifications to the class.

Lab9 
  1. using System;  
  2. namespace DelegatesAndEvents {  
  3.     class Program {  
  4.         delegate void MyDelegate();  
  5.         public static void Main() {  
  6.             MyDelegate myDelegate = null;  
  7.             myDelegate();  
  8.         }  
  9.     }  
  10. }   

Output


Runtime Exception - System.NullReferenceException

For delegates when executed, a compile time check is not performed, but a run time check is performed. Since delegate myDelegate was not initialized, so it was null, therefore it was obvious to get a null reference exception.

Lab10 
  1. using System;  
  2. namespace DelegatesAndEvents {  
  3.     class Program {  
  4.         public delegate void MyDelegate();  
  5.         public delegate void MyDelegate2();  
  6.         public static void Main() {  
  7.             Program programInstance = new Program();  
  8.             MyDelegate del = new MyDelegate(programInstance.Method1);  
  9.             del();  
  10.             MyDelegate2 del2 = new MyDelegate2(del);  
  11.             del2();  
  12.             System.Type typeDel = typeof(MyDelegate2);  
  13.             System.Console.WriteLine(typeDel.FullName);  
  14.             if (del2 is MyDelegate) System.Console.WriteLine("true");  
  15.             Console.ReadLine();  
  16.         }  
  17.         public void Method1() {  
  18.             System.Console.WriteLine("Method1");  
  19.         }  
  20.     }  
  21. }   

Output


We see here that a constructor of a delegate can not only take the method name as a parameter, but also can take the name of any other delegate as a parameter. Delegate del2 of type MyDelegate2 is initialized to del of type MyDelegate1, this basically creates the copy of the prior delegate. Therefore if we see, we now have to objects of delegates pointing to a single method named Method1. But instead of assigning MyDelegate2 to type of MyDelegate1, the  data type of MyDelegate2 remains unaffected and it is not changed and remains to datatype of MyDelegate2 only.

Lab11 
  1. using System;  
  2. namespace DelegatesAndEvents {  
  3.     class Program: EventExercises {  
  4.         public delegate void MyDelegate();  
  5.         public static void Main() {  
  6.             Program programInstance = new Program();  
  7.             MyDelegate del = new MyDelegate(programInstance.Method1);  
  8.             del();  
  9.             Console.ReadLine();  
  10.         }  
  11.     }  
  12.     class EventExercises {  
  13.         public void Method1() {  
  14.             System.Console.WriteLine("Method1");  
  15.         }  
  16.     }  
  17. }   

Output


The example is just to show that the method that is in base class could also be passed to a delegate as a parameter without any error.

Lab12 
  1. using System;  
  2. namespace DelegatesAndEvents {  
  3.     class Program: EventExercises {  
  4.         public delegate void MyDelegate();  
  5.         public void PQR() {  
  6.             MyDelegate del = new MyDelegate(base.Method1);  
  7.             del();  
  8.         }  
  9.         public static void Main() {  
  10.             Program programInstance = new Program();  
  11.             programInstance.PQR();  
  12.             Console.ReadLine();  
  13.         }  
  14.         public void Method1() {  
  15.             System.Console.WriteLine("Method1 from Program");  
  16.         }  
  17.     }  
  18.     class EventExercises {  
  19.         public void Method1() {  
  20.             System.Console.WriteLine("Method1 from EventExercises");  
  21.         }  
  22.     }  
  23. }   

Output


We can also use the keyword base to call the function from the base class. If you remember, base calls code from the base class and not from the derived class. This is in spite of what the documentation says and we quote verbatim 'If the method group resulted from a base-access, an error occurs'. There is no way known to man that can change the method associated with a delegate once the delegate has been created. It remains the same for the entire lifetime of the delegate. The parameter to a delegate creation cannot be a constructor, indexer, property or obviously a user define operator even though they carry code. We are left with only one choice as a parameter, a method.

Lab13 
  1. using System;  
  2. namespace DelegatesAndEvents {  
  3.     class Program {  
  4.         public delegate void MyDelegate();  
  5.         public static void Main() {  
  6.             MyDelegate d = new MyDelegate(Program);  
  7.         }  
  8.     }  
  9. }   

Output

Compile time error

'Program' is a type, which is not valid in the given context

This means that delegates cannot hold constructors as a parameter J

Some more interesting stuff

Lab14 Event Properties 
  1. namespace DelegatesAndEvents {  
  2.     public delegate void MyDelegate();  
  3.     public class Program {  
  4.         public event MyDelegate delegate1 {  
  5.             add {  
  6.                 return null;  
  7.             }  
  8.         }  
  9.     }  
  10. }   

Output

Compile time error

'Program.delegate1': event property must have both add and remove accessors Since 'Program.delegate1.add' returns void, a return keyword must not be followed by an object expression Like normal properties in C#, if we are creating the event property , we should keep in mind that it should have both add and remove accessors.

Lab15 Event Data Type 
  1. namespace DelegatesAndEvents {  
  2.     public class MyDelegate {}  
  3.     public class Program {  
  4.         public event MyDelegate delegate1 {  
  5.             add {  
  6.                 return null;  
  7.             }  
  8.             remove {}  
  9.         }  
  10.     }  
  11. }   

Output

Compile time error

'Program.d1': event must be of a delegate type Since 'Program.delegate.add' returns void, a return keyword must not be followed by an object expression This error means that event should always be the data type of a delegate and not of user defined.

Lab16 
  1. namespace DelegatesAndEvents {  
  2.     delegate void MyDelegate();  
  3.     interface MyInterface {  
  4.         event MyDelegate del = new MyDelegate();  
  5.     }  
  6. }   

Output

Compile time error

'MyInterface.del': event in interface cannot have initializer

'MyDelegate' does not contain a constructor that takes 0 arguments

So this again proves that interfaces can only contain definitions and not implementation code.

Lab16 Accessors in Interfaces? 
  1. namespace DelegatesAndEvents {  
  2.     public delegate void MyDelegate();  
  3.     public interface IMyInterface {  
  4.         event MyDelegate del {  
  5.             remove {}  
  6.             add {  
  7.                 return null;  
  8.             }  
  9.         }  
  10.     }  
  11. }   

Output

Compile time error,

  • Error   CS0069 An event in an interface cannot have add or remove accessors
  • Error   CS0069 An event in an interface cannot have add or remove accessors

The above example demonstrates that in an interface one cannot have accessor code.

Lab17 
  1. namespace DelegatesAndEvents {  
  2.     public delegate void MyDelegate();  
  3.     public class Program {  
  4.         public event MyDelegate del1;  
  5.         public static void Main() {}  
  6.     }  
  7. }   

Output

Compile time warning

  • warning CS0067: The event 'Program. del1 is never used

So our code compiles here, but shows a warning. Compiler gives us a warning when an event is declared but not used.

Lab18 Event in Interface 
  1. namespace DelegatesAndEvents {  
  2.     public delegate void MyDelegate();  
  3.     interface IMyInterface {  
  4.         event MyDelegate myDelegate;  
  5.     }  
  6.     class Program: IMyInterface {  
  7.         event MyDelegate IMyInterface.myDelegate() {}  
  8.     }  
  9. }   

Output

Compile time error

  • CS0071 An explicit interface implementation of an event must use event accessor syntax
  • CS1520 Method must have a return type
  • CS0535 'Program' does not implement interface member 'IMyInterface.myDelegate'
  • CS0539 'Program.' in explicit interface declaration is not a member of interface
  • CS0065 'Program.': event property must have both add and remove accessors

We got a lot of errors in above code.

The error says that we need to use the syntax of the property i.e. get and set, when one try to implement an event that is declared in the interface. Events and interfaces share this strange bonding J.

Lab19 
  1. namespace DelegatesAndEvents {  
  2.     delegate void MyDelegate(int i);  
  3.     class Program {  
  4.         public static void Main() {  
  5.             MyDelegate myDelegate = new MyDelegate(500);  
  6.         }  
  7.     }  
  8. }  

Output

Compile time error

  • CS0149 Method name expected

Like we mentioned delegates are like method pointers, which could only point to methods. So while creating delegate object, it requires the name of the method.In the above code, it gives the compiler error because we pass number instead of a method name.

Lab20 
  1. namespace DelegatesAndEvents {  
  2.     public delegate void MyDelegate();  
  3.     class Program {  
  4.         MyDelegate myDelegate;  
  5.         public static void Main() {  
  6.             Program programInstance = new Program();  
  7.             programInstance.myDelegate.Invoke();  
  8.         }  
  9.     }  
  10. }   

Output



We get a run time error.

So, there is only one defined way to use delegates as explained, and we cannot use Invoke method to directly call the delegate

Conclusion

This article covered the topic of Events and some parts of delegates in detail. Events are very crucial and fun to understand but are tricky to implement. I hope this post helped the readers to get an insight of events and delegates.



Read more

For more technical articles you can reach out to CodeTeddy

My other series of articles,

Happy coding !


Similar Articles