1. 概述
本章讲解如何使用 委托、lambda表达式 和 匿名方法 来创建和使用事件。
2. 主要内容
2.1 理解委托
委托是一种用方法签名形式定义的类型。可以让它指向其他方法,可以通过它调用其他方法。
① 委托支持多播(multicasting),可以用 + 和 += 操作符绑定多个方法到委托中(用 - 和 -= 取消绑定)。
public void Method1() {} public void Method2() {} public delegate void Del(); public void Multicast() { Del d = Method1; d += Method2; d(); }
② 需要绑定到委托的方法,签名不一定非得和委托完全一致。有两种特殊情况:
Covariance:如果方法的返回值类型派生自委托的返回值类型,则该方法可以绑定到该委托。
public delegate TextWriter CovarianceDel(); public StreamWriter MethodStream() { return null; } public StringWriter MethodString() { return null; } CovarianceDel del = MethodStream; del = MethodSting;
Contravariance:如果委托的参数类型派生自方法的参数类型,则该方法可以绑定到该委托。
void DoSomething(TextWriter tw) { } public Delegate void Contravariance(StreamWriter sw); Contravariance = DoSomething;
2.2 使用 Lambda 表达式
Lambda表达式是匿名方法的升级版。
Calculate calc = (x, y) => x + y; Console.WriteLine(calc(3, 4)); //Displays 7
有时候为某些事件声明专用的委托类型会感觉比较笨拙,鉴于此,系统内建了两个委托类型来简化开发
Func<...> : 支持0-16个参数,一个返回值。
Action<...>: 支持0-16个参数,无返回值。
Action<int, int> calc = (x, y) => { Console.WriteLine(x + y); }; calc(3, 4); // displays 7
*闭包(closure):如果一个委托引用了一个本地变量,并且被返回给了调用方。这时委托就具备了比该变量更长的作用域。
为了避免这种情况,编译器会生成相应的代码去保证该变量具有和委托一样长的声明周期。(纯翻译,不知道理解的对不对。。)
2.3 使用事件
订阅和发布 是 一种流行的设计模式,用于实现同类问题的重用方案。
public class Pub { public Action OnChange { get; set; } public void Raise() { if (OnChange != null) OnChange(); } } public void CreateAndRaise() { Pub p = new Pub(); p.OnChange += () => Console.WriteLine("Raise method1"); p.OnChange += () => Console.WriteLine("Raise method2"); p.Raise(); }
上述代码使用委托实现的订阅机制,有以下问题:
① 如果把method2订阅时的 += 换成 =,就会覆盖掉前面method1的订阅。
② OnChange是公共属性,外部用户可以随意调用。
为了避免上述问题,C#推出了 event 关键字。
public class Pub { public event Action OnChange = delegate { }; public void Raise() OnChange(); }
上述代码中的Action可以换成EventHandler<T>委托,可以用于传递数据。
下面是包含异常处理的代码
public class Pub { public event EventHandler OnChange = delegate {}; public void Raise() { var exceptions = new List<Exception>(); foreach(Delegate handler in OnChange.GetInvocationList()) { try { handler.DynamicInvoke(this, EventArgs.Empty); } catch(Exception ex) { exceptions.Add(ex); } } if (exceptions.Any()) throw new AggregatedException(exceptions); } } public void CreateAndRaise() { Pub p = new Pub(); p.OnChange += (sender, e) =>Console.WriteLine("1 called"); p.OnChange += (sender, e) =>{ throw new Exception();}; p.OnChange += (sender, e) =>Console.WriteLine("3 called"); try { p.Raise(); } catch(AggregatedException ex) { Console.WriteLine(ex.InnerExceptions.Count); } }
3. 总结
① 委托是一个具有方法签名方式的类型,可以包含一个指向方法的引用。
② 委托可以被实例化,传递 和 触发。
③ Lambda表达式使用 => 操作符来创建匿名方法。
④ event是基于委托机制,高于委托机制的语法糖,可以方便的实现发布-订阅机制。
⑤ event只能在声明他的类内被触发。event使用者只能删除和添加invocation列表的方法。
⑥ 可以自定义事件访问器或者直接使用内建的委托类型。