摘要:
- 委托
- Lambda表达式
- 事件
1.委托是寻址方式的.NET版本。与C++的函数指针区别:委托时类型安全的类,定义了返回类型和参数的类型。
委托是一种特殊类型的对象,特殊之处在于,我们之前定义的所有对象都包含数据,而委托包含的只是一个或多个方法的地址。
2.声明委托
- 定义要使用的委托,即告诉编译器这种类型的委托表示哪种类型的方法。(编译器在后台将创建表示该委托的类)
- 创建该委托的一个或多个实例 。
delegate void IntMethodInvoker(int x);//必须提供方法的签名和返回类型等细节,类型安全性非常高
定义委托可以在定义类的任何相同地方定义,可以在另一个类的内部定义,也可以在任何类的外部定义,还可以在名称空间中把委托定义为顶层对象。
委托派生自基类System.MulticastDelegate,基类又派生自System.Delegate
与类不同的是,创建类的实例称为“对象”,而创建的委托的实例仍称为委托,必须从上下文确定其含义。
3.使用委托
在C#中,委托在语法上总是接受一个参数的构造函数,这个参数就是委托引用的方法。
private delegate string GetString(); static void Main() { int x = 40; GetAString firstStringMethod = new GetAString(x.ToString);//ToString()是实例方法(不是静态),因此需要指定实例x和方法名来正确地使用委托 ... }
使用委托实例的名称,并通过括号中的等效参数来调用委托
Console.WriteLine("String is {0}",firstStringMethod()); //等价于 //Console.WriteLine("String is{0}",x.ToString());
也可以使用firstStringMethod.Invoke();来调用,是一样的。
委托推断:为了减少输入量,只要需要委托实例,可以只传送地址。
GetAString firstStringMethod = x.ToString;
委托推断在需要委托实例的任何地方使用,也可以用于事件。
使用委托的另外一种方式是:把方法组合到一个数组中,这样可以在循环中调用不同的方法。
delegate double DoubleOp(double x); static void Main() { DoubleOp[] operations = {MathOperations.MultipleByTwo,MathOperations.Square}; ... }
4.Action<T>和Func<T>委托
Action<T>委托表示引用一个void返回类型的方法。因为这个委托类存在不同的变体,所以可以传递至多16种不同的参数类型。
- Action<int T1,int T2>:调用带两个参数的方法
Func<T>委托允许调用带返回类型的方法,与Action<T>类型,Func<T>也定义了不同的变体,至多也可以传递16种不同的参数类型和一个返回类型。
- Func<out TResult>:调用带返回类型且无参数方法
- Func<int T,out TResult>:调用带一个参数的方法
示例使用Func<Int T,out TResult>委托
delegate double DoubleOp(double x); Func<double,double>[] operations = {MathOperations.MultipleByTwo,MathOperations.Square};
那么调用时方法应为
private 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); }
冒泡排序案例体会委托的用途:
/// <summary> /// 冒泡排序的泛型版本 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="sortArray"></param> /// <param name="comparison"></param> 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); }
参数2实则委托,定义了函数签名,即包含连个参数且返回值为布尔类型,本质上传递的是一个函数指针。
5.多播委托
定义:包含多个方法的委托,可按顺序连续调用多个方法,为此委托的签名就必须返回void,否则只能得到委托调用的最后一个方法的结果。
可以使用返回类型为void的Action<T1>委托:
Action<double> operations = MathOperations.MultiplyByTwo; operations += MathOperations.Square;
多播委托可以识别运算符“+”和“+=”,还识别运算符“-”和“-=”,以从委托中删除方法调用。
注意:
- 多播委托对调用方法链的顺序并未正式定义,因此应避免编写依赖于以特定顺序调用方法的代码。
- 多播委托调用的其中一个方法抛出异常,整个迭代就会停止。
为了避免第二个问题应自己迭代方法列表,如:
static void Main() { Action d1 = One; d1 += Two; Delegate[] delegates = d1.GetInvocationList();//返回委托数组 foreach(Action d in delegates) { try { d(); } catch(Exception) { Console.WriteLine("Exception caught"); } } }
6.匿名方法
另外一种使用委托的方法,是用作委托的参数的一段代码。
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("Start of string"));
匿名方法的优点:减少代码,不必定义由委托使用的方法,有助于降低代码的复杂性,特别是定义事件时。
匿名方法的缺点:代码执行的不太快,编译器仍定义了一个方法,只是名称是自动指定的。当需多次编写功能相同的方法,则不建议使用。
必须遵循的规则:
- 在匿名方法中不能使用跳转语句(break、goto或continue)跳到该匿名方法的外部,反之亦然:匿名方法外部的跳转语句不能跳到该方法内部。
- 在匿名方法内部不能访问不安全的代码。也不能访问在匿名方法外部使用的ref和out参数,但可以访问方法外部定义的其他变量。
7.Lambda表达式
从C#3.0开始,可以使用Lambda替代匿名方法。
将上面示例修改为Lambda语法:
string mid = ",middle part,"; Func<string,string> anonDel = param => { param += mid; param += " and this was added to the string."; return param; }; Console.WriteLine(anonDel("Start of string"));
单个参数和多个参数定义方式:
Func<string,string> oneParam = s => String.Format("change uppercase {0}",s.ToUpper());//单参 Func<double,double,double> TwoParams = (x,y) => x*y;//多参 Func<double,double,double> twoParamsWithTypes = (double x,double y) => x*y;//添加参数类型
注意:如果Lambda表达式只有一条语句,在方法块内就不需要花括号和return语句,因为编译器会添加一条隐式的return语句。
Lambda表达式外部的变量:
通过Lambda表达式可以访问Lambda表达式外部的变量,但是要比较小心,特别是通过另一个线程调用Lambda时,我们可能不知道进行了这个调用,也不知道当前外部变量的值是什么。
编译器其实在定义Lambda表达式时创建了一个匿名类,外部变量通过构造函数传递进来。
8.事件
事件基于委托,为委托提供一种发布/订阅机制。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
namespace Wrox.ProcSharp.Delegates { //事件发布程序 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); if(NewCarInfo != null) { NewCarInfo(this, new CarInfoEventArgs(car)); } } } //事件监听器 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); } } //订阅 class Program { static void Main() { var dealer = new CarDealer(); var michael = new Consumer("Michael"); dealer.NewCarInfo += michael.NewCarIsHere; dealer.NewCar("Mercedes"); } } }
作为一个约定,事件一般使用带两个参数的方法:
- 第一个参数是一个对象,包含事件的发送者
- 第二个参数提供了事件的相关信息,随不同的事件类型而不同。
对于EventHandler<TEventArgs>,第一个参数是object类型,第二个参数是T类型,还有一个关于T的约束:必须派生自基类EventArgs
public event EventHandle<TEventArgs>(object sender,TEventArgs e) where TEventArgs:EventArgs
定义事件的简化记法,编译器自动创建委托变量
public event EventHandler<CarInfoEventArgs> NewCarInfo;
完整记法
private delegate EventHandler<CarInfoEventArgs> newCarInfo; public event EventHandler<CarInfoEventArgs> NewCarInfo { add { newCarInfo += value; } remove { newCarInfo = value; } }
与多播委托一样,方法调用顺序无法保证,如果要控制调用,需使用Delegate类的GetInvocationList()方法显式调用。
8.1弱事件