© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2021
A. Troelsen, P. JapiksePro C# 9 with .NET 5https://doi.org/10.1007/978-1-4842-6939-8_12

12. Delegates, Events, and Lambda Expressions

Andrew Troelsen1   and Phillip Japikse2
(1)
Minneapolis, MN, USA
(2)
West Chester, OH, USA
 

Up to this point in the text, most of the applications you have developed added various bits of code to Program.cs as top-level statements, which, in some way or another, sent requests to a given object. However, many applications require that an object be able to communicate back to the entity that created it using a callback mechanism. While callback mechanisms can be used in any application, they are especially critical for graphical user interfaces in that controls (such as a button) need to invoke external methods under the correct circumstances (when the button is clicked, when the mouse enters the button surface, etc.).

Under the .NET Core platform, the delegate type is the preferred means of defining and responding to callbacks within applications. Essentially, the .NET Core delegate type is a type-safe object that “points to” a method or a list of methods that can be invoked later. Unlike a traditional C++ function pointer, however, delegates are classes that have built-in support for multicasting.

Note

In prior versions of .NET, delegates exposed asynchronous method invocation with BeginInvoke()/EndInvoke(). While these are still generated by the compiler, they are not supported under .NET Core. This is because the IAsyncResult()/BeginInvoke() pattern used by delegates has been replaced by the task-based async pattern. For more on asynchronous execution, see Chapter 15.

In this chapter, you will learn how to create and manipulate delegate types, and then you’ll investigate the C# event keyword, which streamlines the process of working with delegate types. Along the way, you will also examine several delegate-centric and event-centric language features of C#, including anonymous methods and method group conversions.

I wrap up this chapter by examining lambda expressions. Using the C# lambda operator (=>), you can specify a block of code statements (and the parameters to pass to those code statements) wherever a strongly typed delegate is required. As you will see, a lambda expression is little more than an anonymous method in disguise and provides a simplified approach to working with delegates. In addition, this same operation (as of .NET Framework 4.6 and later) can be used to implement a single-statement method or property using a concise syntax.

Understanding the Delegate Type

Before formally defining delegates, let’s gain a bit of perspective. Historically, the Windows API made frequent use of C-style function pointers to create entities termed callback functions , or simply callbacks. Using callbacks, programmers were able to configure one function to report back to (call back) another function in the application. With this approach, Windows developers were able to handle button clicking, mouse moving, menu selecting, and general bidirectional communications between two entities in memory.

In the .NET and .NET Core Frameworks, callbacks are accomplished in a type-safe and object-oriented manner using delegates. A delegate is a type-safe object that points to another method (or possibly a list of methods) in the application, which can be invoked later. Specifically, a delegate maintains three important pieces of information.
  • The address of the method on which it makes calls

  • The parameters (if any) of this method

  • The return type (if any) of this method

Note

.NET Core delegates can point to either static or instance methods.

After a delegate object has been created and given the necessary information, it may dynamically invoke the method(s) it points to at runtime.

Defining a Delegate Type in C#

When you want to create a delegate type in C#, you use the delegate keyword. The name of your delegate type can be whatever you desire. However, you must define the delegate to match the signature of the method(s) it will point to. For example, the following delegate type (named BinaryOp) can point to any method that returns an integer and takes two integers as input parameters (you will build and use this delegate yourself a bit later in this chapter, so hang tight for now):
// This delegate can point to any method,
// taking two integers and returning an integer.
public delegate int BinaryOp(int x, int y);
When the C# compiler processes delegate types, it automatically generates a sealed class deriving from System.MulticastDelegate. This class (in conjunction with its base class, System.Delegate) provides the necessary infrastructure for the delegate to hold onto a list of methods to be invoked later. For example, if you were to examine the BinaryOp delegate using ildasm.exe, you would find the details as shown here (you will build this full example in just a moment if you want to check for yourself):
//     -------------------------------------------------------
//     TypDefName: SimpleDelegate.BinaryOp
//     Extends   : System.MulticastDelegate
//     Method #1
//     -------------------------------------------------------
//             MethodName: .ctor
//             ReturnType: Void
//             2 Arguments
//                     Argument #1:  Object
//                     Argument #2:  I
//     Method #2
//     -------------------------------------------------------
//             MethodName: Invoke
//             ReturnType: I4
//             2 Arguments
//                     Argument #1:  I4
//                     Argument #2:  I4
//             2 Parameters
//                     (1) ParamToken : Name : x flags: [none]
//                     (2) ParamToken : Name : y flags: [none] //
//     Method #3
//     -------------------------------------------------------
//             MethodName: BeginInvoke
//             ReturnType: Class System.IAsyncResult
//             4 Arguments
//                     Argument #1:  I4
//                     Argument #2:  I4
//                     Argument #3:  Class System.AsyncCallback
//                     Argument #4:  Object
//             4 Parameters
//                     (1) ParamToken : Name : x flags: [none]
//                     (2) ParamToken : Name : y flags: [none]
//                     (3) ParamToken : Name : callback flags: [none]
//                     (4) ParamToken : Name : object flags: [none]
//
//     Method #4
//     -------------------------------------------------------
//             MethodName: EndInvoke
//             ReturnType: I4 (int32)
//             1 Arguments
//                     Argument #1:  Class System.IAsyncResult
//             1 Parameters
//                     (1) ParamToken : Name : result flags: [none]

As you can see, the compiler-generated BinaryOp class defines three public methods. Invoke() is the key method in .NET Core, as it is used to invoke each method maintained by the delegate object in a synchronous manner, meaning the caller must wait for the call to complete before continuing its way. Strangely enough, the synchronous Invoke() method may not need to be called explicitly from your C# code. As you will see in just a bit, Invoke() is called behind the scenes when you use the appropriate C# syntax.

Note

While BeginInvoke() and EndInvoke() are generated, they are not supported when running your code under .NET Core. This can be frustrating, since you will not receive a compiler error but a runtime error if you use them.

Now, how exactly does the compiler know how to define the Invoke() method? To understand the process, here is the crux of the compiler-generated BinaryOp class type (bold italic marks the items specified by the defined delegate type):
sealed class BinaryOp : System.MulticastDelegate
{
  public int Invoke(int x, int y);
...
}

First, notice that the parameters and return type defined for the Invoke() method exactly match the definition of the BinaryOp delegate.

Let’s see another example. Assume you have defined a delegate type that can point to any method returning a string and receiving three System.Boolean input parameters.
public delegate string MyDelegate (bool a, bool b, bool c);
This time, the compiler-generated class breaks down as follows:
sealed class MyDelegate : System.MulticastDelegate
{
  public string Invoke(bool a, bool b, bool c);
...
}
Delegates can also “point to” methods that contain any number of out or ref parameters (as well as array parameters marked with the params keyword). For example, assume the following delegate type:
public delegate string MyOtherDelegate(
  out bool a, ref bool b, int c);

The signature of the Invoke() method looks as you would expect.

To summarize, a C# delegate type definition results in a sealed class with a compiler-generated method whose parameter and return types are based on the delegate’s declaration. The following pseudocode approximates the basic pattern:
// This is only pseudo-code!
public sealed class DelegateName : System.MulticastDelegate
{
  public delegateReturnValue Invoke(allDelegateInputRefAndOutParams);
}

The System.MulticastDelegate and System.Delegate Base Classes

So, when you build a type using the C# delegate keyword, you are indirectly declaring a class type that derives from System.MulticastDelegate . This class provides descendants with access to a list that contains the addresses of the methods maintained by the delegate object, as well as several additional methods (and a few overloaded operators) to interact with the invocation list. Here are some select members of System.MulticastDelegate:
public abstract class MulticastDelegate : Delegate
{
  // Returns the list of methods "pointed to."
  public sealed override Delegate[] GetInvocationList();
  // Overloaded operators.
  public static bool operator ==
    (MulticastDelegate d1, MulticastDelegate d2);
  public static bool operator !=
    (MulticastDelegate d1, MulticastDelegate d2);
  // Used internally to manage the list of methods maintained by the delegate.
  private IntPtr _invocationCount;
  private object _invocationList;
}
System.MulticastDelegate obtains additional functionality from its parent class, System.Delegate. Here is a partial snapshot of the class definition:
public abstract class Delegate : ICloneable, ISerializable
{
  // Methods to interact with the list of functions.
  public static Delegate Combine(params Delegate[] delegates);
  public static Delegate Combine(Delegate a, Delegate b);
  public static Delegate Remove(
    Delegate source, Delegate value);
  public static Delegate RemoveAll(
    Delegate source, Delegate value);
  // Overloaded operators.
  public static bool operator ==(Delegate d1, Delegate d2);
  public static bool operator !=(Delegate d1, Delegate d2);
  // Properties that expose the delegate target.
  public MethodInfo Method { get; }
  public object Target { get; }
}
Now, understand that you can never directly derive from these base classes in your code (it is a compiler error to do so). Nevertheless, when you use the delegate keyword, you have indirectly created a class that “is-a” MulticastDelegate. Table 12-1 documents the core members common to all delegate types.
Table 12-1.

Select Members of System.MulticastDelegate/System.Delegate

Member

Meaning in Life

Method

This property returns a System.Reflection.MethodInfo object that represents details of a static method maintained by the delegate.

Target

If the method to be called is defined at the object level (rather than a static method), Target returns an object that represents the method maintained by the delegate. If the value returned from Target equals null, the method to be called is a static member.

Combine()

This static method adds a method to the list maintained by the delegate. In C#, you trigger this method using the overloaded += operator as a shorthand notation.

GetInvocationList()

This method returns an array of System.Delegate objects, each representing a method that may be invoked.

Remove() /

RemoveAll()

These static methods remove a method (or all methods) from the delegate’s invocation list. In C#, the Remove() method can be called indirectly using the overloaded -= operator.

The Simplest Possible Delegate Example

To be sure, delegates can cause some confusion when encountered for the first time. Thus, to get the ball rolling, let’s look at a simple console application program (named SimpleDelegate) that uses the BinaryOp delegate type you’ve seen previously. Here is the complete code, with analysis to follow:
//SimpleMath.cs
namespace SimpleDelegate
{
  // This class contains methods BinaryOp will
  // point to.
  public class SimpleMath
  {
    public static int Add(int x, int y) => x + y;
    public static int Subtract(int x, int y) => x - y;
  }
}
//Program.cs
using System;
using SimpleDelegate;
Console.WriteLine("***** Simple Delegate Example ***** ");
// Create a BinaryOp delegate object that
// "points to" SimpleMath.Add().
BinaryOp b = new BinaryOp(SimpleMath.Add);
// Invoke Add() method indirectly using delegate object.
Console.WriteLine("10 + 10 is {0}", b(10, 10));
Console.ReadLine();
//Additional type definitions must be placed at the end of the
// top-level statements
// This delegate can point to any method,
// taking two integers and returning an integer.
public delegate int BinaryOp(int x, int y);
Note

Recall from Chapter 3 that additional type declarations (in this example the BinaryOp delegate) must come after all top-level statements.

Again, notice the format of the BinaryOp delegate type declaration; it specifies that BinaryOp delegate objects can point to any method taking two integers and returning an integer (the actual name of the method pointed to is irrelevant). Here, you have created a class named SimpleMath, which defines two static methods that match the pattern defined by the BinaryOp delegate.

When you want to assign the target method to a given delegate object, simply pass in the name of the method to the delegate’s constructor.
// Create a BinaryOp delegate object that
// "points to" SimpleMath.Add().
BinaryOp b = new BinaryOp(SimpleMath.Add);
At this point, you can invoke the member pointed to using a syntax that looks like a direct function invocation.
// Invoke() is really called here!
Console.WriteLine("10 + 10 is {0}", b(10, 10));
Under the hood, the runtime calls the compiler-generated Invoke() method on your MulticastDelegate-derived class. You can verify this for yourself if you open your assembly in ildasm.exe and examine the CIL code within the Main() method.
.method private hidebysig static void Main(string[] args) cil managed
{
...
  callvirt   instance int32 BinaryOp::Invoke(int32, int32)
}
C# does not require you to explicitly call Invoke() within your code base. Because BinaryOp can point to methods that take two arguments, the following code statement is also permissible:
Console.WriteLine("10 + 10 is {0}", b.Invoke(10, 10));
Recall that .NET Core delegates are type-safe. Therefore, if you attempt to create a delegate object pointing to a method that does not match the pattern, you receive a compile-time error. To illustrate, assume the SimpleMath class now defines an additional method named SquareNumber() , which takes a single integer as input.
public class SimpleMath
{
  public static int SquareNumber(int a) => a * a;
}
Given that the BinaryOp delegate can point only to methods that take two integers and return an integer, the following code is illegal and will not compile:
// Compiler error! Method does not match delegate pattern!
BinaryOp b2 = new BinaryOp(SimpleMath.SquareNumber);

Investigating a Delegate Object

Let’s spice up the current example by creating a static method (named DisplayDelegateInfo()) within the Program class. This method will print out the names of the methods maintained by a delegate object, as well as the name of the class defining the method. To do this, you will iterate over the System.Delegate array returned by GetInvocationList() , invoking each object’s Target and Method properties.
static void DisplayDelegateInfo(Delegate delObj)
{
  // Print the names of each member in the
  // delegate's invocation list.
  foreach (Delegate d in delObj.GetInvocationList())
  {
    Console.WriteLine("Method Name: {0}", d.Method);
    Console.WriteLine("Type Name: {0}", d.Target);
  }
}
Assuming you have updated your Main() method to call this new helper method, as shown here:
BinaryOp b = new BinaryOp(SimpleMath.Add);
DisplayDelegateInfo(b);
you would find the output shown next:
***** Simple Delegate Example *****
Method Name: Int32 Add(Int32, Int32)
Type Name:
10 + 10 is 20
Notice that the name of the target class (SimpleMath) is currently not displayed when calling the Target property. The reason has to do with the fact that your BinaryOp delegate is pointing to a static method and, therefore, there is no object to reference! However, if you update the Add() and Subtract() methods to be nonstatic (simply by deleting the static keywords ), you could create an instance of the SimpleMath class and specify the methods to invoke using the object reference.
using System;
using SimpleDelegate;
Console.WriteLine("***** Simple Delegate Example ***** ");
// Delegates can also point to instance methods as well.
SimpleMath m = new SimpleMath();
BinaryOp b = new BinaryOp(m.Add);
// Show information about this object.
DisplayDelegateInfo(b);
Console.WriteLine("10 + 10 is {0}", b(10, 10));
Console.ReadLine();
In this case, you would find the output shown here:
***** Simple Delegate Example *****
Method Name: Int32 Add(Int32, Int32)
Type Name: SimpleDelegate.SimpleMath
10 + 10 is 20

Sending Object State Notifications Using Delegates

Clearly, the previous SimpleDelegate example was intended to be purely illustrative in nature, given that there would be no compelling reason to define a delegate simply to add two numbers. To provide a more realistic use of delegate types, let’s use delegates to define a Car class that can inform external entities about its current engine state. To do so, you will take the following steps:
  1. 1.

    Define a new delegate type that will be used to send notifications to the caller.

     
  2. 2.

    Declare a member variable of this delegate in the Car class.

     
  3. 3.

    Create a helper function on the Car that allows the caller to specify the method to call back on.

     
  4. 4.

    Implement the Accelerate() method to invoke the delegate’s invocation list under the correct circumstances.

     
To begin, create a new Console Application project named CarDelegate. Now, define a new Car class that looks initially like this:
using System;
using System.Linq;
namespace CarDelegate
{
  public class Car
  {
    // Internal state data.
    public int CurrentSpeed { get; set; }
    public int MaxSpeed { get; set; } = 100;
    public string PetName { get; set; }
    // Is the car alive or dead?
    private bool _carIsDead;
    // Class constructors.
    public Car() {}
    public Car(string name, int maxSp, int currSp)
    {
      CurrentSpeed = currSp;
      MaxSpeed = maxSp;
      PetName = name;
    }
  }
}
Now, consider the following updates, which address the first three points:
public class Car
{
  ...
  // 1) Define a delegate type.
  public delegate void CarEngineHandler(string msgForCaller);
  // 2) Define a member variable of this delegate.
  private CarEngineHandler _listOfHandlers;
  // 3) Add registration function for the caller.
  public void RegisterWithCarEngine(CarEngineHandler methodToCall)
  {
    _listOfHandlers = methodToCall;
  }
}

Notice in this example that you define the delegate types directly within the scope of the Car class, which is certainly not necessary but does help enforce the idea that the delegate works naturally with this class. The delegate type, CarEngineHandler, can point to any method taking a single string as input and void as a return value.

Next, note that you declare a private member variable of your delegate type (named _listOfHandlers) and a helper function (named RegisterWithCarEngine()) that allows the caller to assign a method to the delegate’s invocation list.

Note

Strictly speaking, you could have defined your delegate member variable as public, therefore avoiding the need to create additional registration methods. However, by defining the delegate member variable as private, you are enforcing encapsulation services and providing a more type-safe solution. You’ll revisit the risk of public delegate member variables later in this chapter when you look at the C# event keyword.

At this point, you need to create the Accelerate() method. Recall, the point here is to allow a Car object to send engine-related messages to any subscribed listener. Here is the update:
// 4) Implement the Accelerate() method to invoke the delegate's
//    invocation list under the correct circumstances.
public void Accelerate(int delta)
{
  // If this car is "dead," send dead message.
  if (_carIsDead)
  {
    _listOfHandlers?.Invoke("Sorry, this car is dead...");
  }
  else
  {
    CurrentSpeed += delta;
    // Is this car "almost dead"?
    if (10 == (MaxSpeed - CurrentSpeed))
    {
      _listOfHandlers?.Invoke("Careful buddy! Gonna blow!");
    }
    if (CurrentSpeed >= MaxSpeed)
    {
      _carIsDead = true;
    }
    else
    {
      Console.WriteLine("CurrentSpeed = {0}", CurrentSpeed);
    }
  }
}
Notice that you are using the null propagation syntax when attempting to invoke the methods maintained by the listOfHandlers member variable. The reason is that it will be the job of the caller to allocate these objects by calling the RegisterWithCarEngine() helper method. If the caller does not call this method and you attempt to invoke the delegate’s invocation list, you will trigger a NullReferenceException at runtime. Now that you have the delegate infrastructure in place, observe the updates to the Program class, shown here:
using System;
using CarDelegate;
Console.WriteLine("** Delegates as event enablers ** ");
// First, make a Car object.
Car c1 = new Car("SlugBug", 100, 10);
// Now, tell the car which method to call
// when it wants to send us messages.
c1.RegisterWithCarEngine(
  new Car.CarEngineHandler(OnCarEngineEvent));
// Speed up (this will trigger the events).
Console.WriteLine("***** Speeding up *****");
for (int i = 0; i < 6; i++)
{
  c1.Accelerate(20);
}
Console.ReadLine();
// This is the target for incoming events.
static void OnCarEngineEvent(string msg)
{
  Console.WriteLine(" *** Message From Car Object ***");
  Console.WriteLine("=> {0}", msg);
  Console.WriteLine("******************** ");
}
The code begins by simply making a new Car object. Since you are interested in hearing about the engine events, the next step is to call your custom registration function, RegisterWithCarEngine(). Recall that this method expects to be passed an instance of the nested CarEngineHandler delegate, and as with any delegate, you specify a “method to point to” as a constructor parameter. The trick in this example is that the method in question is located back in the Program class! Again, notice that the OnCarEngineEvent() method is a dead-on match to the related delegate, in that it takes a string as input and returns void. Consider the output of the current example:
***** Delegates as event enablers *****
***** Speeding up *****
CurrentSpeed = 30
CurrentSpeed = 50
CurrentSpeed = 70
***** Message From Car Object *****
=> Careful buddy! Gonna blow!
***********************************
CurrentSpeed = 90
***** Message From Car Object *****
=> Sorry, this car is dead...
***********************************

Enabling Multicasting

Recall that .NET Core delegates have the built-in ability to multicast. In other words, a delegate object can maintain a list of methods to call, rather than just a single method. When you want to add multiple methods to a delegate object, you simply use the overloaded += operator, rather than a direct assignment. To enable multicasting on the Car class, you could update the RegisterWithCarEngine() method, like so:
public class Car
{
  // Now with multicasting support!
  // Note we are now using the += operator, not
  // the assignment operator (=).
  public void RegisterWithCarEngine(
    CarEngineHandler methodToCall)
  {
    _listOfHandlers += methodToCall;
  }
...
}
When you use the += operator on a delegate object, the compiler resolves this to a call on the static Delegate.Combine() method. In fact, you could call Delegate.Combine() directly; however, the += operator offers a simpler alternative. There is no need to modify your current RegisterWithCarEngine() method, but here is an example of using Delegate.Combine() rather than the += operator:
public void RegisterWithCarEngine( CarEngineHandler methodToCall )
{
  if (_listOfHandlers == null)
  {
    _listOfHandlers = methodToCall;
  }
  else
  {
    _listOfHandlers =
      Delegate.Combine(_listOfHandlers, methodToCall)
        as CarEngineHandler;
  }
}
In any case, the caller can now register multiple targets for the same callback notification. Here, the second handler prints the incoming message in uppercase, just for display purposes:
Console.WriteLine("***** Delegates as event enablers ***** ");
// First, make a Car object.
Car c1 = new Car("SlugBug", 100, 10);
// Register multiple targets for the notifications.
c1.RegisterWithCarEngine(
  new Car.CarEngineHandler(OnCarEngineEvent));
c1.RegisterWithCarEngine(
  new Car.CarEngineHandler(OnCarEngineEvent2));
// Speed up (this will trigger the events).
Console.WriteLine("***** Speeding up *****");
for (int i = 0; i < 6; i++)
{
  c1.Accelerate(20);
}
Console.ReadLine();
// We now have TWO methods that will be called by the Car
// when sending notifications.
static void OnCarEngineEvent(string msg)
{
  Console.WriteLine(" *** Message From Car Object ***");
  Console.WriteLine("=> {0}", msg);
  Console.WriteLine("********************************* ");
}
static void OnCarEngineEvent2(string msg)
{
  Console.WriteLine("=> {0}", msg.ToUpper());
}

Removing Targets from a Delegate’s Invocation List

The Delegate class also defines a static Remove() method that allows a caller to dynamically remove a method from a delegate object’s invocation list. This makes it simple to allow the caller to “unsubscribe” from a given notification at runtime. While you could call Delegate.Remove() directly in code, C# developers can use the -= operator as a convenient shorthand notation. Let’s add a new method to the Car class that allows a caller to remove a method from the invocation list.
public class Car
{
...
  public void UnRegisterWithCarEngine(CarEngineHandler methodToCall)
  {
    _listOfHandlers -= methodToCall;
  }
}
With the current updates to the Car class, you could stop receiving the engine notification on the second handler by updating the calling code as follows:
Console.WriteLine("***** Delegates as event enablers ***** ");
// First, make a Car object.
Car c1 = new Car("SlugBug", 100, 10);
c1.RegisterWithCarEngine(
  new Car.CarEngineHandler(OnCarEngineEvent));
// This time, hold onto the delegate object,
// so we can unregister later.
Car.CarEngineHandler handler2 =
  new Car.CarEngineHandler(OnCarEngineEvent2);
c1.RegisterWithCarEngine(handler2);
// Speed up (this will trigger the events).
Console.WriteLine("***** Speeding up *****");
for (int i = 0; i < 6; i++)
{
  c1.Accelerate(20);
}
// Unregister from the second handler.
c1.UnRegisterWithCarEngine(handler2);
// We won't see the "uppercase" message anymore!
Console.WriteLine("***** Speeding up *****");
for (int i = 0; i < 6; i++)
{
  c1.Accelerate(20);
}
Console.ReadLine();

One difference in this code is that this time you are creating a Car.CarEngineHandler object and storing it in a local variable so you can use this object to unregister with the notification later. Thus, the second time you speed up the Car object, you no longer see the uppercase version of the incoming message data, as you have removed this target from the delegate’s invocation list.

Method Group Conversion Syntax

In the previous CarDelegate example, you explicitly created instances of the Car.CarEngineHandler delegate object to register and unregister with the engine notifications.
Console.WriteLine("***** Delegates as event enablers ***** ");
Car c1 = new Car("SlugBug", 100, 10);
c1.RegisterWithCarEngine(new Car.CarEngineHandler(OnCarEngineEvent));
Car.CarEngineHandler handler2 =
  new Car.CarEngineHandler(OnCarEngineEvent2);
c1.RegisterWithCarEngine(handler2);
...

To be sure, if you need to call any of the inherited members of MulticastDelegate or Delegate, manually creating a delegate variable is the most straightforward way of doing so. However, in most cases, you don’t really need to hang onto the delegate object. Rather, you typically need to use the delegate object only to pass in the method name as a constructor parameter.

As a simplification, C# provides a shortcut termed method group conversion. This feature allows you to supply a direct method name, rather than a delegate object, when calling methods that take delegates as arguments.

Note

As you will see later in this chapter, you can also use method group conversion syntax to simplify how you register with a C# event.

To illustrate, consider the following updates to the Program class, which uses method group conversion to register and unregister from the engine notifications:
...
Console.WriteLine("***** Method Group Conversion ***** ");
Car c2 = new Car();
// Register the simple method name.
c2.RegisterWithCarEngine(OnCarEngineEvent);
Console.WriteLine("***** Speeding up *****");
for (int i = 0; i < 6; i++)
{
  c2.Accelerate(20);
}
// Unregister the simple method name.
c2.UnRegisterWithCarEngine(OnCarEngineEvent);
// No more notifications!
for (int i = 0; i < 6; i++)
{
  c2.Accelerate(20);
}
Console.ReadLine();

Notice that you are not directly allocating the associated delegate object but rather simply specifying a method that matches the delegate’s expected signature (a method returning void and taking a single string, in this case). Understand that the C# compiler is still ensuring type safety. Thus, if the OnCarEngineEvent() method did not take a string and return void, you would be issued a compiler error.

Understanding Generic Delegates

In Chapter 10, I mentioned that C# allows you to define generic delegate types. For example, assume you want to define a delegate type that can call any method returning void and receiving a single parameter. If the argument in question may differ, you could model this using a type parameter. To illustrate, consider the following code within a new Console Application project named GenericDelegate:
Console.WriteLine("***** Generic Delegates ***** ");
// Register targets.
MyGenericDelegate<string> strTarget =
  new MyGenericDelegate<string>(StringTarget);
strTarget("Some string data");
//Using the method group conversion syntax
MyGenericDelegate<int> intTarget = IntTarget;
intTarget(9);
Console.ReadLine();
static void StringTarget(string arg)
{
  Console.WriteLine("arg in uppercase is: {0}", arg.ToUpper());
}
static void IntTarget(int arg)
{
  Console.WriteLine("++arg is: {0}", ++arg);
}
  // This generic delegate can represent any method
  // returning void and taking a single parameter of type T.
  public delegate void MyGenericDelegate<T>(T arg);
Notice that MyGenericDelegate<T> defines a single type parameter that represents the argument to pass to the delegate target. When creating an instance of this type, you are required to specify the value of the type parameter, as well as the name of the method the delegate will invoke. Thus, if you specified a string type, you send a string value to the target method.
// Create an instance of MyGenericDelegate<T>
// with string as the type parameter.
MyGenericDelegate<string> strTarget = StringTarget;
strTarget("Some string data");
Given the format of the strTarget object, the StringTarget() method must now take a single string as a parameter.
static void StringTarget(string arg)
{
  Console.WriteLine(
    "arg in uppercase is: {0}", arg.ToUpper());
}

The Generic Action<> and Func<> Delegates

Over the course of this chapter, you have seen that when you want to use delegates to enable callbacks in your applications, you typically follow the steps shown here:
  1. 1.

    Define a custom delegate that matches the format of the method being pointed to.

     
  2. 2.

    Create an instance of your custom delegate, passing in a method name as a constructor argument.

     
  3. 3.

    Invoke the method indirectly, via a call to Invoke() on the delegate object.

     

When you take this approach, you typically end up with several custom delegates that might never be used beyond the current task at hand (e.g., MyGenericDelegate<T>, CarEngineHandler, etc.). While it may certainly be the case that you do indeed need to have a custom, uniquely named delegate type for your project, other times the exact name of the delegate type is irrelevant. In many cases, you simply want “some delegate” that takes a set of arguments and possibly has a return value other than void. In these cases, you can use the framework’s built-in Action<> and Func<> delegate types. To illustrate their usefulness, create a new Console Application project named ActionAndFuncDelegates.

The generic Action<> delegate is defined in the System namespace, and you can use this generic delegate to “point to” a method that takes up to 16 arguments (that ought to be enough!) and returns void. Now recall, because Action<> is a generic delegate, you will need to specify the underlying types of each parameter as well.

Update your Program class to define a new static method that takes three (or so) unique parameters. Here’s an example:
// This is a target for the Action<> delegate.
static void DisplayMessage(string msg, ConsoleColor txtColor, int printCount)
{
  // Set color of console text.
  ConsoleColor previous = Console.ForegroundColor;
  Console.ForegroundColor = txtColor;
  for (int i = 0; i < printCount; i++)
  {
    Console.WriteLine(msg);
  }
  // Restore color.
  Console.ForegroundColor = previous;
}
Now, rather than building a custom delegate manually to pass the program’s flow to the DisplayMessage() method, you can use the out-of-the-box Action<> delegate , as so:
Console.WriteLine("***** Fun with Action and Func *****");
// Use the Action<> delegate to point to DisplayMessage.
Action<string, ConsoleColor, int> actionTarget =
  DisplayMessage;
actionTarget("Action Message!", ConsoleColor.Yellow, 5);
Console.ReadLine();

As you can see, using the Action<> delegate saves you the bother of defining a custom delegate type. However, recall that the Action<> delegate type can point only to methods that take a void return value. If you want to point to a method that does have a return value (and don’t want to bother writing the custom delegate yourself), you can use Func<>.

The generic Func<> delegate can point to methods that (like Action<>) take up to 16 parameters and a custom return value. To illustrate, add the following new method to the Program class:
// Target for the Func<> delegate.
static int Add(int x, int y)
{
  return x + y;
}
Earlier in the chapter, I had you build a custom BinaryOp delegate to “point to” addition and subtraction methods. However, you can simplify your efforts using a version of Func<> that takes a total of three type parameters. Be aware that the final type parameter of Func<> is always the return value of the method. Just to solidify that point, assume the Program class also defines the following method:
static string SumToString(int x, int y)
{
  return (x + y).ToString();
}
Now, the calling code can call each of these methods, as so:
Func<int, int, int> funcTarget = Add;
int result = funcTarget.Invoke(40, 40);
Console.WriteLine("40 + 40 = {0}", result);
Func<int, int, string> funcTarget2 = SumToString;
string sum = funcTarget2(90, 300);
Console.WriteLine(sum);

In any case, given that Action<> and Func<> can save you the step of manually defining a custom delegate, you might be wondering if you should use them all the time. The answer, like so many aspects of programming, is “it depends.” In many cases, Action<> and Func<> will be the preferred course of action (no pun intended). However, if you need a delegate that has a custom name that you feel helps better capture your problem domain, building a custom delegate is as simple as a single code statement. You’ll see both approaches as you work over the remainder of this text.

Note

Many important .NET Core APIs make considerable use of Action<> and Func<> delegates, including the parallel programming framework and LINQ (among others).

That wraps up our initial look at the delegate type. Next, let’s move on to the related topic of the C# event keyword.

Understanding C# Events

Delegates are interesting constructs, in that they enable objects in memory to engage in a two-way conversation. However, working with delegates in the raw can entail the creation of some boilerplate code (defining the delegate, declaring necessary member variables, creating custom registration and unregistration methods to preserve encapsulation, etc.).

Moreover, when you use delegates in the raw as your application’s callback mechanism, if you do not define a class’s delegate member variables as private, the caller will have direct access to the delegate objects. In this case, the caller could reassign the variable to a new delegate object (effectively deleting the current list of functions to call), and, worse yet, the caller would be able to directly invoke the delegate’s invocation list. To demonstrate this problem, create a new Console Application named PublicDelegateProblem and add the following reworking (and simplification) of the Car class from the previous CarDelegate example:
namespace PublicDelegateproblem
{
  public class Car
  {
    public delegate void CarEngineHandler(string msgForCaller);
    // Now a public member!
    public CarEngineHandler ListOfHandlers;
    // Just fire out the Exploded notification.
    public void Accelerate(int delta)
    {
      if (ListOfHandlers != null)
      {
        ListOfHandlers("Sorry, this car is dead...");
      }
    }
  }
}
Notice that you no longer have private delegate member variables encapsulated with custom registration methods. Because these members are indeed public, the caller can directly access the listOfHandlers member variable and reassign this type to new CarEngineHandler objects and invoke the delegate whenever it so chooses.
using System;
using PublicDelegateProblem;
Console.WriteLine("***** Agh! No Encapsulation! ***** ");
// Make a Car.
Car myCar = new Car();
// We have direct access to the delegate!
myCar.ListOfHandlers = CallWhenExploded;
myCar.Accelerate(10);
// We can now assign to a whole new object...
// confusing at best.
myCar.ListOfHandlers = CallHereToo;
myCar.Accelerate(10);
// The caller can also directly invoke the delegate!
myCar.ListOfHandlers.Invoke("hee, hee, hee...");
Console.ReadLine();
static void CallWhenExploded(string msg)
{
  Console.WriteLine(msg);
}
static void CallHereToo(string msg)
{
  Console.WriteLine(msg);
}
Exposing public delegate members breaks encapsulation, which not only can lead to code that is hard to maintain (and debug) but could also open your application to possible security risks! Here is the output of the current example:
***** Agh! No Encapsulation! *****
Sorry, this car is dead...
Sorry, this car is dead...
hee, hee, hee...

Obviously, you would not want to give other applications the power to change what a delegate is pointing to or to invoke the members without your permission. Given this, it is common practice to declare private delegate member variables.

The C# event Keyword

As a shortcut, so you don’t have to build custom methods to add or remove methods to a delegate’s invocation list, C# provides the event keyword . When the compiler processes the event keyword, you are automatically provided with registration and un-registration methods, as well as any necessary member variables for your delegate types. These delegate member variables are always declared private, and, therefore, they are not directly exposed from the object firing the event. To be sure, the event keyword can be used to simplify how a custom class sends out notifications to external objects.

Defining an event is a two-step process. First, you need to define a delegate type (or reuse an existing one) that will hold the list of methods to be called when the event is fired. Next, you declare an event (using the C# event keyword) in terms of the related delegate type.

To illustrate the event keyword, create a new console application named CarEvents. In this iteration of the Car class, you will define two events named AboutToBlow and Exploded. These events are associated to a single delegate type named CarEngineHandler. Here are the initial updates to the Car class:
using System;
namespace CarEvents
{
  public class Car
  {
...
    // This delegate works in conjunction with the
    // Car's events.
    public delegate void CarEngineHandler(string msgForCaller);
    // This car can send these events.
    public event CarEngineHandler Exploded;
    public event CarEngineHandler AboutToBlow;
 ...
  }
}
Sending an event to the caller is as simple as specifying the event by name, along with any required parameters as defined by the associated delegate. To ensure that the caller has indeed registered with the event, you will want to check the event against a null value before invoking the delegate’s method set. With these points in mind, here is the new iteration of the Car’s Accelerate() method:
public void Accelerate(int delta)
{
  // If the car is dead, fire Exploded event.
  if (_carIsDead)
  {
    Exploded?.Invoke("Sorry, this car is dead...");
  }
  else
  {
    CurrentSpeed += delta;
    // Almost dead?
    if (10 == MaxSpeed - CurrentSpeed)
    {
      AboutToBlow?.Invoke("Careful buddy! Gonna blow!");
    }
    // Still OK!
    if (CurrentSpeed >= MaxSpeed)
    {
      _carIsDead = true;
    }
    else
    {
      Console.WriteLine("CurrentSpeed = {0}", CurrentSpeed);
    }
  }
}

With this, you have configured the car to send two custom events without having to define custom registration functions or declare delegate member variables. You will see the usage of this new automobile in just a moment, but first let’s check the event architecture in a bit more detail.

Events Under the Hood

When the compiler processes the C# event keyword, it generates two hidden methods, one having an add_ prefix and the other having a remove_ prefix . Each prefix is followed by the name of the C# event. For example, the Exploded event results in two hidden methods named add_Exploded() and remove_Exploded(). If you were to check out the CIL instructions behind add_AboutToBlow() , you would find a call to the Delegate.Combine() method. Consider the partial CIL code:
.method public hidebysig specialname instance void  add_AboutToBlow(
  class [System.Runtime]System.EventHandler`1<class CarEvents.CarEventArgs> 'value') cil managed
  {
...
    IL_000b: call class [System.Runtime]System.Delegate [System.Runtime]System.Delegate::Combine(class [System.Runtime]System.Delegate, class [System.Runtime]System.Delegate)
...
  } // end of method Car::add_AboutToBlow
As you would expect, remove_AboutToBlow() will call Delegate.Remove() on your behalf.
.method public hidebysig specialname instance void  remove_AboutToBlow (
  class [System.Runtime]System.EventHandler`1<class CarEvents.CarEventArgs> 'value') cil managed
  {
...
    IL_000b:  call class [System.Runtime]System.Delegate [System.Runtime]System.Delegate::Remove(class [System.Runtime]System.Delegate, class [System.Runtime]System.Delegate)
...
}
Finally, the CIL code representing the event itself uses the .addon and .removeon directives to map the names of the correct add_XXX() and remove_XXX() methods to invoke.
.event class [System.Runtime]System.EventHandler`1<class CarEvents.CarEventArgs> AboutToBlow
{
  .addon instance void CarEvents.Car::add_AboutToBlow(
    class [System.Runtime]System.EventHandler`1<class CarEvents.CarEventArgs>)
  .removeon instance void CarEvents.Car::remove_AboutToBlow(
    class [System.Runtime]System.EventHandler`1<class CarEvents.CarEventArgs>)
} // end of event Car::AboutToBlow

Now that you understand how to build a class that can send C# events (and are aware that events are little more than a typing time-saver), the next big question is how to listen to the incoming events on the caller’s side.

Listening to Incoming Events

C# events also simplify the act of registering the caller-side event handlers. Rather than having to specify custom helper methods, the caller simply uses the += and -= operators directly (which triggers the correct add_XXX() or remove_XXX() method in the background). When you want to register with an event, follow the pattern shown here:
// NameOfObject.NameOfEvent +=
//    new RelatedDelegate(functionToCall);
//
Car.CarEngineHandler d =
  new Car.CarEngineHandler(CarExplodedEventHandler);
myCar.Exploded += d;
When you want to detach from a source of events, use the -= operator, using the following pattern:
// NameOfObject.NameOfEvent -=
//  new RelatedDelegate(functionToCall);
//
myCar.Exploded -= d;
Note that you can also use the method group conversion syntax with events as well:
Car.CarEngineHandler d = CarExplodedEventHandler;
myCar.Exploded += d;
Given these very predictable patterns, here is the refactored calling code, now using the C# event registration syntax:
Console.WriteLine("***** Fun with Events ***** ");
Car c1 = new Car("SlugBug", 100, 10);
// Register event handlers.
c1.AboutToBlow += CarIsAlmostDoomed;
c1.AboutToBlow += CarAboutToBlow;
Car.CarEngineHandler d = CarExploded;
c1.Exploded += d;
Console.WriteLine("***** Speeding up *****");
for (int i = 0; i < 6; i++)
{
  c1.Accelerate(20);
}
// Remove CarExploded method
// from invocation list.
c1.Exploded -= d;
Console.WriteLine(" ***** Speeding up *****");
for (int i = 0; i < 6; i++)
{
  c1.Accelerate(20);
}
Console.ReadLine();
static void CarAboutToBlow(string msg)
{
  Console.WriteLine(msg);
}
static void CarIsAlmostDoomed(string msg)
{
  Console.WriteLine("=> Critical Message from Car: {0}", msg);
}
static void CarExploded(string msg)
{
  Console.WriteLine(msg);
}

Simplifying Event Registration Using Visual Studio

Visual Studio helps with the process of registering event handlers. When you apply the += syntax during event registration, you will find an IntelliSense window displayed, inviting you to hit the Tab key to autocomplete the associated delegate instance (see Figure 12-1), which is captured using method group conversion syntax .
../images/340876_10_En_12_Chapter/340876_10_En_12_Fig1_HTML.jpg
Figure 12-1.

Delegate selection IntelliSense

After you hit the Tab key, the IDE will generate the new method automatically, as shown in Figure 12-2.
../images/340876_10_En_12_Chapter/340876_10_En_12_Fig2_HTML.jpg
Figure 12-2.

Delegate target format IntelliSense

Note the stub code is in the correct format of the delegate target (note that this method has been declared static because the event was registered within a static method).
static void NewCar_AboutToBlow(string msg)
{
  throw new NotImplementedException();
}

IntelliSense is available to all .NET Core events, your custom events, and all the events in the base class libraries. This IDE feature is a massive time-saver, given that it saves you from having to search the help system to figure out both the correct delegate to use with an event and the format of the delegate target method.

Creating Custom Event Arguments

Truth be told, there is one final enhancement you could make to the current iteration of the Car class that mirrors Microsoft’s recommended event pattern. As you begin to explore the events sent by a given type in the base class libraries, you will find that the first parameter of the underlying delegate is a System.Object, while the second parameter is a descendant of System.EventArgs.

The System.Object argument represents a reference to the object that sent the event (such as the Car), while the second parameter represents information regarding the event at hand. The System.EventArgs base class represents an event that is not sending any custom information.
public class EventArgs
{
  public static readonly EventArgs Empty;
  public EventArgs();
}
For simple events, you can pass an instance of EventArgs directly. However, when you want to pass along custom data, you should build a suitable class deriving from EventArgs. For this example, assume you have a class named CarEventArgs, which maintains a string representing the message sent to the receiver.
using System;
namespace CarEvents
{
  public class CarEventArgs : EventArgs
  {
    public readonly string msg;
    public CarEventArgs(string message)
    {
      msg = message;
    }
  }
}
With this, you would now update the CarEngineHandler delegate type definition as follows (the events would be unchanged):
public class Car
{
  public delegate void CarEngineHandler(object sender, CarEventArgs e);
...
}
Here, when firing the events from within the Accelerate() method, you would now need to supply a reference to the current Car (via the this keyword) and an instance of the CarEventArgs type. For example, consider the following partial update:
public void Accelerate(int delta)
{
  // If the car is dead, fire Exploded event.
  if (carIsDead)
  {
    Exploded?.Invoke(this, new CarEventArgs("Sorry, this car is dead..."));
  }
...
}
On the caller’s side, all you would need to do is update your event handlers to receive the incoming parameters and obtain the message via the read-only field. Here’s an example:
static void CarAboutToBlow(object sender, CarEventArgs e)
{
  Console.WriteLine($"{sender} says: {e.msg}");
}
If the receiver wants to interact with the object that sent the event, you can explicitly cast the System.Object. From this reference, you can make use of any public member of the object that sent the event notification.
static void CarAboutToBlow(object sender, CarEventArgs e)
{
  // Just to be safe, perform a
  // runtime check before casting.
  if (sender is Car c)
  {
    Console.WriteLine(
      $"Critical Message from {c.PetName}: {e.msg}");
  }
}

The Generic EventHandler<T> Delegate

Given that so many custom delegates take an object as the first parameter and an EventArgs descendant as the second, you could further streamline the previous example by using the generic EventHandler<T> type, where T is your custom EventArgs type . Consider the following update to the Car type (notice how you no longer need to define a custom delegate type at all):
public class Car
{
...
  public event EventHandler<CarEventArgs> Exploded;
  public event EventHandler<CarEventArgs> AboutToBlow;
}
The calling code could then use EventHandler<CarEventArgs> anywhere you previously specified CarEventHandler (or, once again, use method group conversion).
Console.WriteLine("***** Prim and Proper Events ***** ");
// Make a car as usual.
Car c1 = new Car("SlugBug", 100, 10);
// Register event handlers.
c1.AboutToBlow += CarIsAlmostDoomed;
c1.AboutToBlow += CarAboutToBlow;
EventHandler<CarEventArgs> d = CarExploded;
c1.Exploded += d;
...

Great! At this point, you have seen the core aspects of working with delegates and events in the C# language. While you could use this information for just about all your callback needs, you will wrap up this chapter with a look at some final simplifications, specifically anonymous methods and lambda expressions .

Understanding C# Anonymous Methods

As you have seen, when a caller wants to listen to incoming events, it must define a custom method in a class (or structure) that matches the signature of the associated delegate. Here’s an example:
SomeType t = new SomeType();
// Assume "SomeDelegate" can point to methods taking no
// args and returning void.
t.SomeEvent += new SomeDelegate(MyEventHandler);
// Typically only called by the SomeDelegate object.
static void MyEventHandler()
{
  // Do something when event is fired.
}

When you think about it, however, methods such as MyEventHandler() are seldom intended to be called by any part of the program other than the invoking delegate. As far as productivity is concerned, it is a bit of a bother (though in no way a showstopper) to manually define a separate method to be called by the delegate object.

To address this point, it is possible to associate an event directly to a block of code statements at the time of event registration. Formally, such code is termed an anonymous method. To illustrate the syntax, first create a new Console Application named AnonymousMethods, and copy the Car.cs and CarEventArgs.cs classes from the CarEvents project into the new project (making sure to change their namespaces to AnonymousMethods). Update the Program.cs file’s code to match the following, which handles the events sent from the Car class using anonymous methods, rather than specifically named event handlers:
using System;
using AnonymousMethods;
Console.WriteLine("***** Anonymous Methods ***** ");
Car c1 = new Car("SlugBug", 100, 10);
// Register event handlers as anonymous methods.
c1.AboutToBlow += delegate
{
  Console.WriteLine("Eek! Going too fast!");
};
c1.AboutToBlow += delegate(object sender, CarEventArgs e)
{
  Console.WriteLine("Message from Car: {0}", e.msg);
};
c1.Exploded += delegate(object sender, CarEventArgs e)
{
  Console.WriteLine("Fatal Message from Car: {0}", e.msg);
};
// This will eventually trigger the events.
for (int i = 0; i < 6; i++)
{
  c1.Accelerate(20);
}
Console.ReadLine();
Note

The final curly bracket of an anonymous method must be terminated by a semicolon. If you fail to do so, you are issued a compilation error.

Again, notice that the calling code no longer needs to define specific static event handlers such as CarAboutToBlow() or CarExploded(). Rather, the unnamed (aka anonymous) methods are defined inline at the time the caller is handling the event using the += syntax . The basic syntax of an anonymous method matches the following pseudocode:
SomeType t = new SomeType();
t.SomeEvent += delegate (optionallySpecifiedDelegateArgs)
{ /* statements */ };
When handling the first AboutToBlow event within the previous code sample, notice that you are not specifying the arguments passed from the delegate.
c1.AboutToBlow += delegate
{
  Console.WriteLine("Eek! Going too fast!");
};
Strictly speaking, you are not required to receive the incoming arguments sent by a specific event. However, if you want to make use of the possible incoming arguments, you will need to specify the parameters prototyped by the delegate type (as shown in the second handling of the AboutToBlow and Exploded events). Here’s an example:
c1.AboutToBlow += delegate(object sender, CarEventArgs e)
{
  Console.WriteLine("Critical Message from Car: {0}", e.msg);
};

Accessing Local Variables

Anonymous methods are interesting, in that they can access the local variables of the method that defines them. Formally speaking, such variables are termed outer variables of the anonymous method. The following important points about the interaction between an anonymous method scope and the scope of the defining method should be mentioned:
  • An anonymous method cannot access ref or out parameters of the defining method.

  • An anonymous method cannot have a local variable with the same name as a local variable in the outer method.

  • An anonymous method can access instance variables (or static variables, as appropriate) in the outer class scope.

  • An anonymous method can declare local variables with the same name as outer class member variables (the local variables have a distinct scope and hide the outer class member variables).

Assume your top-level statements define a local integer named aboutToBlowCounter. Within the anonymous methods that handle the AboutToBlow event, you will increment this counter by one and print out the tally before the statements complete.
Console.WriteLine("***** Anonymous Methods ***** ");
int aboutToBlowCounter = 0;
// Make a car as usual.
Car c1 = new Car("SlugBug", 100, 10);
// Register event handlers as anonymous methods.
c1.AboutToBlow += delegate
{
  aboutToBlowCounter++;
  Console.WriteLine("Eek! Going too fast!");
};
c1.AboutToBlow += delegate(object sender, CarEventArgs e)
{
  aboutToBlowCounter++;
  Console.WriteLine("Critical Message from Car: {0}", e.msg);
};
...
// This will eventually trigger the events.
for (int i = 0; i < 6; i++)
{
  c1.Accelerate(20);
}
Console.WriteLine("AboutToBlow event was fired {0} times.",
  aboutToBlowCounter);
Console.ReadLine();

After you run this updated code, you will find the final Console.WriteLine() reports the AboutToBlow event was fired twice.

Using static with Anonymous Methods (New 9.0)

The previous example demonstrated anonymous methods interacting with variables declared outside of the scope of the method itself. While this might be what you intend, it breaks encapsulation and could introduce unintended side effects into your program. Recall from Chapter 4 that local functions can be isolated from the containing code by setting them as static, as in the following example:
static int AddWrapperWithStatic(int x, int y)
{
  //Do some validation here
  return Add(x,y);
  static int Add(int x, int y)
  {
    return x + y;
  }
}
New in C# 9.0, anonymous methods can also be marked as static to preserve encapsulation and ensure that the method cannot introduce any side effects into the containing code. For example, see the updated anonymous method here:
c1.AboutToBlow += static delegate
{
  //This causes a compile error because it is marked static
  aboutToBlowCounter++;
  Console.WriteLine("Eek! Going too fast!");
};

The preceding code will not compile due to the anonymous methods attempting to access the variable declared outside its scope.

Discards with Anonymous Methods (New 9.0)

Discards , introduced in Chapter 3, have been updated in C# 9.0 for use as input parameters for anonymous methods, with a catch. Because the underscore (_) was a legal variable identifier in previous versions of C#, there must be two or more discards used with the anonymous method to be treated as discards.

For example, the following code created a delegate for a Func that takes two integers and returns another. This implementation ignores any variables passed in and returns 42:
Console.WriteLine("******** Discards with Anonymous Methods ********");
Func<int,int,int> constant = delegate (int _, int _) {return 42;};
Console.WriteLine("constant(3,4)={0}",constant(3,4));

Understanding Lambda Expressions

To conclude your look at the .NET Core event architecture, you will examine C# lambda expressions. As just explained, C# supports the ability to handle events “inline” by assigning a block of code statements directly to an event using anonymous methods, rather than building a stand-alone method to be called by the underlying delegate. Lambda expressions are nothing more than a concise way to author anonymous methods and ultimately simplify how you work with the .NET Core delegate type.

To set the stage for your examination of lambda expressions, create a new Console Application project named LambdaExpressions. To begin, consider the FindAll() method of the generic List<T> class. This method can be called when you need to extract a subset of items from the collection and is prototyped like so:
// Method of the System.Collections.Generic.List<T>
public List<T> FindAll(Predicate<T> match)
As you can see, this method returns a new List<T> that represents the subset of data. Also notice that the sole parameter to FindAll() is a generic delegate of type System.Predicate<T>. This delegate type can point to any method returning a bool and takes a single type parameter as the only input parameter.
// This delegate is used by FindAll() method
// to extract out the subset.
public delegate bool Predicate<T>(T obj);

When you call FindAll(), each item in the List<T> is passed to the method pointed to by the Predicate<T> object. The implementation of said method will perform some calculations to see whether the incoming data matches the necessary criteria and will return true or false. If this method returns true, the item will be added to the new List<T> that represents the subset (got all that?).

Before you see how lambda expressions can simplify working with FindAll(), let’s work the problem out in longhand notation, using the delegate objects directly. Add a method (named TraditionalDelegateSyntax()) within your Program type that interacts with the System.Predicate<T> type to discover the even numbers in a List<T> of integers.
using System;
using System.Collections.Generic;
using LambdaExpressions;
Console.WriteLine("***** Fun with Lambdas ***** ");
TraditionalDelegateSyntax();
Console.ReadLine();
static void TraditionalDelegateSyntax()
{
  // Make a list of integers.
  List<int> list = new List<int>();
  list.AddRange(new int[] { 20, 1, 4, 8, 9, 44 });
  // Call FindAll() using traditional delegate syntax.
  Predicate<int> callback = IsEvenNumber;
  List<int> evenNumbers = list.FindAll(callback);
  Console.WriteLine("Here are your even numbers:");
  foreach (int evenNumber in evenNumbers)
  {
    Console.Write("{0} ", evenNumber);
  }
  Console.WriteLine();
}
// Target for the Predicate<> delegate.
static bool IsEvenNumber(int i)
{
  // Is it an even number?
  return (i % 2) == 0;
}

Here, you have a method (IsEvenNumber()) that oversees testing the incoming integer parameter to see whether it is even or odd via the C# modulo operator, %. If you execute your application, you will find the numbers 20, 4, 8, and 44 print to the console.

While this traditional approach to working with delegates behaves as expected, the IsEvenNumber() method is invoked only in limited circumstances—specifically when you call FindAll(), which leaves you with the baggage of a full method definition. While you could make this a local function, if you were to instead use an anonymous method, your code would clean up considerably. Consider the following new method of the Program class:
static void AnonymousMethodSyntax()
{
  // Make a list of integers.
  List<int> list = new List<int>();
  list.AddRange(new int[] { 20, 1, 4, 8, 9, 44 });
  // Now, use an anonymous method.
  List<int> evenNumbers =
    list.FindAll(delegate(int i) { return (i % 2) == 0; } );
  Console.WriteLine("Here are your even numbers:");
  foreach (int evenNumber in evenNumbers)
  {
    Console.Write("{0} ", evenNumber);
  }
  Console.WriteLine();
}
In this case, rather than directly creating a Predicate<T> delegate object and then authoring a stand-alone method, you can inline a method anonymously. While this is a step in the right direction, you are still required to use the delegate keyword (or a strongly typed Predicate<T>), and you must ensure that the parameter list is a dead-on match.
List<int> evenNumbers = list.FindAll(
  delegate(int i)
  {
    return (i % 2) == 0;
  }
);
Lambda expressions can be used to simplify the call to FindAll() even more. When you use lambda syntax, there is no trace of the underlying delegate object whatsoever. Consider the following new method to the Program class:
static void LambdaExpressionSyntax()
{
  // Make a list of integers.
  List<int> list = new List<int>();
  list.AddRange(new int[] { 20, 1, 4, 8, 9, 44 });
  // Now, use a C# lambda expression.
  List<int> evenNumbers = list.FindAll(i => (i % 2) == 0);
  Console.WriteLine("Here are your even numbers:");
  foreach (int evenNumber in evenNumbers)
  {
    Console.Write("{0} ", evenNumber);
  }
  Console.WriteLine();
}
In this case, notice the rather strange statement of code passed into the FindAll() method, which is in fact a lambda expression. In this iteration of the example, there is no trace whatsoever of the Predicate<T> delegate (or the delegate keyword, for that matter). All you have specified is the lambda expression.
i => (i % 2) == 0
Before I break this syntax down, first understand that lambda expressions can be used anywhere you would have used an anonymous method or a strongly typed delegate (typically with far fewer keystrokes). Under the hood, the C# compiler translates the expression into a standard anonymous method making use of the Predicate<T> delegate type (which can be verified using ildasm.exe or reflector.exe). Specifically, the following code statement:
// This lambda expression...
List<int> evenNumbers = list.FindAll(i => (i % 2) == 0);
is compiled into the following approximate C# code:
// ...becomes this anonymous method.
List<int> evenNumbers = list.FindAll(delegate (int i)
{
  return (i % 2) == 0;
});

Dissecting a Lambda Expression

A lambda expression is written by first defining a parameter list, followed by the => token (C#’s token for the lambda operator found in the lambda calculus), followed by a set of statements (or a single statement) that will process these arguments. From a high level, a lambda expression can be understood as follows:
ArgumentsToProcess => StatementsToProcessThem
Within the LambdaExpressionSyntax() method, things break down like so:
// "i" is our parameter list.
// "(i % 2) == 0" is our statement set to process "i".
List<int> evenNumbers = list.FindAll(i => (i % 2) == 0);
The parameters of a lambda expression can be explicitly or implicitly typed. Currently, the underlying data type representing the i parameter (an integer) is determined implicitly. The compiler can figure out that i is an integer based on the context of the overall lambda expression and the underlying delegate. However, it is also possible to explicitly define the type of each parameter in the expression by wrapping the data type and variable name in a pair of parentheses, as follows:
// Now, explicitly state the parameter type.
List<int> evenNumbers = list.FindAll((int i) => (i % 2) == 0);
As you have seen, if a lambda expression has a single, implicitly typed parameter, the parentheses may be omitted from the parameter list. If you want to be consistent regarding your use of lambda parameters, you can always wrap the parameter list within parentheses, leaving you with this expression:
List<int> evenNumbers = list.FindAll((i) => (i % 2) == 0);
Finally, notice that currently the expression has not been wrapped in parentheses (you have of course wrapped the modulo statement to ensure it is executed first before the test for equality). Lambda expressions do allow for the statement to be wrapped as follows:
// Now, wrap the expression as well.
List<int> evenNumbers = list.FindAll((i) => ((i % 2) == 0));
Now that you have seen the various ways to build a lambda expression, how can you read this lambda statement in human-friendly terms? Leaving the raw mathematics behind, the following explanation fits the bill:
// My list of parameters (in this case, a single integer named i)
// will be processed by the expression (i % 2) == 0.
List<int> evenNumbers = list.FindAll((i) => ((i % 2) == 0));

Processing Arguments Within Multiple Statements

The first lambda expression was a single statement that ultimately evaluated to a Boolean. However, as you know, many delegate targets must perform several code statements. For this reason, C# allows you to build lambda expressions containing multiple statements by specifying a code block using the standard curly braces. Consider the following example update to the LambdaExpressionSyntax() method:
static void LambdaExpressionSyntax()
{
  // Make a list of integers.
  List<int> list = new List<int>();
  list.AddRange(new int[] { 20, 1, 4, 8, 9, 44 });
  // Now process each argument within a group of
  // code statements.
  List<int> evenNumbers = list.FindAll((i) =>
  {
    Console.WriteLine("value of i is currently: {0}", i);
    bool isEven = ((i % 2) == 0);
    return isEven;
  });
  Console.WriteLine("Here are your even numbers:");
  foreach (int evenNumber in evenNumbers)
  {
    Console.Write("{0} ", evenNumber);
  }
  Console.WriteLine();
}
In this case, the parameter list (again, a single integer named i) is being processed by a set of code statements. Beyond the calls to Console.WriteLine(), the modulo statement has been broken into two code statements for increased readability. Assuming each of the methods you’ve looked at in this section is called from within your top-level statements:
Console.WriteLine("***** Fun with Lambdas ***** ");
TraditionalDelegateSyntax();
AnonymousMethodSyntax();
Console.WriteLine();
LambdaExpressionSyntax();
Console.ReadLine();
you will find the following output:
***** Fun with Lambdas *****
Here are your even numbers:
20      4       8       44
Here are your even numbers:
20      4       8       44
value of i is currently: 20
value of i is currently: 1
value of i is currently: 4
value of i is currently: 8
value of i is currently: 9
value of i is currently: 44
Here are your even numbers:
20      4       8       44

Lambda Expressions with Multiple (or Zero) Parameters

The lambda expressions you have seen in this chapter so far processed a single parameter. This is not a requirement, however, as a lambda expression may process multiple arguments (or none). To illustrate the first scenario of multiple arguments, add the following incarnation of the SimpleMath type:
public class SimpleMath
{
  public delegate void MathMessage(string msg, int result);
  private MathMessage _mmDelegate;
  public void SetMathHandler(MathMessage target)
  {
    _mmDelegate = target;
  }
  public void Add(int x, int y)
  {
    _mmDelegate?.Invoke("Adding has completed!", x + y);
  }
}
Notice that the MathMessage delegate type is expecting two parameters. To represent them as a lambda expression, the Main() method might be written as follows:
// Register with delegate as a lambda expression.
SimpleMath m = new SimpleMath();
m.SetMathHandler((msg, result) =>
  {Console.WriteLine("Message: {0}, Result: {1}", msg, result);});
// This will execute the lambda expression.
m.Add(10, 10);
Console.ReadLine();
Here, you are leveraging type inference, as the two parameters have not been strongly typed for simplicity. However, you could call SetMathHandler() , as follows:
m.SetMathHandler((string msg, int result) =>
  {Console.WriteLine("Message: {0}, Result: {1}", msg, result);});
Finally, if you are using a lambda expression to interact with a delegate taking no parameters at all, you may do so by supplying a pair of empty parentheses as the parameter. Thus, assuming you have defined the following delegate type:
public delegate string VerySimpleDelegate();
you could handle the result of the invocation as follows:
// Prints "Enjoy your string!" to the console.
VerySimpleDelegate d = new VerySimpleDelegate( () => {return "Enjoy your string!";} );
Console.WriteLine(d());
Using the new expression syntax, the previous line can be written like this:
VerySimpleDelegate d2 =
  new VerySimpleDelegate(() => "Enjoy your string!");
which can also be shortened to this:
VerySimpleDelegate d3 = () => "Enjoy your string!";

Using static with Lambda Expressions (New 9.0)

Since lambda expressions are shorthand for delegates, it is understandable that lambda also support the static keyword (with C# 9.0) as well as discards (covered in the next section). Add the following to your top-level statements:
var outerVariable = 0;
Func<int, int, bool> DoWork = (x,y) =>
{
  outerVariable++;
  return true;
};
DoWork(3,4);
Console.WriteLine("Outer variable now = {0}", outerVariable);
When this code is executed, it outputs the following:
***** Fun with Lambdas *****
Outer variable now = 1
If you update the lambda to static, you will receive a compile error since the expression is trying to update a variable declared in an outer scope.
var outerVariable = 0;
Func<int, int, bool> DoWork = static (x,y) =>
{
 //Compile error since it’s accessing an outer variable
  //outerVariable++;
  return true;
};

Discards with Lambda Expressions (New 9.0)

As with delegates (and C# 9.0), input variables to a lambda expression can be replaced with discards if the input variables aren’t needed. The same catch applies as with delegates. Since the underscore (_) was a legal variable identifier in previous versions of C#, they must be two or more discards used with the lambda expression.
var outerVariable = 0;
Func<int, int, bool> DoWork = (x,y) =>
{
  outerVariable++;
  return true;
};
DoWork(_,_);
Console.WriteLine("Outer variable now = {0}", outerVariable);

Retrofitting the CarEvents Example Using Lambda Expressions

Given that the whole reason for lambda expressions is to provide a clean, concise manner to define an anonymous method (and therefore indirectly a manner to simplify working with delegates), let’s retrofit the CarEventArgs project created earlier in this chapter. Here is a simplified version of that project’s Program class, which makes use of lambda expression syntax (rather than the raw delegates) to hook into each event sent from the Car object:
using System;
using CarEventsWithLambdas;
Console.WriteLine("***** More Fun with Lambdas ***** ");
// Make a car as usual.
Car c1 = new Car("SlugBug", 100, 10);
// Hook into events with lambdas!
c1.AboutToBlow += (sender, e)
  => { Console.WriteLine(e.msg);};
c1.Exploded += (sender, e) => { Console.WriteLine(e.msg); };
// Speed up (this will generate the events).
Console.WriteLine(" ***** Speeding up *****");
for (int i = 0; i < 6; i++)
{
  c1.Accelerate(20);
}
Console.ReadLine();

Lambdas and Expression-Bodied Members (Updated 7.0)

Now that you understand lambda expressions and how they work, it should be much clearer how expression-bodied members work under the covers. As mentioned in Chapter 4, as of C# 6, it is permissible to use the => operator to simplify member implementations. Specifically, if you have a method or property (in addition to a custom operator or conversion routine; see Chapter 11) that consists of exactly a single line of code in the implementation, you are not required to define a scope via curly bracket. You can instead leverage the lambda operator and write an expression-bodied member. In C# 7, you can also use this syntax for class constructors, finalizers (covered in Chapter 9), and get and set accessors on property members.

Be aware, however, this new shortened syntax can be used anywhere at all, even when your code has nothing to do with delegates or events. So, for example, if you were to build a trivial class to add two numbers, you might write the following:
class SimpleMath
{
  public int Add(int x, int y)
  {
    return x + y;
  }
  public void PrintSum(int x, int y)
  {
    Console.WriteLine(x + y);
  }
}
Alternatively, you could now write code like the following:
class SimpleMath
{
  public int Add(int x, int y) =>  x + y;
  public void PrintSum(int x, int y) => Console.WriteLine(x + y);
}
Ideally, at this point, you can see the overall role of lambda expressions and understand how they provide a “functional manner” to work with anonymous methods and delegate types. Although the lambda operator (=>) might take a bit to get used to, always remember a lambda expression can be broken down to the following simple equation:
ArgumentsToProcess =>
{
  //StatementsToProcessThem
}
Or, if using the => operator to implement a single-line type member, it would be like this:
TypeMember => SingleCodeStatement

It is worth pointing out that the LINQ programming model also makes substantial use of lambda expressions to help simplify your coding efforts. You will examine LINQ beginning in Chapter 13.

Summary

In this chapter, you examined several ways in which multiple objects can partake in a bidirectional conversation. First, you looked at the C# delegate keyword, which is used to indirectly construct a class derived from System.MulticastDelegate. As you saw, a delegate object maintains the method to call when told to do so.

You then examined the C# event keyword, which, when used in conjunction with a delegate type, can simplify the process of sending your event notifications to waiting callers. As shown via the resulting CIL, the .NET event model maps to hidden calls on the System.Delegate/System.MulticastDelegate types. In this light, the C# event keyword is purely optional, in that it simply saves you some typing time. As well, you have seen that the C# 6.0 null conditional operator simplifies how you safely fire events to any interested party.

This chapter also explored a C# language feature termed anonymous methods. Using this syntactic construct, you can directly associate a block of code statements to a given event. As you have seen, anonymous methods are free to ignore the parameters sent by the event and have access to the “outer variables” of the defining method. You also examined a simplified way to register events using method group conversion.

Finally, you wrapped things up by looking at the C# lambda operator, =>. As shown, this syntax is a great shorthand notation for authoring anonymous methods, where a stack of arguments can be passed into a group of statements for processing. Any method in the .NET Core platform that takes a delegate object as an argument can be substituted with a related lambda expression, which will typically simplify your code base quite a bit.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.181.231