委托
委托与事件,经常拿来就用,往往忘记其实现原理,对其使用方式也越来越局限。周家安老师在《C# 6.0 学习笔记》中对委托和事件的讲解,深入浅出,清晰明了,故特意摘抄一篇随笔,勤看勤思。
首先,委托是一种形式上与方法签名相似的类型。
定义一个委托:
public delegate void DoSome(string msg);
使用关键字 delegate, 类型名称为 DoSome(string msg).
创建一个DoSome(string msg)委托类型对象,并实例化。
1 static void TestDo(string str) 2 { 3 // ToDo 4 } 5 6 7 DoSome d1 = new DoSome(TestDo); 8 9 10 // 或者11 12 DoSome d2;13 14 d2 = TestDo;
委托列表
1 public delegate void DoSome(string msg); 2 static void Main(string[] args) 3 { 4 DoSome d = new DoSome(TestDo); 5 d("Hello"); 6 Console.WriteLine("--------------------------------------"); 7 d += new DoSome(Test1); 8 d("123"); 9 Console.WriteLine("--------------------------------------");10 d += TDo2;11 d("world");12 Console.WriteLine("--------------------------------------");13 d -= TestDo;14 d("nihao");15 }16 17 static void TestDo(string str)18 {19 Console.WriteLine(str);20 }21 22 static void Test1(string str)23 {24 Console.WriteLine("Test1 + " + str);25 }26 static void TDo2(string str)27 {28 Console.WriteLine("TDo2 + " + str);29 }
输出:
事件
事件自身就是委托类型。
1 class MyApp 2 { 3 public delegate void SpaceKeyPressedEventHandler(); 4 5 // 声明事件 6 public event SpaceKeyPressedEventHandler SpaceKeyPressed; 7 8 // 通过该方法引发事件 9 protected virtual void OnSpaceKeyPressed()10 {11 12 if (this.SpaceKeyPressed != null)13 {14 // 将事件分发15 SpaceKeyPressed();16 }17 }18 19 // 启动事件监听的接口20 public void StartRun()21 {22 // 监听事件23 while (true)24 {25 ConsoleKeyInfo keyinfo = Console.ReadKey();26 if (keyinfo.Key == ConsoleKey.Spacebar)27 {28 // 引发事件29 OnSpaceKeyPressed();30 }31 32 if (keyinfo.Key == ConsoleKey.Escape)33 {34 // 跳出循环35 break;36 }37 }38 }39 }40 41 class Program42 {43 44 static void Main(string[] args)45 {46 MyApp app = new MyApp();47 // 订阅事件,指定处理事件的方法48 app.SpaceKeyPressed += app_SpaceKeyPressed;49 app.SpaceKeyPressed += app_SecondEventHandler;50 51 // 启动事件监听52 app.StartRun();53 54 } 55 56 // 事件处理157 private static void app_SpaceKeyPressed()58 {59 Console.WriteLine("{0} 按下空格键。", DateTime.Now.ToLongTimeString());60 }61 // 事件处理262 private static void app_SecondEventHandler()63 {64 Console.WriteLine("事件的第二个处理方法。");65 }66 67 }
通常,作为事件委托,有两个参数,一个是Object类型,表示引发事件的对象,即是谁引发了事件的,多数情况下在调用事件时是把类的当前实例引用(this)传递过去。另一个参数是从System.EventArgs派省的类的实例。这是一个标准的事件处理程序的签名,为了规范事件的处理,.NET类库已经定义好一个System.EventHandler委托,用于声明事件。它的原型如下:
1 public delegate void EventHandler(object sender, EventArgs e);
引发事件的对象实例将传递给sender参数,而与事件相关的数据则传递给e参数。如果不需要传递过多的数据,可以通过System.EventArgs.Empty静态成员返回一个空的EventArgs对象类传递。
但是,由于不同的事件要传递的参数不同,更多时候是从EventArgs类派生的子类的实例,显然一个EventHandler委托是不能满足各种情况的。如果针对不同的事件也顶一个一个对应的委托,水量一旦多起来,既混乱,也不好管理。为了解决这个问题,.NET类库又提供了一个带有泛型参数的事件处理委托。原型如下:
1 public delegate void EventHandler(object sender, TEventArgs e);
TEventArgs 是一个泛型参数,应该是System.EventArgs类或者System.EventArgs类的派生类型。
泛型参数的事件,实例:
1 // EventArgs 派生类 2 // 创建泛型参数 KeyPressedEventArgs 类 3 public class KeyPressedEventArgs : EventArgs 4 { 5 public ConsoleKey pressedKey { get; private set; } 6 public KeyPressedEventArgs(ConsoleKey key) 7 { 8 pressedKey = key; 9 }10 }11 12 public class MyApp13 {14 // 捕捉按键的事件 声明一个泛型参数KeyPressedEventArgs类型的15 public event EventHandlerKeyPressed;16 17 // 通过该方法引发事件18 protected virtual void OnKeyPressed(KeyPressedEventArgs e)19 {20 if (this.KeyPressed != null )21 {22 this.KeyPressed(this, e);23 }24 }25 26 // 事件监听端口启动27 public void Start()28 {29 while (true)30 {31 ConsoleKeyInfo keyInfo = Console.ReadKey();32 // 如果按下了ESC键,则退出循环33 if (keyInfo.Key == ConsoleKey.Escape)34 {35 break;36 }37 // 引发事件38 OnKeyPressed(new KeyPressedEventArgs(keyInfo.Key));39 }40 }41 }42 43 44 class Program45 {46 47 static void Main(string[] args)48 {49 50 MyApp app = new MyApp();51 // 订阅事件,指定处理事件的方法52 app.KeyPressed += app_KeyPressed;53 // 启动事件监听54 app.Start();55 } 56 57 // 事件处理58 private static void app_KeyPressed(Object sender, KeyPressedEventArgs e)59 {60 Console.WriteLine("已按下 {0} 键", e.pressedKey.ToString());61 }62 63 }