C# 高级编程(笔记4)
第8章 委托、Lambda表达式和事件
1.多播委托:一个委托中包含多个方法
如果调用多播委托,就可以按顺序连续调用多个方法,为此,委托的签名就必须返回void;否则,就只能得到委托调用的最后一个方法的结果,前面调用的方法返回结果被后面调用的方法返回的结果给覆盖了。
通过一个委托调用多个方法还可能导致一个大问题。多播委托包含一个逐个调用的委托集合。如果通过委托调用的其中一个方法抛出一个异常,整个迭代就会停止。
static void One() { Console.WriteLine("One"); throw new Exception("Error in one"); } static void Two() { Console.WriteLine("Two"); } Action d1 = One; d1 += Two; try { d1(); } catch (Exception) { Console.WriteLine("Exception caught"); } ----委托只调用了第一个方法。因为第一个方法抛出了一个异常,所以委托的迭代会停止,不再调Two()方法
在这种情况下,为了避免这个问题,应自己迭代方法列表。Delegate类定义GetInvocationList()方法,它返回一个Delegate对象数组。现在可以使用这个委托调用与委托直接相关的方法,捕获异常,并继续下一次迭代。
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"); } } } ----得到的结果是: one EXception caught TWo
2.可以为每个参数以及返回类型自定义委托
public delegate double deleOp(double s);
3.还可以使用Action<T>和Func<T>委托
Action<T>委托:只带void返回类型
泛型Action<T>委托表示引用一个void返回类型的方法。这个委托类存在不同的变体,可以传递至多16种不同的参数类型。没有泛型参数的Action类可调用没有参数的方法。Action<in T>调用带一个参数的方法,Action<in T1, in T2>调用带两个参数的方法,Action<in T1, in T2,in T3, in T4,in T5, in T6,in T7, in T8>调用带8个参数的方法。
Func<T>允许调用带返回类型的方法。与Action<T>类似,Func<T>也定义了不同的变体,至多也可以传递16个参数类型和一个返回类型。Func<out TResult>委托类型可以调用带返回类型且无参数的方法,Func<in T, out TResult>调用带一个参数和一返回值的方法,Func<in T1, in T2,in T3,in T4,out TResult>调用带4个参数和一返回值的方法
4.匿名方法
还可以通过匿名方法来使用委托,
static void Main() { 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")); }
----Func <string, string>委托接受一个字符串参数,返回一个字符串。anonDel是这种委托类型的变量。不是把方法名赋予这个变量,而是使用一段简单的代码:它前面是关键字de1egate,后面是一个字符串参数;
----在使用匿名方法时,必须遵循两条规则。在匿名方法中不能使用跳转语句(break、goto或continue)跳到该匿名方法的外部,反之亦然:匿名方法外部的跳转语句不能跳到该匿名方法的内部。在匿名方法内部不能访问不安全的代码。另外,也不能访问在匿名方法外部使用的ref和out参数。但可以使用在匿名方法外部定义的其他变量。
----从c#3.0开始,可以使用Lambda表达式替代匿名方法
5.Lambda表达式
static void Main() { string mid = ", middle part,"; Func<string, string> lambda = param => { param += mid; param += " and this was added to the string."; return param; }; Console.WriteLine(lambda("Start of string")); }
----Lmbda运算符"=>"的左边列出了需要的参数。Lambda运算符的右边定义了赋予委托变量lambda的方法的实现代码[即"=>"的右边为方法体]。
Lambda表达式有几种定义参数的方式:
A.如果只有一个参数,只写出参数名就足够了。下面的Lambda表达式使用了参数s。因为委托类型定义了一个string参数,所以s的类型就是string。实现代码调用String.Format()方法来返回一个字符串,
Func<string, string> oneParam = s => String.Format("change uppercase {0}", s.ToUpper()); Console.WriteLine(oneParam("test"));
B.如果委托使用多个参数,就把参数名放在括号中。这里参数x和y的类型是double,由Func<double,double,double>委托定义:
Func<double, double, double> twoParams = (x, y) => x * y; Console.WriteLine(twoParams(3, 2));
上面也可以写成:Func<double, double, double> twoParams = (x, y) => {return x * y;};
----如果Lambda表达式只有一条语句,在方法块内就不需要括号和return语句,因为编译器会添加一条隐式的return语句。
----但是,如果在Lambda表达式的实现代码中需要多条语句,就必须添加括号和return语句:
----为了方便,可以在括号中给变量名添加参数类型:
Func<double, double, double> twoParams = (double x, double y) => x * y;
6.事件
首先,事件是基于委托的,也可以说事件变量其实就是一个委托类型的变量,如
public event EventHandler<CarInfo> newCarEvent;//声明一个事件变量 //EventHandler是一个委托类型,其原型为: public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e) where TEventArgs : EVentArgs;//支持泛型委托 //泛型TEventArgs是关于事件的一些附加信息,就是事件发布者要告诉订阅者的信息
好,既然是委托,那在要用到该事件的地方就得定义与委托原型一样的方法,这个就是事件订阅者要做的事情,拿客户买汽车的为例:
//以下是两个订阅者 //宝马汽车客户 public class BMWBuyer { public void Buy(Object o, CarInfo e) { Console.WriteLine("{0}:{1}", "BMWBuyer", e.GetCarInfo()); } } //大众汽车客户 public class AutoDasBuyer { public void Buy(Object o, CarInfo e) { Console.WriteLine("{0}:{1}", "AutoDasBuyer", e.GetCarInfo()); } }
----CarInfo类就是委托原型中的TEventArgs类型,也就是发布者要告诉订阅者的信息类,必须继承自EVentArgs类
//事件附加信息CarInfo是一个类,必须继承自EventArgs,这个类中包含了本次事件的所有详细信息 public class CarInfo : EventArgs { private String carName; public CarInfo(String name) { this.carName = name; } public String GetCarInfo() { return carName; } }
现在,事件订阅者有了,事件的详细信息也有了,还差一个事件发布者
public class CarFactory { public event EventHandler<CarInfo> newCarEvent; public void StartEvent(String carName) { if (newCarEvent != null) { newCarEvent(this, new CarInfo(carName));//触发事件 } } }
最后一步是把事件发布者与订阅者关联起来,通过“+=”,很简单
static void Main(string[] args) { CarFactory factory = new CarFactory(); // BMWBuyer bmwBuyer = new BMWBuyer(); factory.newCarEvent += bmwBuyer.Buy;//关联发布者与订阅者 factory.StartEvent("BMW");//触发事件 // AutoDasBuyer autoBuyer = new AutoDasBuyer(); factory.newCarEvent += autoBuyer.Buy; factory.StartEvent("AutoDaus"); Console.ReadKey(); }
//通过将发布者与订阅者关联上,发布者只要发布消息,所有的订阅者都可以收到这个消息