What are events in C #?
An event can be used to provide notifications. You can subscribe to the event if you need these notifications. You can also create your own events that will notify you that something that interests you has happened. The .NET Framework offers built-in types that you can use to create events. Using delegates, lambda expressions, and anonymous methods, you can create and use events in a convenient way.
Understanding Delegates in C #
In C #, delegates form the basic building blocks for events. A delegate is a type that defines the signature of a method. For example, in C ++, this can be done using a function pointer. In C #, you can create an instance of a delegate that points to another method. You can call this method through a delegate instance.
The following is an example of declaring a delegate and calling a method through it.
Using a delegate in C #
}class Program { public delegate double MathDelegate(double value1, double value2); public static double Add(double value1, double value2) { return value1 + value2; } public static double Subtract(double value1, double value2) { return value1 - value2; } Console.ReadLine(); public static void Main() { MathDelegate mathDelegate = Add; var result = mathDelegate(5, 2); Console.WriteLine(result); // : 7 mathDelegate = Subtract; result = mathDelegate(5, 2); Console.WriteLine(result); // : 3 }
As you can see, we use the delegate keyword to tell the compiler that we are creating a delegate type.
Setting up delegates is easy with the automatic creation of a new delegate type.
You can also use the new keyword to create a delegate.
MathDelegate mathDelegate = new MathDelegate(Add)
;
An instantiated delegate is an object; You can also use it and pass as an argument to other methods.
Multicast Delegates in C #
Another great feature of delegates is that you can combine them together. This is called multicasting. You can use the + or + = operator to add another method to the call list of an existing delegate instance. Similarly, you can also remove a method from the call list using the decrement assignment operator (- or - =). This feature serves as the basis for events in C #. The following is an example of a multicast delegate.
class Program { static void Hello(string s) { Console.WriteLine(" Hello, {0}!", s); } static void Goodbye(string s) { Console.WriteLine(" Goodbye, {0}!", s); } delegate void Del(string s); static void Main() { Del a, b, c, d; // a Hello: a = Hello; // b Goodbye: b = Goodbye; // a b - c: c = a + b; // a c, d, Goodbye: d = c - a; Console.WriteLine("Invoking delegate a:"); a("A"); Console.WriteLine("Invoking delegate b:"); b("B"); Console.WriteLine("Invoking delegate c:"); c("C"); Console.WriteLine("Invoking delegate d:"); d("D"); /* : Invoking delegate a: Hello, A! Invoking delegate b: Goodbye, B! Invoking delegate c: Hello, C! Goodbye, C! Invoking delegate d: Goodbye, D! */ Console.ReadLine(); } }
This is possible because delegates inherit from the
System.MulticastDelegate
class, which in turn inherits from
System.Delegate
. You can use the members defined in these base classes for your delegates.
For example, to find out how many methods a multicast delegate will call, you can use the following code:
int invocationCount = d.GetInvocationList().GetLength(0);
Covariance and contravariance in C #
When you assign a method to a delegate, the method signature does not have to exactly match the delegate. This is called covariance and contravariance. Covariance allows a method to have a more derived return type than the one defined in the delegate. Contravariance allows a method with parameter types that are less derived than types in the delegate.
Delegate Covariance
Here is an example of covariance,
class Program { public delegate TextWriter CovarianceDel(); public static StreamWriter MethodStream() { return null; } public static StringWriter MethodString() { return null; } static void Main() { CovarianceDel del; del = MethodStream; del = MethodString; Console.ReadLine(); } }
Since both
StreamWriter
and
StringWriter
inherit from
TextWriter
, you can use
CovarianceDel
with both methods.
Contravariance in delegates
The following is an example of contravariance.
class Program { public static void DoSomething(TextWriter textWriter) { } public delegate void ContravarianceDel(StreamWriter streamWriter); static void Main() { ContravarianceDel del = DoSomething; Console.ReadLine(); } }
Since the
DoSomething
method can work with
TextWriter
, it certainly can work with
StreamWriter
. Due to contravariance, you can call a delegate and pass an instance of
StreamWriter
to the
DoSomething
method.
You can learn more about this concept here .
Lambda expressions in C #
Sometimes an entire method signature may require more code than the body of the method itself. There are also situations in which you need to create an entire method just to use it as a delegate.
For these cases, Microsoft has added some new features in C #, for example, anonymous methods in 2.0. In C # 3.0, things got even better when lambda expressions were added. Lambda expression is the preferred way when writing new code.
The following is an example of the latest lambda syntax.
class Program { public delegate double MathDelegate(double value1, double value2); public static void Main() { MathDelegate mathDelegate = (x,y) => x + y; var result = mathDelegate(5, 2); Console.WriteLine(result); // : 7 mathDelegate = (x, y) => x - y; ; result = mathDelegate(5, 2); Console.WriteLine(result); // : 3 Console.ReadLine(); } }
To read this code, you need to use the word “follows” in the context of the special lambda syntax. For example, the first lambda expression in the above example reads “x and y follow the addition of x and y”.
A lambda function does not have a specific name, unlike a method. Because of this, lambdas are called anonymous functions. You also do not need to explicitly specify the type of the return value. The compiler assumes it automatically from your lambda. And in the case of the above example, the parameter types x and y are also not explicitly specified.
You can create lambdas that span multiple operators. You can do this by adding curly braces around the operators that make up the lambda, as shown in the example below.
MathDelegate mathDelegate = (x,y) => { Console.WriteLine("Add"); return x + y; };
Sometimes a delegate announcement for an event seems a bit cumbersome. Because of this, the .NET Framework has several built-in delegate types that you can use when declaring delegates. In the MathDelegate example, you used the following delegate:
public delegate double MathDelegate(double value1, double value2);
You can replace this delegate with one of the built-in types, namely
Func <int, int, int>
.
like this,
class Program { public static void Main() { Func<int, int, int> mathDelegate = (x,y) => { Console.WriteLine("Add"); return x + y; }; var result = mathDelegate(5, 2); Console.WriteLine(result); // : 7 mathDelegate = (x, y) => x - y; ; result = mathDelegate(5, 2); Console.WriteLine(result); // : 3 Console.ReadLine(); } }
Func <...> types can be found in the System namespace. They represent delegates that return a type and take from 0 to 16 parameters. All of these types are inherited from System.MulticaseDelegate so that you can add multiple methods to your call list.
If you need a delegate type that does not return a value, you can use the System.Action types. They can also take from 0 to 16 parameters, but do not return a value.
Here is an example of using the Action type,
class Program { public static void Main() { Action<int, int> mathDelegate = (x,y) => { Console.WriteLine(x + y); }; mathDelegate(5, 2); // : 7 mathDelegate = (x, y) => Console.WriteLine(x - y) ; mathDelegate(5, 2); // : 3 Console.ReadLine(); } }
You can learn more about .NET built-in delegates here .
Things get complicated when your lambda function begins to refer to variables declared outside of the lambda expression or to this. Typically, when a control leaves the scope of a variable, the variable becomes invalid. But what if the delegate refers to a local variable. To fix this, the compiler generates code that extends the life of the captured variable, at least as long as the longest-lived delegate lives. This is called a closure.
You can learn more about closures here .
Events in C #
Consider a popular development pattern - publisher-subscriber (pub / sub). You can subscribe to an event, and then you will be notified when the event publisher initiates a new event. This system is used to establish weak communication between components in an application.
The delegate forms the basis for the event system in C #.
An event is a special type of delegate that facilitates event-oriented programming. Events are members of a class that cannot be called outside the class, regardless of the access specifier. So, for example, an event declared as public would allow other classes to use + = and - = for this event, but triggering an event (that is, calling a delegate) is allowed only in the class containing the event. Let's look at an example,
// - Pub public class Pub { // OnChange callback- public event Action OnChange = delegate { }; public void Raise() { // OnChange OnChange(); } }
Then, a method in another class can subscribe to the event by adding one of its methods to the event delegate:
The following is an example showing how a class can provide an open delegate and generate an event.
class Program { static void Main(string[] args) { // pub Pub p = new Pub(); // Subscriber 1 OnChange p.OnChange += () => Console.WriteLine("Subscriber 1!"); // Subscriber 2 OnChange p.OnChange += () => Console.WriteLine("Subscriber 2!"); // p.Raise(); // Raise() callback- Console.WriteLine("Press enter to terminate!"); Console.ReadLine(); } }
Even if the event is declared as
public
, it cannot be triggered directly anywhere except in the class in which it is located.
Using the
event
keyword, the compiler protects our field from unwanted access.
As well as,
It does not allow the use of = (direct delegate assignment). Therefore, your code is now protected from the risk of deleting previous subscribers by using = instead of + =.
In addition, you might notice the special syntax for initializing the OnChange field for an empty delegate, such as
delegate { }
. This ensures that our OnChange field is never null. Therefore, we can remove the null check before invoking the event if there are no other class members making it null.
When you run the above program, your code creates a new instance of Pub, subscribes to the event in two different ways, and generates an event by calling p.Raise. The Pub class is completely unaware of any of the subscribers. It just generates an event.
You can also read my article, C # Publisher-Subscriber Design Pattern, for a deeper understanding of this concept.
Well, that's all for now. I hope you get the idea. Thanks for reading the post. Please let me know if there are any errors or changes are needed in the comments below. Thank you in advance!
useful links
www.c-sharpcorner.com/blogs/c-sharp-generic-delegates-func-action-and-predicate
docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/covariance-contravariance
https://web.archive.org/web/20150707082707/http://diditwith.net/PermaLink,guid,235646ae-3476-4893-899d-105e4d48c25b.aspx