委托、Lambda表达式和事件

1.1 引用方法



     委托是寻址方法的 .NET 版本。委托是类型安全的类。它定义了返回类型和参数的类型。委托类不仅包含对方法的引用,也可以包含对多个方法的引用。

Lambda 表达式与委托直接相关。当参数是委托类型时,就可以使用Lambda表达式实现委托引用的方法。

1.2委托



     当要把 方法 传送给其他方法时,需要使用 委托。以下两个示例:

  • 启动线程和任务——在C# 中,可以告诉计算机并行运行某些新的执行序列同时运行当前的任务。这种序列就是称为线程,在其中一个基类 System.Threading.Thread 的一个实例上使用方法 Start(),就可以启动一个线程。如果要告诉俺计算机启动一个新的执行序列,就必须说明要在哪里启动该序列。必须为计算机提供开始启动的方法的细节,即Thread类的构造函数必须带有一个参数,该参数定义了线程调用的方法。
  • 事件——一般是通知代码发生了什么事件。GUI 编程主要处理事件,在引发事件时,运行库需要知道应执行哪个方法。这就需要把处理事件的方法作为一个参数传递给委托的。

委托只是一种特殊类型的对象,其特殊之处在于,我们以前定义的所有对象都包含数据,而委托包含的只是一个或多个方法的地址。

1.2.1 声明委托



               在C#中使用一个类时,分两个阶段。首先,需要定义这个类,即告诉编译器这个类由什么字段和方法组成。然后(除非只使用静态方法),实例化类的一个对象。使用委托时,也需要经过这两个步骤。首先必须定义要使用的委托,对于委托,定义它就是告诉编译器这种类型的委托表示哪种类型的方法,然后,必须创建该委托的一个或多个实例。编译器在后台将创建表示该委托的一个类。

定义委托的语法如下:

delegate void IntMethodInvoker(int x);

上面示例中,定义了一个委托IntMethodInvoker,并制定该委托的每个实例都可以包含一个方法的引用,该方法带有一个int 参数,并返回void。理解委托的一个要点是它们的类型安全性非常高。在定义委托时,必须给出它所表示的方法的 签名和返回类型 等全部细节。

例:delegate double TwoLongsOp(long first,long second);

或 delegate string GetAString();

委托的语法类似于方法的定义,但没有方法体,定义的前面要加上关键字 delegate。因为定义委托基本上定义一个新类,所以可以在定义类的任何相同地方定义委托,也就是说,可以在另一个类的内部定义,也可以在任何类的外部定义,还可以在名称空间中把委托定义为 顶层对象。根据定义的可见性,和委托的作用域。可以在委托的定义上应用任意常见的访问修饰符。public、 private、 protected等。

委托实现为派生自基类System.MulticastDelegate 的类,System.MulticastDelegate 又派生自基类System.Delegate。

在创建委托时,所创建的委托的实例仍称为委托。

    1.2.2 使用委托



               下面的例子来说明使用委托:

private delegate string GetAString();

static void Main()

{

int x=40;

GetAString firstStringMethod=new GetAString(x.ToString());

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

}

using System;

namespace DelegateTest

{

class Program

{

private delegate string GetAString();

static void Main(string[] args)

{

int x = 40;

GetAString fisrtMethod = new GetAString(new Program().SayHi);

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

//与 Console.WriteLine("String is {0}",firstMethod.Invoke());

}

private string SayHi()//必须匹配定义委托时的签名。

{

return "Say hi!";

}

}

}

//结果为:String is Say hi!

在上面的代码中,实例化了类型为GetAString 的一个委托,并对它进行初始化,使它引用整形变量 x 的ToString() 方法。在C#中,委托在语法上总是接受一个参数的构造函数,这个参数就是委托引用的方法。这个方法必须匹配最初定义委托时的签名。所以在这个示例中,如果用不带参数并返回一个字符串的方法来初始化 firstStringMethod 变量,就会产生一个编译错误。

实际上,给委托实例提供圆括号与调用委托类的 Invoke() 方法完全相同。因为 firstStringMethod 是委托类型的一个变量,所以C# 编译器会用 firstStringMethod.Invoke() 代替 firstString()。

firstStringMethod()----->firstStringMethod.Invoke();

委托推断:只需要委托实例,就可以只传送地址的名称。委托推断可以在需要委托实例的任何地方使用。委托推断也可以用于事件,因为事件基于委托。

:调用上述方法名时输入形式不能为 x.ToString()(不要输入圆括号),也不能把它传送给委托变量。输入圆括号调用一个方法。调用 x.ToString() 方法会返回一个不能赋予委托变量的字符串对象。只能把方法的地址赋予委托变量。


 ***委托的一个特征是它们的类型时安全的,可以确保被调用的方法的签名是正确的,但有趣的是,它们不关心在什么类型的对象上调用该方法,甚至不考虑该方法时静态方法,还是实例方法。

***给定委托的实例可以引用任何类型任何对象上的实例方法或静态方法——只要方法的签名匹配于委托的签名即可。


实例:

using System;

namespace DelegateTest

{

class Program

{

delegate double DoubleOp(double x);//定义委托

static void Main(string[] args)

{

DoubleOp[] operations = { MathOperations.MultiplyByTwo,MathOperations.Square};

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

{

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

Console.WriteLine(operations[i]);

ProcessAndDisplayNumber(operations[i], 3.0);

ProcessAndDisplayNumber(operations[i], 7.32);

ProcessAndDisplayNumber(operations[i], 16.23);

Console.WriteLine();

}

}

static void ProcessAndDisplayNumber(DoubleOp actions, double value)

{

double result = actions(value);

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

}

}

}

 

          1.2.3 Action<T> 和 Func<T> 委托


泛型 Ation<T> 委托表示引用一个 void 返回类型的方法。这个委托类存在不同的变体,可以传递至多16种不同的参数类型。没有泛型参数的Action的类可以调用没有参数的方法。

Action<in T> 或 Action< in T1, in T2>

Func<T> 委托可以以类似的方式使用。Func<T>允许调用带返回类型的方法。与Action<T> 类似,Func<T> 也定义了不同的变体,至多也可以传递16个参数类型和一个返回类型。

Func<in T,out TResult> 或 Func<in T1,in T2,in T3,in T4,out TResult>

上节的 delegate double DoubleOp(double x);  也可以使用 Func<in T, out TResult>委托。

如下所示:

Func<double,double> operations={MathOperations.MultiplyByTwo,MathOperations.Square};

调用过程如下

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

{

double result=action(value);

Console.WriteLine("Value is {0},result of operations is {1}",value ,result);

}

冒泡排序示例:(BubbleSorter示例)

using System;

using System.Collections.Generic;

namespace DelegateBubbleSort

{

class BubbleSort

{

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);

}

}

}

using System;

namespace DelegateBubbleSort

{

class Employee

{

public 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("{0},{1:C}", Name, Salary);

}

public static bool CompareSalary(Employee e1,Employee e2)

{

return e1.Salary < e2.Salary;

}

}

}

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

namespace DelegateBubbleSort

{

class Program

{

static void Main(string[] args)

{

Employee[] empolyee =

{

new Employee("Bug",1000),

new Employee("PSD",10000),

new Employee("SSSD",1500)

};

BubbleSort.Sort(empolyee, Employee.CompareSalary);

foreach (var emp in empolyee)

{

Console.WriteLine(emp);

}

}

}

}

          1.2.4 多播委托


前面使用的每个委托都只包含一个方法调用。调用委托的次数与调用方法的次数相同。如果要调用多个方法,就需要多次显式调用这个委托。但是,委托也可以包含多个方法,这种委托称为多播委托。

如果调用多播委托,就可以按顺序连续调用多个方法,为此,委托的签名就必须返回void;否则,就只能得到委托调用的最后一个方法的结果。

【  通过一个委托调用多个方法还可能导致一个大问题。多播委托包含一个逐个调用的委托集合,如果通过委托调用的其中一个方法抛出一个异常,整个迭代就会停止。】

using System;

namespace MutiplyDelegate

{

class Program

{

static void One()

{

Console.WriteLine("One!");

throw new Exception("Error in One!");

}

static void Two()

{

Console.WriteLine("Two!");

}

static void Main(string[] args)

{

Action dl = One;

dl += Two;

try

{

dl();

}

catch (Exception )

{

Console.WriteLine("I caught you!");

}

}

}

}

//结果

One!

I caught you!

上面例子中,委托只调用了第一个方法,因为第一个方法抛出了一个异常。所以委托的迭代会停止,不再调用Two()方法。没有指定调用方法的顺序时,结果会有所不同。

在这种情况下,为了避免这个问题,应自己迭代方法列表。 Delegate类定义 GetInvocationList()方法,它返回一个Delegate 对象数组。源码如下:

using System;

namespace MutiplyDelegate

{

class Program

{

static void One()

{

Console.WriteLine("One!");

throw new Exception("Error in One!");

}

static void Two()

{

Console.WriteLine("Two!");

}

static void Main(string[] args)

{

//Action dl = One;

//dl += Two;

//try

//{

//    dl();

//}

//catch (Exception )

//{

//    Console.WriteLine("I caught you!");

//}

Action dl = One;

dl += Two;

Delegate[] delegates = dl.GetInvocationList();

foreach (Action d in delegates)

{

try

{

d();

}

catch (System.Exception ex)

{

Console.WriteLine(ex.Message);

}

}

}

}

}

//结果

One!

Error in One!

Two!

1.2.5匿名方法

通过匿名方法是用作委托的参数的一段代码。

用匿名方法定义委托的语法与前面的定义并没有区别。但在实例化时,就有区别了。

using System;

namespace MutiplyDelegate

{

class Program

{

static void Main(string[] args)

{

string mid = ", middle part,";

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

{

param += mid;

param += " and this was added to the string .";

return param;

};

Console.WriteLine(anonDel("Strat of !"));

}

}

}

结果:

Strat of !, middle part, and this was added to the string .

Func<string,string> 委托接受一个字符串参数,返回一个字符串。anonDel 是这种委托类型的变量。不是把方法名赋予这个变量,而是使用一段简单的代码: 它前面是关键字 delegate ,后面是一个字符串参数。在调用委托时,把一个字符串作为参数传递,将返回的字符串输出到控制台上。

【在使用匿名方法时,必须遵循两条规则。

  1. 在匿名方法中不能使用跳转语句(break,goto,continue)跳到该匿名方法的外部,反之亦然:匿名方法外部的跳转语句不能呢个跳到匿名方法的内部。
  2. 在匿名方法内部不能访问不安全的代码。另外也不能访问在匿名方法外部使用的 ref 和 out参数 。但可以使用在匿名方法外部定义的其他变量】

如果需要用匿名方法多次编写同一个功能,就不要使用匿名方法。

时间: 2024-10-13 22:30:32

委托、Lambda表达式和事件的相关文章

委托、Lambda表达式、事件系列07,使用EventHandler委托

谈到事件注册,EventHandler是最常用的. EventHandler是一个委托,接收2个形参.sender是指事件的发起者,e代表事件参数. □ 使用EventHandler实现猜拳游戏 使用EventHandler实现一个猜拳游戏,每次出拳,出剪刀.石头.布这三者的其中一种. 首先抽象出一个被观察者,其中提供了事件,提供了执行事件的方法. public class FistGame { public string FistName { get; set; } public event

委托、Lambda表达式、事件系列05,Action委托与闭包

来看使用Action委托的一个实例: static void Main(string[] args) { int i = 0; Action a = () => i++; a(); a(); Console.WriteLine(i); } 结果是期望能的2.但令人好奇的是:栈上的变量i是如何传递给Action委托的? 反编译进行查看,首先看Main方法对应的IL代码: 再看c_DisplayClass1的IL代码: 从中可以看出:→在托管堆上创建了一个名为c_DisplayClass1的实例→把

委托、Lambda表达式、事件系列04,委托链是怎样形成的, 多播委托

在"委托.Lambda表达式.事件系列01,委托是什么,委托的基本用法,委托的Method和Target属性"中,反编译委托,发现委托都是多播委托. 既然委托是多播委托,我们可以通过"+="把多个方法赋给委托变量,这样就形成了一个委托链, 它是怎样形成的?来看下面的例子: namespace ConsoleApplication3 { internal delegate void MySayDel(string msg); class Program { stati

委托、Lambda表达式、事件系列06,使用Action实现观察者模式

在"实现观察者模式(Observer Pattern)的2种方式"中,曾经通过接口的方式.委托与事件的方式实现过观察者模式.本篇体验使用Action实现此模式. 就举一个足球场上的例子,当裁判吹响终场哨,胜队庆祝,失败队落寞.把裁判看作是被观察者,比赛中的两队看作是观察者. 裁判作为被观察者需要提供一个Action委托供观察者方法注册. public class Referee { public Action DoSth; public void ISayGameOver() { Co

委托、Lambda表达式、事件系列03,从委托到Lamda表达式

在"委托.Lambda表达式.事件系列02,什么时候该用委托"一文中,使用委托让代码简洁了不少. namespace ConsoleApplication2 { internal delegate bool MyCalculateDelegate(int val); class Program { static void Main(string[] args) { IEnumerable<int> source = new List<int>(){2, 3, 4

委托、Lambda表达式、事件系列02,什么时候该用委托

假设要找出整型集合中小于5的数. static void Main(string[] args) { IEnumerable<int> source = new List<int>(){2, 3, 4, 5, 6, 7, 8, 9,10, 11}; var result = GetNumbersLessThanFive(source); foreach (int n in result) { Console.WriteLine(n); } } static IEnumerable&

个人总结:匿名委托+内置委托+Lambda表达式

匿名委托+内置委托+Lambda表达式 概要 在实际开发中,项目开发框架不同,用到的技术是不一样的,这里总结一下用Linq开发,ORM之EF开发会大量使用的Lambda表达式; 正文 之前提过.Net有一套扩展在IEumberable<T>泛型接口的扩展方法,本文是从这个基础继续总结; 1.void Action<in T>: 先拿一个ForEach()来讲: public void ForEach(Action<T> action); 这个Action<T>

C# 委托、Lambda表达式和事件——学习总结

1.概括 1.1.委托是寻址方法的.NET版本,类似C++中的指针.委托可以理解成指向函数的指针,它是类型安全的,定义了具体的参数和返回值. ——定义一个委托,实际上是定义一个类.委托是对方法的引用,如方法Func,把其功能交给委托的类来实现. ——委托的作用:结合泛型,可以实现功能上的扩展(如针对整型的函数,可以通过委托指向多种函数,实现类中某个属性的比较).Lambda表达式需要使用委托定义.事件需要使用到委托. 1.2.Lambda表达式与委托直接相关.Lambda表达式可以理解成一个简单

委托、匿名函数、Lambda表达式和事件的学习

委托: 还记得C++里的函数指针么?大家可以点击这里查看一下以前的笔记.C#的委托和C++中的函数指针效果一致. 当我们需要将函数作为对象进行传递和使用时就需要用到委托. 下面我们看一个例子: 1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace Test 8 { 9 cl