In C#, an object or a class can be used to inform other objects or classes when something happens, which is known as an event. There are two kinds of classes in the event, they are publishers and subscribers. The publisher is a class or object that sends (or raises) the event, while the subscriber is a class or object that receives (or handles) the event. Fortunately, lambda expressions can also be used to handle events. Let's take a look at the following code to discuss events further:
public class EventClassWithoutEvent { public Action OnChange { get; set; } public void Raise() { if (OnChange != null) { OnChange(); } } }
The preceding code can be found in the EventsInLambda.csproj
project. As we can see, a class named EventClassWithoutEvent
has been created in the project. The class has a property named OnChange
. This property's role is to store the action that subscribes the class and will be run when the Raise()
method is invoked. Now, let's consume the Raise()
method using the following code:
public partial class Program { private static void CreateAndRaiseEvent() { EventClassWithoutEvent ev = new EventClassWithoutEvent(); ev.OnChange += () => Console.WriteLine("1st: Event raised"); ev.OnChange += () => Console.WriteLine("2nd: Event raised"); ev.OnChange += () => Console.WriteLine("3rd: Event raised"); ev.OnChange += () => Console.WriteLine("4th: Event raised"); ev.OnChange += () => Console.WriteLine("5th: Event raised"); ev.Raise(); } }
If we run the preceding CreateAndRaiseEvent()
method, we will retrieve the following output on the console:
From the code, we can see that when we invoke the CreateAndRaiseEvent()
method, the code instances an EventClassWithoutEvent
class. It then subscribes to the event with five different methods inside the lambda expression and then raises the event by invoking the Raise()
method. The following code snippet will explain this further:
EventClassWithoutEvent ev = new EventClassWithoutEvent(); ev.OnChange += () => Console.WriteLine("1st: Event raised"); ev.Raise();
From the preceding code snippet, we can see that the lambda expression can be used to subscribe to the event since it uses a delegate to store the subscribed method. However, there is still a weakness in the preceding code. Take a look at the last OnChange
assignment from this code:
ev.OnChange += () => Console.WriteLine("5th: Event raised");
Now, suppose that we change it to this:
ev.OnChange = () => Console.WriteLine("5th: Event raised");
Then, we will remove all four previous subscribers. Another weakness is that EventClassWithoutEvent
raises the event but nothing can stop the users of the class from raising this event. By invoking OnChange()
, all users of the class can raise the event to all subscribers.
The use of the event
keyword can solve our preceding problem since it will enforce the users of the class to subscribe something only using either the +=
or -=
operator. Let's take a look at the following code to explain this further:
public class EventClassWithEvent { public event Action OnChange = () => { }; public void Raise() { OnChange(); } }
From the preceding code, we can see that we are no longer using a public property but a public field in the EventClassWithEvent
class. Using the event
keyword, the compiler will secure our field from unwanted access. The event keyword will also protect the subscription list since it cannot be assigned to any lambda expression using the =
operator but has to be used with the +=
or -=
operator. Now, let's take a look at the following code to prove this:
public partial class Program { private static void CreateAndRaiseEvent2() { EventClassWithEvent ev = new EventClassWithEvent(); ev.OnChange += () => Console.WriteLine("1st: Event raised"); ev.OnChange += () => Console.WriteLine("2nd: Event raised"); ev.OnChange += () => Console.WriteLine("3rd: Event raised"); ev.OnChange += () => Console.WriteLine("4th: Event raised"); ev.OnChange = () => Console.WriteLine("5th: Event raised"); ev.Raise(); } }
We now have a method named CreateAndRaiseEvent2()
, which is exactly same as the CreateAndRaiseEvent()
method except that the last OnChange
assignment used the =
operator instead of the +=
operator. However, since we have applied the event keyword to the OnChange
field, the code cannot be compiled and the CS0070
error code will occur, as shown in the following screenshot:
There is no risk anymore since the event keyword has restricted the use of the =
operator. The event
keyword also prevents the outside user of the class from raising the event. Only the part of the class that defines the event can raise the event. Let's take a look at the difference between the EventClassWithoutEvent
and EventClassWithEvent
class:
public partial class Program { private static void CreateAndRaiseEvent3() { EventClassWithoutEvent ev = new EventClassWithoutEvent(); ev.OnChange += () => Console.WriteLine("1st: Event raised"); ev.OnChange += () => Console.WriteLine("2nd: Event raised"); ev.OnChange += () => Console.WriteLine("3rd: Event raised"); ev.OnChange(); ev.OnChange += () => Console.WriteLine("4th: Event raised"); ev.OnChange += () => Console.WriteLine("5th: Event raised"); ev.Raise(); } }
The reference of the preceding CreateAndRaiseEvent3()
method is CreateAndRaiseEvent()
, but we insert ev.OnChange()
; in between the third event and fourth event. If we run the method, it will run successfully, and we will see the following output on the console:
As we can see from the output, OnChange()
in the EventClassWithoutEvent
class can raise the event. Compared to the EventClassWithEvent
class, if we insert OnChange()
between any subscribing event, the compiler will create a compile error, as shown in the following code:
public partial class Program { private static void CreateAndRaiseEvent4() { EventClassWithEvent ev = new EventClassWithEvent(); ev.OnChange += () => Console.WriteLine("1st: Event raised"); ev.OnChange += () => Console.WriteLine("2nd: Event raised"); ev.OnChange += () => Console.WriteLine("3rd: Event raised"); ev.OnChange(); ev.OnChange += () => Console.WriteLine("4th: Event raised"); ev.OnChange += () => Console.WriteLine("5th: Event raised"); ev.Raise(); } }
If we compile the preceding code, we will get the CS0070
error code again, since we insert ev.OnChange()
; in between the third event and the fourth event.
Actually, C# has a class named EventHandler
or EventHandler<T>
that we can use to initialize an event instead of using an Action
class. An EventHandler
class takes a sender object and event arguments. The sender is the object that raises the event. Using EventHandler<T>
, we can define the type of event arguments. Let's take a look at the following code, which we can find in the EventWithEventHandler.csproj
project:
public class MyArgs : EventArgs { public int Value { get; set; } public MyArgs(int value) { Value = value; } } public class EventClassWithEventHandler { public event EventHandler<MyArgs> OnChange = (sender, e) => { }; public void Raise() { OnChange(this, new MyArgs(100)); } }
We have two classes, named MyArgs
and EventClassWithEventHandler
. The EventClassWithEventHandler
class uses EventHandler<MyArgs>
, which defines the event argument's type. We need to pass an instance of MyArgs
when raising the event. Subscribers of the event can access the arguments and use them. Now, let's take a look at the following CreateAndRaiseEvent()
method code:
public partial class Program { private static void CreateAndRaiseEvent() { EventClassWithEventHandler ev = new EventClassWithEventHandler(); ev.OnChange += (sender, e) => Console.WriteLine( "Event raised with args: {0}", e.Value); ev.Raise(); } }
If we run the preceding code, we will get the following output on the console:
From the preceding code, we can see that the lambda expression plays its role to subscribe to an event, as follows:
ev.OnChange += (sender, e) => Console.WriteLine( "Event raised with args: {0}", e.Value);