Delegates, Events and Lambda Expression

The content and code of this article is referenced from book Pro C#5.0 and the .NET 4.5 Framework by Apress. The intention of the writing is to review the konwledge and gain better understanding of the .net framework. 

 

Up to this point, most of the applications you developed 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 callback mechanism.

Under the .net platform, the delegate type is the preferred means of defining and responding to callbacks. Essentially, the .net delegate type is a type-safe object that "points to " a methods or list of methods can be invoked at a later time.

1. Understanding the .net delegate type

Traditionaly, we use c-style function to achieve callback in button-cliking, mouse-moving, menu-selecting. However, the problem with the approach is that it represents little more than a raw address in memory, and therefore, there might be frequent source of bugs, hard crashes and runtime disasters.

In the .net framework, callback function is accomplished in a much safer and more object-oriented manner using delegates. In essence, a delegate is a type-safe object that points to another method in the application, which can be invoked at a later time. Delegate contains three important pieces of information - (1) the address of the method on which it makes calls (2) the parameters of the methods (3) the return type of this method

1.1 Defining a delegate type

In .net, you use keyword delegate to define a delegate type, and the name of the delegate can be whatever you desire. However, you must define the delegate to match the signature of the method it will point to .

public delegate int BinaryOp(int x, int y);

As example, the delegate BinaryOp can points to a method that returns an integer and takes two integers as input parameters. When c# compiler processes delegate types, it automatically generates a sealed class deriving from System.MulticastDelegate, and defines three public methods - BeginInvoke, EndInvode, and Invoke. BeginInvoke and EndInvoke provide the ability to call method asynchronously on a separate thread of execution.

1         sealed class BinaryOp : System.MulticastDelegate   //generated at the background
2         {
3             public int Invoke (int x, int y);
4             public IAsyncResult BeginInvoke (int x, int y, AsyncCallback cb, object state);
5             public int EndInvoke (IAsyncResult result);
6         }

Delegates can also point to methods that contain any number of out or ref parameters.

public delegate int BinaryOp(out int x, ref bool z, int y);

To summarize, a C# delegate type definition results in a sealed class with three compiler-generated methods whose prameter and return types are based on the delegate‘s declaration.

1.2 System.MulticaseDelegate and System.Delegate base classes

When you build a type using delegate, you are indirectly declaring a class type that derives from System.MulticastDelegate. And it provides descendants with access to a list that contains the addresses of the methods maintained by the delegate objects.

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, Target returns an object that represents the method maintained by the delegate. 
Combine() Adds a method to the list maintained by the delegate. You can also use += operator as a shorthand notation
GetInvocationList() This method returns an array of System.Delegate objects, each representing a paticular method that may be invoked. 
Remove(), RemoveAll() remove a method from the delegate‘s invocation list. You can also use -= operator as a shorthand notation.
   

1.3 One simple delegate example

 1     public delegate int BinaryOp(int x, int y);
 2     public class SimpleMath
 3     {
 4         public static int Add (int x, int y)
 5         {
 6             return x + y;
 7         }
 8
 9         public static int Substract(int x, int y)
10         {
11             return x - y;
12         }
13     }
14
15     class MainClass
16     {
17         public static void Main (string[] args)
18         {
19             //delegate object that points to Add method
20             BinaryOp op = new BinaryOp (SimpleMath.Add);
21             Console.WriteLine (op (10, 20)); //invoke the add method
22         }
23     }

Under the hood, the runtime actually calls the compiler-generated Invoke() method on you MulticastDelegate derived class.

1.4 Sending object state notification using delegate

Now, let‘s look at a more realistic delegate example.

 1     public class Car
 2     {
 3         //property
 4         public int CurrentSpeed { get; set; }
 5         public int MaxSpeed { get; set; }
 6         public string PetName { get; set; }
 7
 8         private bool carIsDead;
 9
10         //constructor
11         public Car(){MaxSpeed = 100;}
12         public Car(string name, int maxSp, int currSp)
13         {
14             CurrentSpeed = currSp;
15             PetName = name;
16             MaxSpeed = maxSp;
17         }
18
19         //delegate
20         public delegate void CarEngineHandler(string msgForCaller); //delegate take one string as parameter, and returns void
21
22         private CarEngineHandler listOfHanlder;
23
24         //assign methods to delegate
25         public void RegisterWithCarEngine(CarEngineHandler methodToCall)
26         {
27             listOfHanlder = methodToCall;
28         }
29
30         public void Accelerate(int delta)
31         {
32             if (carIsDead) {
33                 if (listOfHanlder != null)
34                     listOfHanlder ("Sorry , this car is dead.."); //invoke method in delegate
35             } else {
36                 CurrentSpeed += delta;
37                 if (listOfHanlder != null && CurrentSpeed > 80) {
38                     listOfHanlder ("Careful buddy");
39                 }
40             }
41         }
42     }

In accelerate, we need to check the value against null becuase it is the caller‘s job to allocate these objects by calling register method.

1.5 Enable multicasting

In c#, a delegate object can maintain a list of methods to call, rather than just a single method.

        //assign methods to delegate
        public void RegisterWithCarEngine(CarEngineHandler methodToCall)
        {
            listOfHanlder += methodToCall;
        }

And we use += operator to achieve multicasting. When we use +=, acutally the runtime is calling Delegate.Combine().

1         public void RegisterWithCarEngine(CarEngineHandler methodToCall)
2         {
3
4             if (listOfHanlder == null)
5                 listOfHanlder += methodToCall;
6             else
7                 Delegate.Combine (listOfHanlder, methodToCall);
8         }

1.6 removing targets from delegate invocation list

The delegate also defines a static Remove() method that allows a caller to dynamically remove a method from a delegate‘s invocation list. As alternative, you can also use -= operator.

1         public void UnRegisterWithCarEngine(CarEngineHandler methodToCall)
2         {
3             Delegate.Remove(listOfHanlder, methodToCall);
4         }

1.7 Method Group conversion Syntax

        public static void Main (string[] args)
        {
            Car c1 = new Car ("car1", 120, 50);
            Car.CarEngineHandler handler = new Car.CarEngineHandler (PrintMsg); //create delegate object
            c1.RegisterWithCarEngine (handler);
        }

        public static void PrintMsg(string msg)
        {
            Console.WriteLine (msg);
        }

In the example, we create a delegate variable, then pass the variable to car method. As a simplification, C# provides a shortcut termed method group conversion, allows you to supply a direct method name, rather than a delegate object, when calling method take delegate as arguments.

        public static void Main (string[] args)
        {
            Car c1 = new Car ("car1", 120, 50);
            c1.RegisterWithCarEngine (PrintMsg); //pass method directly
        }

1.8 Understanding Generic Delegates

C# allows you to create generic delegates.

        public static void Main (string[] args)
        {
            Car c1 = new Car ("car1", 120, 50);
            c1.RegisterWithCarEngine (PrintMsg); //pass method directly
        }

1.9 The Generic Action<> and Func<> Delegates

In many cases, we can use framework‘s built-in Action<> and Func<> delegates, instead of creating many custom delegates. The Action<> delegate is defined in System namespace. You can use this generic delegate to point to a method that takes up 16 arguments and returns void.

        public static void Main (string[] args)
        {
            Action<string, int> action = new Action<string, int> (DisplayMsg);

            action ("test", 100);
        }

        static void DisplayMsg(string arg1, int arg2)
        {
            Console.WriteLine (arg1);
            Console.WriteLine (arg2);
        }

If you want to point a method has return value, you can use Func<>.

public static void Main (string[] args)
        {
            Func<int, int, int> func = new Func<int, int, int> (Add);  //the last parameter is returning type
            Console.WriteLine(func (14, 24));
        }

        static int Add(int arg1, int arg2)
        {
            return arg1 + arg2;
        }

2. C# Events 

As a shortcut, C# provides the event keyword, and you don‘t have to build custom methods to add or remove methods to delegate‘s invocation list. When the compiler processes the event keyword, you are provided with registration and unregistration methods.

        //delegate
        public delegate void CarEngineHandler(string msgForCaller); //delegate take one string as parameter, and returns void

        //create event, to handle registration and unregistration
        public event CarEngineHandler Exploded;

        public void Accelerate(int delta)
        {
            if (carIsDead) {
                if (Exploded != null)
                    Exploded ("Sorry , this car is dead.."); //invoke method in delegate
            } else {
                CurrentSpeed += delta;
                if (Exploded != null && CurrentSpeed > 80) {
                    Exploded ("Careful buddy");
                }
            }
        }

At the background, two methods are generated for event keyword, one method start with add_CarEngineHanlder and the other starts with Remove_CarEngineHandler. The caller simply make use of += and -= operators directly to register and unregister methods with delegate.

        public static void Main (string[] args)
        {
            Car car = new Car ("car1", 150, 90);
            Car.CarEngineHandler d = new Car.CarEngineHandler (PrintMsg);
            car.Exploded += d;
        }

You can even simplify the code by using method group conversion.

        public static void Main (string[] args)
        {
            Car car = new Car ("car1", 150, 90);
            car.Exploded += PrintMsg;
        }

2.1 Custom Event arguments

There is one final enhancement we could make to the Car class that mirros Microsoft‘s recommended event pattern. In general, the first parameter of the underlying delegate is System.object represents a reference to the calling object, and the second parameter is a descendant of System.EventArgs represents information regarding the event.

    public class CarEventArgs : EventArgs
    {
        public readonly string msg;
        public CarEventArgs(string message)
        {
            msg = message;
        }
    }
        //delegate
        public delegate void CarEngineHandler(object sender, CarEventArgs e); 

        //create event, to handle registration and unregistration
        public event CarEngineHandler Exploded;

        public void Accelerate(int delta)
        {
            if (carIsDead) {
                if (Exploded != null)
                    Exploded (this, new CarEventArgs("Sorry , this car is dead..")); //invoke method in delegate
            }
        }
        public static void Main (string[] args)
        {
            Car car = new Car ("car1", 150, 90);
            car.Exploded += CarBlow;
            car.Accelerate (10);
        }

        public static void CarBlow(object sender, CarEventArgs e)
        {
            Console.WriteLine(sender);
            Console.WriteLine(e.msg);
        }

2.2 Generic EventHandler<T> delegate

Given that so many custom delegates take an object and an EventArgs as parameters, you could further streamline the example by using EventHanlder<T>, where T is your own EventArgs type.

        public event EventHandler<CarEventArgs> Exploded;  //no need to delcare delegate
        public static void Main (string[] args)
        {
            Car car = new Car ("car1", 150, 90);

            EventHandler<CarEventArgs> d = new EventHandler<CarEventArgs> (CarBlow);
            car.Exploded += d;
            car.Accelerate (10);
        }

3. C# anonymous methods

When a caller wants to listen to incoming events, it must define a method that matches the signature of associated delegate. However, the custom methods are seldom used by any other parts of the program. In C#, it is possible to associate an event directly to a block of code statement at the time of event registration. Such code is termed as anonymous methods.

        public static void Main (string[] args)
        {
            Car c1 = new Car ("car", 100, 80);
            c1.Exploded += delegate {
                Console.WriteLine("test anonymous method");
            };

            c1.Exploded += delegate(object sender, CarEventArgs e) {
                Console.WriteLine("test anonymouse method two");
            };
        }

3.1 Accessing local variable

Anonymous methods are able to access the local variables of the method that defines them.

        public static void Main (string[] args)
        {
            int count = 0;

            Car c1 = new Car ("car", 100, 80);
            c1.Exploded += delegate {
                Console.WriteLine("test anonymous method");
                count++;
            };

            c1.Exploded += delegate(object sender, CarEventArgs e) {
                count++;
                Console.WriteLine("test anonymouse method two");
            };
        }

4. Understanding Lambda expression

Lambda expressions are nothing more than a very concise way to author anonymous methods and simplify how we work with .net delegate type. Before looking at the example, let‘s investigate the special delegate type in c# Predicate<T> which points to any method returning a bool and takes a single type parameter as the only input parameters. It is used in List<T> FindAll (Predicate<T> match) method to narrow down the list.

    public static void TraditionalDelegateSyntax()
        {
            List<int> list = new List<int> ();
            list.AddRange(new int[]{20, 1, 4, 8, 3, 44});

            Predicate<int> callback = new Predicate<int> (IsEvenNumber);

            List<int> eNumber = list.FindAll (callback);

            foreach (int i in eNumber) {
                Console.WriteLine (i);
            }
        }

        static bool IsEvenNumber(int i)
        {
            return (i % 2) == 0;
        }

Traditionally, we have a method IsEvenNumber to return bool value based on the input parameter. With lambda, we can improve the code with less keystrokes.

         public static void AnonymouseMethodSyntax()
        {
            List<int> list = new List<int> ();
            list.AddRange(new int[]{20, 1, 4, 8, 3, 44});

            List<int> eNumber = list.FindAll (delegate(int i) {
                return (i % 2) == 0;
            });

            foreach (int i in eNumber) {
                Console.WriteLine (i);
            }
        }
        public static void AnonymouseMethodSyntax()
        {
            List<int> list = new List<int> ();
            list.AddRange(new int[]{20, 1, 4, 8, 3, 44});

            List<int> eNumber = list.FindAll (delegate(int i) {
                return (i % 2) == 0;
            });

            List<int> eNumber1 = list.FindAll ( i => (i % 2) == 0);  //lambda expression

            foreach (int i in eNumber) {
                Console.WriteLine (i);
            }
        }

In this case, rather that directly creating a Predicate<T> delegate type and then authoring a standalone method, we are able to inline a method anonymously.

4.1 Dissecting lambda expression

A lambda expression is written by first defining a parameter list, followed by the => token, followed by a set of statements that will process these arguments. It can be understood as ArgumentsToProcess => StatementsToProcessThem

List<int> eNumber1 = list.FindAll ( i => (i % 2) == 0);  //lambda expression with implicit parameter type
            List<int> eNumber1 = list.FindAll ( (int i) => (i % 2) == 0);  //explicitly state the parameter type

4.2 Processing arguments with multiple statements

C# allows you to build lambda expressions using multiple statement blocks.

List<int> eNumber1 = list.FindAll ( (int i) =>
                {
                    Console.WriteLine("test");
                    bool isEven = (i % 2) == 0;
                    return isEven;
                });  

 

时间: 2024-10-25 07:34:43

Delegates, Events and Lambda Expression的相关文章

Part 99 Lambda expression in c#

class Program { static void Main(string[] args) { List<Person> persons = new List<Person>() { new Person{ID=101,Name="lin1"}, new Person{ID=102,Name="lin2"}, new Person{ID=103,Name="lin3"} }; Person person = perso

Android 使用Java8新特性之Lambda expression

前言 Lambda expression,java8的新特性.使用Lambda expression,可以替代只有一个函数的接口实现,告别匿名内部类,代码看起来更简洁易懂. java8还有其它一些新特性,不过在android上可能都无法使用. studio 2.x后 支持jack编译器,使用它,能使用java8的Lambda expression,但其它特性也不敢保证就能用. 注:Android SDK中集成了JDK的一些源码,有些原生JDK中的类,可能增加了新特性的一些实现,但Android中

java8函数表达式的定义[Definition of a Lambda Expression]

英文来源于:Java in a Nutshell, 6th Edition Definition of a Lambda Expression A lambda expression is essentially a function that does not have a name, and can be treated as a value in the language. As Java does not allow code to run around on its own outsi

Lambda Expression in C#

1.Expression Expression<Func<double, double>> exp = a => Math.Sin(a); 委托类型Func<double, double>,它限定生成的表达式树是一个接受double,并返回double的一元Lambda函数 <Func<double, double, double, double, double> 输入参数为4个double,返回一个double类型 static void Ma

JDK 1.8 Lambda Expression 的优点与限制

我们知道JDK 1.8新增了Lambda Expression这一特性. 那么,JDK 1.8为什么要新增这个特性呢? 这个特性给JDK 1.8带来了什么好处? 它在JDK 1.8中可以做什么?不可以做什么? 在这篇文章,我打算简单聊聊这些话题. 1. Lambda Expression是什么? Lambda Expression,又名 Anonymous function,  它起源于Alonzo Church在1936年提出的 lambda calculus. 这是数理逻辑中的一个概念,具体

C++ lambda expression

Emerged since c++11, lambda expression/function is an unnamed function object capable of capturing variables in scope. 1. syntax of a lambda expression [ captures ] <tparams>(optional)(c++20) ( params ) specifiers exception attr -> ret requires(o

java Lambda expression

Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性. Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中). 使用 Lambda 表达式可以使代码变的更加简洁紧凑. 语法 lambda 表达式的语法格式如下: (parameters) -> expression 或 (parameters) ->{ statements; } 以下是lambda表达式的重要特征: 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值. 可选的参数圆括号:一个

Delegates, Events and Singletons with Unity3D – C#

?? 在这里我将演示如何创建代表. 事件和Singletons 在一起工作.本教程为 Unity3D 编写. 我想知道这为什么?作为一个年轻的自学程序员,我经常发现自己写tons 和布尔的语句,以确定是否发生了某些event 或action .我听这些events 事件通过Coroutines 协同程序和其他方法来返回值.如果你发现自己这做得一样好,停下来 ! 欢迎光临Events事件...... 介绍最近,我一直要改善我的 C# 编程技能,以及发现自己缺乏知识,了解Events事件基础.所以,

函数式编程之根-λ表达式(lambda expression)

学习函数式编程的大图(big map)/鸟瞰图,并在没有掌握Scheme的各种语言细节之前,给出Scheme代码.这意味着我们不需要看懂源代码,而是将这里的介绍作为后续学习的大图,使自己知道身在何处: 1930s初,普林斯顿大学的逻辑学家阿伦佐·丘奇 (Alonzo Church,1903-1995) 开发出了一种新的形式系统(formal system),即拉姆达运算/演算 (λ-calculus .lambda calculus ,lambda即希腊字母λ). λ运算的核心是λ表达式,以此形