Delegates, Lambdas, and Events using C#


This post was republished to .net minds at 11:29:02 18-09-2016

Delegates, Lambdas, and Events

 

 

Category.NET Framework.

 

Delegates: 

Delegates are the .NET variant of addresses to methods. Compare this to C++, where a function pointer is nothing more than a pointer to a memory location that is not type-safe. You have no idea what a pointer is really pointing to, and items such as parameters and return types are not known. This is completely different with .NET; delegates are type-safe classes that define the return types and types of parameters. The delegate class not only contains a reference to a method, but can hold references to multiple methods. Lambda expressions are directly related to delegates. When the parameter is a delegate type, you can use a lambda expression to implement a method that’s referenced from the delegate. This chapter explains the basics of delegates and lambda expressions, and shows you how to implement methods called by delegates with lambda expressions. It also demonstrates how .NET uses delegates as the means of implementing events.

Declaring Delegates

When you want to use a class in C#, you do so in two stages. First, you need to define the class — that is, you need to tell the compiler what fields and methods make up the class. Then (unless you are using only static methods), you instantiate an object of that class. With delegates it is the same process. You start by declaring the delegates, you want to use. Declaring delegates means telling the compiler what kind of method a delegate of that type will represent. Then, you have to create one or more instances of that delegate. Behind the scenes, the compiler creates a class that represents the delegate. The syntax for declaring delegates looks like this:

delegate void IntMethodInvoker(int x);

This declares a delegate called IntMethodInvoker, and indicates that each instance of this delegate can hold a reference to a method that takes one int parameter and returns void. The crucial point to understand about delegates is that they are type-safe. When you define the delegate, you have to provide full details about the signature and the return type of the method that it represents. Suppose that you want to define a delegate called TwoLongsOp that will represent a method that takes two longs as its parameters and returns a double. You could do so like this:

delegate double TwoLongsOp(long first, long second);

Or, to define a delegate that will represent a method that takes no parameters and returns a string, you might write this:

delegate string GetAString();

Using Delegates

The following code snippet demonstrates the use of a delegate. It is a rather long-winded way of calling the ToString method on an int :

private delegate string GetAString();

static void Main()

{

int x = 40;

GetAString firstStringMethod = new GetAString(x.ToString);

Console.WriteLine("String is {0}", firstStringMethod());

// With firstStringMethod initialized to x.ToString(),

// the above statement is equivalent to saying

// Console.WriteLine("String is {0}", x.ToString());

}

This code instantiates a delegate of type GetAString and initializes it so it refers to the ToString method of the integer variable x. Delegates in C# always syntactically take a one-parameter constructor, the parameter being the method to which the delegate refers. This method must match the signature with which you originally defined the delegate. In this case, you would get a compilation error if you tried to initialize the variable firstStringMethod with any method that did not take any parameters and return a string. Notice that because int.ToString is an instance method (as opposed to a static one), you need to specify the instance (x) as well as the name of the method to initialize the delegate properly.

 

 

Simple Delegate Example:

This example defines a MathOperations class that uses a couple of static methods to perform two operations on doubles. Then you use delegates to invoke these methods. The math class looks like this:

//MathOperations.cs

namespace SimpleDelegate

{

    class MathOperations

    {

        public static double MultiplyByThree(double value)

        {

            return value * 3;

        }

 

        public static double Cube(double value)

        {

            return value * value * value;

        }

    }

 

} 

//Program.cs

using System;

 

namespace SimpleDelegate

{

    delegate double DoubleOp(double x);

 

    class Program

    {

        static void Main()

        {

            DoubleOp[] operations =

            {

            MathOperations.MultiplyByThree,

            MathOperations.Cube

            };

 

            for (int i = 0; i < operations.Length; i++)

            {

                Console.WriteLine("Using operations[{0}]:", i);

                ProcessAndDisplayNumber(operations[i], 4.0);

                ProcessAndDisplayNumber(operations[i], 4.23);

                ProcessAndDisplayNumber(operations[i], 3.34);

                Console.WriteLine();

            }

            Console.ReadKey();

        }

 

        static void ProcessAndDisplayNumber(DoubleOp action, double value)

        {

            double result = action(value);

            Console.WriteLine(

               "Value is {0}, result of operation is {1}", value, result);

        }

    }

}

 Output : 

 

Action<T> and Func<T> Delegates :

Instead of defining a new delegate type with every parameter and return type, you can use the Action<T> and Func<T> delegates. The generic Action<T> delegate is meant to reference a method with void return. This delegate class exists in different variants so that you can pass up to 16 different parameter types. The Action class without the generic parameter is for calling methods without parameters. Action<in T> is for calling a method with one parameter; Action<in T1, in T2> for a method with two parameters; and Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8> for a method with eight parameters.

The Func<T> delegates can be used in a similar manner. Func<T> allows you to invoke methods with a return type. Similar to Action<T>, Func<T> is defined in different variants to pass up to 16 parameter types and a return type. Func<out TResult> is the delegate type to invoke a method with a return type and without parameters. Func<in T, out TResult> is for a method with one parameter, and Func<in T1, in T2, in T3, in T4, out TResult> is for a method with four parameters.

The example in the preceding section declared a delegate with a double parameter and a double return type:

delegate double DoubleOp(double x);

Instead of declaring the custom delegate DoubleOp you can use the Func<in T, out TResult> delegate. You can declare a variable of the delegate type, or as shown here, an array of the delegate type:

Func<double, double>[] operations =

{

MathOperations.MultiplyByTwo,

MathOperations.Square

};

and use it with the ProcessAndDisplayNumber() method as a parameter:

static void ProcessAndDisplayNumber(Func<double, double> action,double value)

{

double result = action(value);

Console.WriteLine("Value is {0}, result of operation is {1}",

value, result);

}

BubbleSorter Example :

Suppose some client code hands you an array of Currency structs or any other class or struct that it may have defined, then you need to be able to sort the array. This presents a problem with the line if(sortArray[i] <sortArray[i+1]) in the preceding code, because that requires you to compare two objects on the array to determine which one is greater. You can do that for ints, but how do you do it for a new class that doesn’t implement the < operator? The answer is that the client code that knows about the class will have to pass in a delegate wrapping a method that does the comparison. Also, instead of using an int type for the temp variable, a generic Sort method can be implemented using a generic type. With a generic Sort<T> method accepting type T, a comparison method is needed that has two parameters of type T and a return type of bool for the if comparison. This method can be referenced from a Func<T1, T2, TResult> delegate, where T1 and T2 are the same type: Func<T, T, bool>.

This way, you give your Sort<T> method the following signature:

static public void Sort<T>(IList<T> sortArray, Func<T, T, bool> comparison)

The documentation for this method states that comparison must refer to a method that takes two arguments, and returns true if the value of the first argument is smaller than the second one.

Now you are all set.

Example 1 : 

Here’s the definition for the BubbleSorter class :

using System;

using System.Collections.Generic;

 

namespace BubbleSorter

{

    class BubbleSorter

    {

        static public void Sort<T>(IList<T> sortArray, Func<T, T, bool> comparison)

        {

            bool swapped = true;

            do

            {

                swapped = false;

                for (int i = 0; i < sortArray.Count - 1; i++)

                {

                    if (comparison(sortArray[i + 1], sortArray[i]))

                    {

                        T temp = sortArray[i];

                        sortArray[i] = sortArray[i + 1];

                        sortArray[i + 1] = temp;

                        swapped = true;

                    }

                }

            } while (swapped);

 

 

        }

    }

}

 

Here is the definition for the Emloyee.cs

namespace BubbleSorter

{

    class Employee

    {

        public Employee(string name, decimal salary)

        {

            this.Name = name;

            this.Salary = salary;

        }

 

        public string Name { get; private set; }

        public decimal Salary { get; private set; }

 

        public override string ToString()

        {

            return string.Format("Employee Name : {0} and Employee Salary : {1:C}", Name, Salary);

        }

 

        public static bool CompareSalary(Employee e1, Employee e2)

        {

            return e1.Salary < e2.Salary;

        }

    }

}

Here is the definition of Program.cs :

using System;

 

namespace BubbleSorter

{

    class Program

    {

        static void Main()

        {

            Employee[] employees =

              {

                new Employee("Prabhat", 20000),

                new Employee("Deven", 10000),

                new Employee("Vicky", 25000),

                new Employee("Kapil", 1000000.38m),

                new Employee("Rajiv", 23000),

                new Employee("Ashis", 50000)

              };

 

            BubbleSorter.Sort(employees, Employee.CompareSalary);

 

            foreach (var employee in employees)

            {

                Console.WriteLine(employee);

            }

            Console.ReadKey();

 

        }

    }

}

 

Output :

 

Example 2 : 

Here we are going to add one another class Customer.cs with the employee class. In this case Bubblesorter.cs will be same even the type of datatype is different. In the customer class, OrderAmount is a Type of int but salary for Employee class is a type of double.

Here’s the definition of BubbleSorter.cs:

using System;

using System.Collections.Generic;

 

namespace BubbleSorter

{

    class BubbleSorter

    {

        static public void Sort<T>(IList<T> sortArray, Func<T, T, bool> comparison)

        {

            bool swapped = true;

            do

            {

                swapped = false;

                for (int i = 0; i < sortArray.Count - 1; i++)

                {

                    if (comparison(sortArray[i + 1], sortArray[i]))

                    {

                        T temp = sortArray[i];

                        sortArray[i] = sortArray[i + 1];

                        sortArray[i + 1] = temp;

                        swapped = true;

                    }

                }

            } while (swapped);

 

 

        }

    }

}

 

Here’s the definition of Employee.cs:

 

namespace BubbleSorter

{

    class Employee

    {

        public Employee(string name, decimal salary)

        {

            this.Name = name;

            this.Salary = salary;

        }

 

        public string Name { get; private set; }

        public decimal Salary { get; private set; }

 

        public override string ToString()

        {

            return string.Format("Employee Name : {0} and Employee Salary : {1:C}", Name, Salary);

        }

 

        public static bool CompareSalary(Employee e1, Employee e2)

        {

            return e1.Salary < e2.Salary;

        }

    }

}

Here’s the definition of Customer.cs:

 

namespace BubbleSorter

{

    class Customer

    {

        public Customer(string name, int OrderAmount)

        {

            this.Name = name;

            this.OrderAmount = OrderAmount;

        }

 

        public string Name { get; private set; }

        public int OrderAmount { get; private set; }

 

        public override string ToString()

        {

            return string.Format("Customer Name : {0} and Order Amount : {1:C}", Name, OrderAmount);

        }

 

        public static bool CompareOrderAmount(Customer c1, Customer c2)

        {

            return c1.OrderAmount < c2.OrderAmount;

        }

    }

}

 

Here’s the definition of program.cs :

 

using System;

 

namespace BubbleSorter

{

    class Program

    {

        static void Main()

        {

            Console.WriteLine("----------Employee Sort based on salary -------");

            Employee[] employees =

              {

                new Employee("Prabhat", 20000),

                new Employee("Deven", 10000),

                new Employee("Vicky", 25000),

                new Employee("Kapil", 1000000.38m),

                new Employee("Rajiv", 23000),

                new Employee("Ashis", 50000)

              };

 

            BubbleSorter.Sort(employees, Employee.CompareSalary);

 

            foreach (var employee in employees)

            {

                Console.WriteLine(employee);

            }

            Console.WriteLine();

            Console.WriteLine("--------------Customer Sort based on Order Amount -----");

            // Customer Class Sort based on Order Amount using BubbleSorter class

            Customer[] customer =

              {

                new Customer("Rahul", 1800),

                new Customer("Piyush", 6000),

                new Customer("Subhranshu", 8000),

                new Customer("Prasanna", 90000),

                new Customer("Sunil", 7000),

                new Customer("Santosh", 3000)

              };

 

            BubbleSorter.Sort(customer, Customer.CompareOrderAmount);

 

            foreach (var cust in customer)

            {

                Console.WriteLine(cust);

            }

 

            Console.ReadKey();

 

        }

    }

}

Output: 

Multicast Delegates :

it is possible for a delegate to wrap more than one method. Such a delegate is known as a multicast delegate. When a multicast delegate is called, it successively calls each method in order. For this to work, the delegate signature should return a void; otherwise, you would only get the result of the last method invoked by the delegate.

Example of Multicast Delegates:

Here’s the definition of MathOperation.cs.

using System;

 

namespace MulticastDelegate

{

    class MathOperations

    {

        public static void MultiplyByThree(double value)

        {

            double result = value * 3;

            Console.WriteLine("Multiplying by 3: {0} gives {1}", value, result);

        }

 

        public static void Cube(double value)

        {

            double result = value * value * value;

            Console.WriteLine("Cubing : {0} gives {1}", value, result);

        }

    }

}

 

Here’s the definition of Program.cs

using System;

 

namespace MulticastDelegate

{

    class Program

    {

        static void Main()

        {

            Action<double> operations = MathOperations.MultiplyByThree;

            operations += MathOperations.Cube;

 

            ProcessAndDisplayNumber(operations, 3.0);

            ProcessAndDisplayNumber(operations, 7.23);

            ProcessAndDisplayNumber(operations, 2.524);

            Console.ReadKey();

        }

 

        static void ProcessAndDisplayNumber(Action<double> action, double value)

        {

            Console.WriteLine();

            Console.WriteLine("ProcessAndDisplayNumber called with value = {0}", value);

            action(value);

 

        }

    }

}

 

Output : 

Anonymous Methods : 

Up to this point, a method must already exist for the delegate to work (that is, the delegate is defined with the same signature as the method(s) it will be used with). However, there is another way to use delegates — with anonymous methods. An anonymous method is a block of code that is used as the parameter for the delegate. The syntax for defining a delegate with an anonymous method doesn’t change. It’s when the delegate is instantiated that things change. The following very simple console application shows how using an anonymous method can work :

using System;

 

namespace AnnonymousMethod

{

    class Program

    {

        static void Main()

        {

            string mid = ", Delegate,";

 

            Func<string, string> anonDel = delegate(string param)

            {

                param += mid;

                param += " with an annonymous method..";

                return param;

            };

            Console.WriteLine(anonDel("This is the example of "));

            Console.ReadKey();

        }

    }

}

 

Output :

 

 

LAMBDA EXPRESSIONS

Lambda expressions can be used whenever you have a delegate parameter type. The previous example using anonymous methods is modified here to use a lambda expression.

Example 1: 

using System;

 

namespace LembdaExpression

{

    class Program

    {

        static void Main()

        {

            string mid = ", Delegate,";

            Func<string, string> lambda = param =>

            {

                param += mid;

                param += " with an annonymous method..";

                return param;

            };

            Console.WriteLine(lambda("This is the example of "));

            Console.ReadKey();

        }

    }

}

 

Output : 

 

Example 2 : 

 

using System;

namespace LembdaExpression

{

    class Program

    {

        static void Main()

        {

            SimpleDemos();

 

            int someVal = 5;

            Func<int, int> f = x => x + someVal;

 

            someVal = 7;

 

            Console.WriteLine(f(3));

            Console.ReadKey();

        }

 

 

        static void SimpleDemos()

        {

            Func<string, string> oneParam = s => String.Format("change uppercase {0}", s.ToUpper());

            Console.WriteLine(oneParam("hello"));

 

            Func<double, double, double> twoParams = (x, y) => x * y;

            Console.WriteLine(twoParams(3, 2));

 

            Func<double, double, double> twoParamsWithTypes = (double x, double y) => x * y;

            Console.WriteLine(twoParamsWithTypes(4, 2));

 

            Func<double, double> operations = x => x * 2;

            operations += x => x * x;

 

            ProcessAndDisplayNumber(operations, 3.0);

            ProcessAndDisplayNumber(operations, 5.44);

            ProcessAndDisplayNumber(operations, 2.674);

            Console.WriteLine();

        }

 

        static void ProcessAndDisplayNumber(Func<double, double> action, double value)

        {

            double result = action(value);

            Console.WriteLine(

               "Value is {0}, result of operation is {1}", value, result);

        }

 

    }

}

 

Output :

EVENTS: 

Events are based on delegates and offer a publish/subscribe mechanism to delegates. You can find events everywhere across the framework. In Windows applications, the Button class offers the Click event. This type of event is a delegate. A handler method that is invoked when the Click event is fired needs to be defined, with the parameters as defined by the delegate type.

In the code example shown in this section, events are used to connect CarDealer and Consumer classes. The CarDealer offers an event when a new car arrives. The Consumer class subscribes to the event to be informed when a new car arrives.

Event Publisher

We start with a CarDealer class that offers a subscription based on events. CarDealer defines the event named NewCarInfo of type EventHandler<CarInfoEventArgs> with the event keyword. Inside the method NewCar, the event NewCarInfo is fi red by invoking the method RaiseNewCarInfo.

The implementation of this method verifies if the delegate is not null, and raises the event

Here’s the definition of CarDelear.cs

using System;

 

namespace EventSample

{

    public class CarInfoEventArgs : EventArgs

    {

        public CarInfoEventArgs(string car)

        {

            this.Car = car;

        }

 

        public string Car { get; private set; }

    }

 

    public class CarDealer

    {

        public event EventHandler<CarInfoEventArgs> NewCarInfo;

 

        public void NewCar(string car)

        {

            Console.WriteLine("CarDealer, new car {0}", car);

 

            RaiseNewCarInfo(car);

        }

 

        protected virtual void RaiseNewCarInfo(string car)

        {

            EventHandler<CarInfoEventArgs> newCarInfo = NewCarInfo;

            if (newCarInfo != null)

            {

                newCarInfo(this, new CarInfoEventArgs(car));

            }

        }

    }

}

 

Here’s the example of Consumer.cs

using System;

 

namespace EventSample

{

    public class Consumer

    {

        private string name;

 

        public Consumer(string name)

        {

            this.name = name;

        }

 

        public void NewCarIsHere(object sender, CarInfoEventArgs e)

        {

            Console.WriteLine("{0}: car {1} is new", name, e.Car);

        }

    }

}

 

Here’s the example of program.cs

 

namespace EventSample

{

    class Program

    {

        static void Main()

        {

            var dealer = new CarDealer();

 

            var michael = new Consumer("Maruti");

            dealer.NewCarInfo += michael.NewCarIsHere;

 

            dealer.NewCar("Suzuki");

 

            var nick = new Consumer("Honda");

            dealer.NewCarInfo += nick.NewCarIsHere;

 

            dealer.NewCar("I10");

 

            dealer.NewCarInfo -= michael.NewCarIsHere;

 

            dealer.NewCar("Racing");

        }

    }

}

 

Output:

 


Recommended Book for MVC : List of Some Important books for .NET Framework, C#, MVC, WCF



(Visited 151 times, 1 visits today)

Leave a Reply

Your email address will not be published. Required fields are marked *