本篇文章将为你介绍一下 Delegate 的使用方式,逐渐揭开 C# 当中事件(Event)的由来,它能使处理委托类型的过程变得更加简单。
还将为您解释委托的协变与逆变,以及如何使用 Delegate 使 Observer(观察者)模式的使用变得更加简单。
在事件的介绍上,会讲述事件的使用方式,并以ASP.NET的用户控件为例子,介绍一下自定义事件的使用。
最后一节,将介绍Predicate<T>、Action<T>、Func<T,TResult>多种泛型委托的使用和Lambda的发展过程与其使用方式。
因为时间仓促,文中有错误的地方敬请点评。

目录

一、委托类型的来由

二、建立委托类

三、委托使用方式

四、深入解析事件

五、Lambda 表达式

一、委托类型的来由

记得在使用C语言的年代,整个项目中都充满着针指的身影,那时候流行使用函数指针来创建回调函数,使用回调可以把函数回调给程序中的另一个函数。但函数指针只是简单地把地址指向另一个函数,并不能传递其他额外信息。

在.NET中,在大部分时间里都没有指针的身影,因为指针被封闭在内部函数当中。可是回调函数却依然存在,它是以委托的方式来完成的。委托可以被视为一个
更高级的指针,它不仅仅能把地址指向另一个函数,而且还能传递参数,返回值等多个信息。系统还为委托对象自动生成了同步、异步的调用方式,开发人员使用
BeginInvoke、EndInvoke 方法就可以抛开 Thread 而直接使用多线程调用 。

二、建立委托类

使用delegate就可以直接建立任何名称的委托类型,当进行系统编译时,系统就会自动生成此类型。您可以使用delegate void
MyDelegate() 方式建立一个委托类,并使用ILDASM.exe观察其成员。由ILDASM.exe
中可以看到,它继承了System.MulticastDelegate类,并自动生成BeginInvoke、EndInvoke、Invoke
等三个常用方法。

Invoke 方法是用于同步调用委托对象的对应方法,而BeginInvoke、EndInvoke是用于以异步方式调用对应方法的。
对于异步调用的使用方式,可以参考:C#综合揭秘——细说多线程

1      public class MyDelegate:MulticastDelegate
2 {
3 //同步调用委托方法
4 public virtual void Invoke();
5 //异步调用委托方法
6 public virtual IAsyncResult BeginInvoke(AsyncCallback callback,object state);
7 public virtual void EndInvoke(IAsyncResult result);
8 }

MulticastDelegate是System.Delegate的子类,它是一个特殊类,编译器和其他工具可以从此类派生,但是自定义类不能显式地从此类进行派生。它支持多路广播委托,并拥有一个带有链接的委托列表,在调用多路广播委托时,系统将按照调用列表中的委托出现顺序来同步调用这些委托。

MulticastDelegate具有两个常用属性:Method、Target。其中Method 用于获取委托所表示的方法Target 用于获取当前调用的类实例。

MulticastDelegate有以下几个常用方法:

方法名称 说明
Clone 创建委托的浅表副本。
GetInvocationList 按照调用顺序返回此多路广播委托的调用列表。
GetMethodImpl 返回由当前的 MulticastDelegate 表示的静态方法。
GetObjectData 用序列化该实例所需的所有数据填充 SerializationInfo 对象。
MemberwiseClone 创建当前 Object 的浅表副本。
RemoveImpl 调用列表中移除与指定委托相等的元素

MulticastDelegate与Delegate给委托对象建立了强大的支持,下面向各位详细介绍一下委托的使用方式。

三、委托使用方式

3.1 简单的委托

当建立委托对象时,委托的参数类型必须与委托方法相对应。只要向建立委托对象的构造函数中输入方法名称example.Method,委托就会直接绑定此 方法。使用myDelegate.Invoke(string message),就能显式调用委托方法。但在实际的操作中,我们无须用到 Invoke 方法,而只要直接使用myDelegate(string message),就能调用委托方法。

 1     class Program
2 {
3 delegate void MyDelegate(string message);
4
5 public class Example
6 {
7 public void Method(string message)
8 {
9 MessageBox.Show(message);
10 }
11 }
12
13 static void Main(string[] args)
14 {
15 Example example=new Example();
16 MyDelegate myDelegate=new MyDelegate(example.Method);
17 myDelegate("Hello World");
18 Console.ReadKey();
19 }
20 }

3.2 带返回值的委托

当建立委托对象时,委托的返回值必须与委托方法相对应。使用下面的例子,方法将返回 “Hello Leslie” 。

 1     class Program
2 {
3 delegate string MyDelegate(string message);
4
5 public class Example
6 {
7 public string Method(string name)
8 {
9 return "Hello " + name;
10 }
11 }
12
13 static void Main(string[] args)
14 {
15 Example example=new Example();
16 //绑定委托方法
17 MyDelegate myDelegate=new MyDelegate(example.Method);
18 //调用委托,获取返回值
19 string message = myDelegate("Leslie");
20 Console.WriteLine(message);
21 Console.ReadKey();
22 }
23 }

3.3 多路广播委托

在第二节前曾经提过,委托类继承于MulticastDelegate,这使委托对象支持多路广播,即委托对象可以绑定多个方法。当输入参数后,每个方法会按顺序进行迭代处理,并返回最后一个方法的计算结果。
下面的例子中,Price 类中有两个计算方法,Ordinary 按普通的9.5折计算,Favourable 按优惠价 8.5
折计算。委托同时绑定了这两个方法,在输入参数100以后,Ordinary、Favourable这两个方法将按顺序迭代执行下去,最后返回
Favourable 方法的计算结果 85。

 1         delegate double MyDelegate(double message);
2
3 public class Price
4 {
5 public double Ordinary(double price)
6 {
7 double price1 = 0.95 * price;
8 Console.WriteLine("Ordinary Price : "+price1);
9 return price1;
10 }
11
12 public double Favourable(double price)
13 {
14 double price1 = 0.85 * price;
15 Console.WriteLine("Favourable Price : " + price1);
16 return price1;
17 }
18
19 static void Main(string[] args)
20 {
21 Price price = new Price();
22 //绑定Ordinary方法
23 MyDelegate myDelegate = new MyDelegate(price.Ordinary);
24 //绑定Favourable方法
25 myDelegate += new MyDelegate(price.Favourable);
26 //调用委托
27 Console.WriteLine("Current Price : " + myDelegate(100));
28 Console.ReadKey();
29 }
30 }

运行结果

3.4 浅谈Observer模式

回顾一下简单的 Observer 模式,它使用一对多的方式,可以让多个观察者同时关注同一个事物,并作出不同的响应。

例如下面的例子,Manager的底薪为基本工资的1.5倍,Assistant的底薪为基本工资的1.2倍。WageManager类的
RegisterWorker方法与RemoveWorker方法可以用于注册和注销观察者,最后执行Execute方法可以对多个已注册的观察者同时输
入参数。

 1     public class WageManager
2 {
3 IList<Worker> workerList = new List<Worker>();
4
5 public void RegisterWorker(Worker worker)
6 {
7 workerList.Add(worker);
8 }
9
10 public void RemoveWorker(Worker worker)
11 {
12 workerList.Remove(worker);
13 }
14
15 public void Excute(double basicWages)
16 {
17 if (workerList.Count != 0)
18 foreach (var worker in workerList)
19 worker.GetWages(basicWages);
20 }
21
22 static void Main(string[] args)
23 {
24 WageManager wageManager = new WageManager();
25 //注册观察者
26 wageManager.RegisterWorker(new Manager());
27 wageManager.RegisterWorker(new Assistant());
28 //同时输入底薪3000元,分别进行计算
29 wageManager.Excute(3000);
30
31 Console.ReadKey();
32 }
33 }
34
35 public abstract class Worker
36 {
37 public abstract double GetWages(double basicWages);
38 }
39
40 public class Manager:Worker
41 {
42 //Manager实际工资为底薪1.5倍
43 public override double GetWages(double basicWages)
44 {
45 double totalWages = 1.5 * basicWages;
46 Console.WriteLine("Manager's wages is " + totalWages);
47 return totalWages;
48 }
49 }
50
51 public class Assistant : Worker
52 {
53 //Assistant实际工资为底薪的1.2倍
54 public override double GetWages(double basicWages)
55 {
56 double totalWages = 1.2 * basicWages;
57 Console.WriteLine("Assistant's wages is " + totalWages);
58 return totalWages;
59 }
60 }

运行结果

开发 Observer 模式时借助委托,可以进一步简化开发的过程。由于委托对象支持多路广播,所以可以把Worker类省略。在WageManager类中建立了一个委托对象 wageHandler,通过Attach与Detach方法可以分别加入或取消委托。如果观察者想对事物进行监测,只需要加入一个委托对象即可。记得在 第二节曾经提过,委托的GetInvodationList方法能获取多路广播委托列表,在Execute方法中,就是通过去多路广播委托列表去判断所绑 定的委托数量是否为0。

 1         public delegate double Handler(double basicWages);
2
3 public class Manager
4 {
5 public double GetWages(double basicWages)
6 {
7 double totalWages=1.5 * basicWages;
8 Console.WriteLine("Manager's wages is : " + totalWages);
9 return totalWages;
10 }
11 }
12
13 public class Assistant
14 {
15 public double GetWages(double basicWages)
16 {
17 double totalWages = 1.2 * basicWages;
18 Console.WriteLine("Assistant's wages is : " + totalWages);
19 return totalWages;
20 }
21 }
22
23 public class WageManager
24 {
25 private Handler wageHandler;
26
27 //加入观察者
28 public void Attach(Handler wageHandler1)
29 {
30 wageHandler += wageHandler1;
31 }
32
33 //删除观察者
34 public void Detach(Handler wageHandler1)
35 {
36 wageHandler -= wageHandler1;
37 }
38
39 //通过GetInvodationList方法获取多路广播委托列表,如果观察者数量大于0即执行方法
40 public void Execute(double basicWages)
41 {
42 if (wageHandler!=null)
43 if(wageHandler.GetInvocationList().Count() != 0)
44 wageHandler(basicWages);
45 }
46
47 static void Main(string[] args)
48 {
49 WageManager wageManager = new WageManager();
50 //加入Manager观察者
51 Manager manager = new Manager();
52 Handler managerHandler = new Handler(manager.GetWages);
53 wageManager.Attach(managerHandler);
54
55 //加入Assistant观察者
56 Assistant assistant = new Assistant();
57 Handler assistantHandler = new Handler(assistant.GetWages);
58 wageManager.Attach(assistantHandler);
59
60 //同时加入底薪3000元,分别进行计算
61 wageManager.Execute(3000);
62 Console.ReadKey();
63 }
64 }

最后运行结果与上面的例子相同。

3.5 委托的协变与逆变

在 Framework 2.0 出现之前,委托协变这个概念还没有出现。此时因为委托是安全类型,它们不遵守继承的基础规则。即会这下面的情况:Manager 虽然是 Worker 的子类,但 GetWorkerHander 委托不能直接绑定 GetManager 方法,因为在委托当中它们的返回值 Manager 与 Worker 被视为完全无关的两个类型。

 1      public class Worker
2 {.......}
3 public class Manager:Worker
4 {.......}
5
6 class Program
7 {
8 public delegate Worker GetWorkerHandler(int id);
9 public delegate Manager GetManagerHandler(int id);
10
11 public static Worker GetWorker(int id)
12 {
13 Worker worker = new Worker();
14 ..............
15 return worker;
16 }
17
18 public static Manager GetManager(int id)
19 {
20 Manager manager = new Manager();
21 ..............
22 return manager;
23 }
24
25 static void Main(string[] args)
26 {
27 GetWorkerHandler workerHandler = new GetWorkerHandler(GetWorker);
28 var worker=workerHandler(1);
29
30 GetManagerHandler managerHandler = new GetManagerHandler(GetManager);
31 var manager = managerHandler(2);
32 Console.ReadKey();
33 }
34 }

自从Framework 2.0 面试以后,委托协变的概念就应运而生,此时委托可以按照传统的继承规则进行转换。即 GetWorkerHandler 委托可以直接绑定 GetManager 方法。

 1      public class Worker
2 {.......}
3 public class Manager:Worker
4 {.......}
5
6 class Program
7 {
8 public delegate Worker GetWorkerHandler(int id);
9 //在 Framework2.0 以上,委托 GetWorkerHandler 可绑定 GetWorker 与 GetManager 两个方法
10
11 public static Worker GetWorker(int id)
12 {
13 Worker worker = new Worker();
14 return worker;
15 }
16
17 public static Manager GetManager(int id)
18 {
19 Manager manager = new Manager();
20 return manager;
21 }
22
23 static void Main(string[] args)
24 {
25 GetWorkerHandler workerHandler = new GetWorkerHandler(GetWorker);
26 Worker worker=workerHandler(1);
27 GetWorkerHandler managerHandler = new GetWorkerHandler(GetManager);
28 Manager manager = managerHandler(2) as Manager;
29 Console.ReadKey();
30 }
31 }

委托逆变,是指委托方法的参数同样可以接收 “继承” 这个传统规则。像下面的例子,以 object 为参数的委托,可以接受任何 object 子类的对象作为参数。最后可以在处理方法中使用 is 对输入数据的类型进行判断,分别处理对不同的类型的对象。

 1     class Program
2 {
3 public delegate void Handler(object obj);
4
5 public static void GetMessage(object message)
6 {
7 if (message is string)
8 Console.WriteLine("His name is : " + message.ToString());
9 if (message is int)
10 Console.WriteLine("His age is : " + message.ToString());
11 }
12
13 static void Main(string[] args)
14 {
15 Handler handler = new Handler(GetMessage);
16 handler(29);
17 Console.ReadKey();
18 }
19 }

运行结果

注意委托与其绑定方法的参数必须一至,即当 Handler 所输入的参数为 A 类型,其绑定方法 GetMessage 的参数也必须为 A 类或者 A 的父类 。相反,当绑定方法的参数为 A 的子类,系统也无法辨认。

3.6 泛型委托

委托逆变虽然实用,但如果都以 object 作为参数,则需要每次都对参数进行类型的判断,这不禁令人感到厌烦。
为此,泛型委托应运而生,泛型委托有着委托逆变的优点,同时利用泛型的特性,可以使一个委托绑定多个不同类型参数的方法,而且在方法中不需要使用 is 进行类型判断,从而简化了代码。

 1     class Program
2 {
3 public delegate void Handler<T>(T obj);
4
5 public static void GetWorkerWages(Worker worker)
6 {
7 Console.WriteLine("Worker's total wages is " + worker.Wages);
8 }
9
10 public static void GetManagerWages(Manager manager)
11 {
12 Console.WriteLine("Manager's total wages is "+manager.Wages);
13 }
14
15 static void Main(string[] args)
16 {
17 Handler<Worker> workerHander = new Handler<Worker>(GetWorkerWages);
18 Worker worker = new Worker();
19 worker.Wages = 3000;
20 workerHander(worker);
21
22 Handler<Manager> managerHandler = new Handler<Manager>(GetManagerWages);
23 Manager manager = new Manager();
24 manager.Wages = 4500;
25 managerHandler(manager);
26
27 Console.ReadKey();
28 }
29 }

运行结果

回到目录

四、深入解析事件

4.1 事件的由来

在介绍事件之前大家可以先看看下面的例子, PriceManager 负责对商品价格进行处理,当委托对象 GetPriceHandler 的返回值大于100元,按8.8折计算,低于100元按原价计算。

 1     public delegate double PriceHandler();
2
3 public class PriceManager
4 {
5 public PriceHandler GetPriceHandler;
6
7 //委托处理,当价格高于100元按8.8折计算,其他按原价计算
8 public double GetPrice()
9 {
10 if (GetPriceHandler.GetInvocationList().Count() > 0)
11 {
12 if (GetPriceHandler() > 100)
13 return GetPriceHandler()*0.88;
14 else
15 return GetPriceHandler();
16 }
17 return -1;
18 }
19 }
20
21 class Program
22 {
23 static void Main(string[] args)
24 {
25 PriceManager priceManager = new PriceManager();
26
27 //调用priceManager的GetPrice方法获取价格
28 //直接调用委托的Invoke获取价格,两者进行比较
29 priceManager.GetPriceHandler = new PriceHandler(ComputerPrice);
30 Console.WriteLine(string.Format("GetPrice\n Computer's price is {0}!",
31 priceManager.GetPrice()));
32 Console.WriteLine(string.Format("Invoke\n Computer's price is {0}!",
33 priceManager.GetPriceHandler.Invoke()));
34
35 Console.WriteLine();
36
37 priceManager.GetPriceHandler = new PriceHandler(BookPrice);
38 Console.WriteLine(string.Format("GetPrice\n Book's price is {0}!",
39 priceManager.GetPrice()));
40 Console.WriteLine(string.Format("Invoke\n Book's price is {0}!" ,
41 priceManager.GetPriceHandler.Invoke()));
42
43 Console.ReadKey();
44 }
45 //书本价格为98元
46 public static double BookPrice()
47 {
48 return 98.0;
49 }
50 //计算机价格为8800元
51 public static double ComputerPrice()
52 {
53 return 8800.0;
54 }
55 }

运行结果

观察运行的结果,如果把委托对象 GetPriceHandler 设置为 public ,外界可以直接调用 GetPriceHandler.Invoke 获取运行结果而移除了 GetPrice 方法的处理,这正是开发人员最不想看到的。
为了保证系统的封装性,开发往往需要把委托对象 GetPriceHandler 设置为 private, 再分别加入 AddHandler,RemoveHandler 方法对 GetPriceHandler 委托对象进行封装。

 1     public delegate double PriceHandler();
2
3 public class PriceManager
4 {
5 private PriceHandler GetPriceHandler;
6
7 //委托处理,当价格高于100元按8.8折计算,其他按原价计算
8 public double GetPrice()
9 {
10 if (GetPriceHandler!=null)
11 {
12 if (GetPriceHandler() > 100)
13 return GetPriceHandler()*0.88;
14 else
15 return GetPriceHandler();
16 }
17 return -1;
18 }
19
20 public void AddHandler(PriceHandler handler)
21 {
22 GetPriceHandler += handler;
23 }
24
25 public void RemoveHandler(PriceHandler handler)
26 {
27 GetPriceHandler -= handler;
28 }
29 }
30 ................
31 ................

为了保存封装性,很多操作都需要加入AddHandler、RemoveHandler 这些相似的方法代码,这未免令人感到厌烦。
为了进一步简化操作,事件这个概念应运而生。

4.2 事件的定义

事件(event)可被视作为一种特别的委托,它为委托对象隐式地建立起add_XXX、remove_XXX
两个方法,用作注册与注销事件的处理方法。而且事件对应的变量成员将会被视为 private
变量,外界无法超越事件所在对象直接访问它们,这使事件具备良好的封装性,而且免除了add_XXX、remove_XXX等繁琐的代码。

1     public class EventTest
2 {
3 public delegate void MyDelegate();
4 public event MyDelegate MyEvent;
5 }

观察事件的编译过程可知,在编译的时候,系统为 MyEvent 事件自动建立add_MyEvent、remove_MyEvent 方法。

4.3 事件的使用方式

事件能通过+=和-=两个方式注册或者注销对其处理的方法,使用+=与-=操作符的时候,系统会自动调用对应的 add_XXX、remove_XXX 进行处理。
值得留意,在PersonManager类的Execute方法中,如果 MyEvent
绑定的处理方法不为空,即可使用MyEvent(string)引发事件。但如果在外界的 main 方法中直接使用
personManager.MyEvent (string)
来引发事件,系统将引发错误报告。这正是因为事件具备了良好的封装性,使外界不能超越事件所在的对象访问其变量成员。

注意在事件所处的对象之外,事件只能出现在+=,-=的左方。

此时,开发人员无须手动添加 add_XXX、remove_XXX 的方法,就可实现与4.1例子中的相同功能,实现了良好的封装。

 1     public delegate void MyDelegate(string name);
2
3 public class PersonManager
4 {
5 public event MyDelegate MyEvent;
6
7 //执行事件
8 public void Execute(string name)
9 {
10 if (MyEvent != null)
11 MyEvent(name);
12 }
13 }
14
15 class Program
16 {
17 static void Main(string[] args)
18 {
19 PersonManager personManager = new PersonManager();
20 //绑定事件处理方法
21 personManager.MyEvent += new MyDelegate(GetName);
22 personManager.Execute("Leslie");
23 Console.ReadKey();
24 }
25
26 public static void GetName(string name)
27 {
28 Console.WriteLine("My name is " + name);
29 }
30 }

4.4 事件处理方法的绑定

在绑定事件处理方法的时候,事件出现在+=、-= 操作符的左边,对应的委托对象出现在+=、-= 操作符的右边。对应以上例子,事件提供了更简单的绑定方式,只需要在+=、-= 操作符的右方写上方法名称,系统就能自动辩认。

 1     public delegate void MyDelegate(string name);
2
3 public class PersonManager
4 {
5 public event MyDelegate MyEvent;
6 .........
7 }
8
9 class Program
10 {
11 static void Main(string[] args)
12 {
13 PersonManager personManager = new PersonManager();
14 //绑定事件处理方法
15 personManager.MyEvent += GetName;
16 .............
17 }
18
19 public static void GetName(string name)
20 {.........}
21 }

如果觉得编写 GetName 方法过于麻烦,你还可以使用匿名方法绑定事件的处理。

 1     public delegate void MyDelegate(string name);
2
3 public class PersonManager
4 {
5 public event MyDelegate MyEvent;
6
7 //执行事件
8 public void Execute(string name)
9 {
10 if (MyEvent != null)
11 MyEvent(name);
12 }
13
14 static void Main(string[] args)
15 {
16 PersonManager personManager = new PersonManager();
17 //使用匿名方法绑定事件的处理
18 personManager.MyEvent += delegate(string name){
19 Console.WriteLine("My name is "+name);
20 };
21 personManager.Execute("Leslie");
22 Console.ReadKey();
23 }
24 }

4.5 C#控件中的事件

在C#控件中存在多个的事件,像Click、TextChanged、SelectIndexChanged 等等,很多都是通过 EventHandler 委托绑定事件的处理方法的,EventHandler 可说是C#控件中最常见的委托 。

public delegate void EventHandler (Object sender, EventArgs e)

EventHandler 委托并无返回值,sender 代表引发事件的控件对象,e 代表由该事件生成的数据 。在ASP.NET中可以直接通过btn.Click+=new EventHandler(btn_onclick) 的方式为控件绑定处理方法。

 1 <html xmlns="http://www.w3.org/1999/xhtml">
2 <head runat="server">
3 <title></title>
4 <script type="text/C#" runat="server">
5 protected void Page_Load(object sender, EventArgs e)
6 {
7 btn.Click += new EventHandler(btn_onclick);
8 }
9
10 public void btn_onclick(object obj, EventArgs e)
11 {
12 Button btn = (Button)obj;
13 Response.Write(btn.Text);
14 }
15 </script>
16 </head>
17 <body>
18 <form id="form1" runat="server">
19 <div>
20 <asp:Button ID="btn" runat="server" Text="Button"/>
21 </div>
22 </form>
23 </body>
24 </html>

更多时候,只需要在页面使用 OnClick=“btn_onclick" 方法,在编译的时候系统就会自动对事件处理方法进行绑定。

 1 <html xmlns="http://www.w3.org/1999/xhtml">
2 <head runat="server">
3 <title></title>
4 <script type="text/C#" runat="server">
5 public void btn_onclick(object obj, EventArgs e)
6 {
7 Button btn = (Button)obj;
8 Response.Write(btn.Text);
9 }
10 </script>
11 </head>
12 <body>
13 <form id="form1" runat="server">
14 <div>
15 <asp:Button ID="btn" runat="server" Text="Button" OnClick="btn_onclick"/>
16 </div>
17 </form>
18 </body>
19 </html>

EventHandler 只是 EventHandler<TEventArgs> 泛型委托的一个简单例子。事实上,大家可以利用 EventHandler<TEventArgs> 构造出所需要的委托。

public delegate void EventHandler<TEventArgs> (Object sender, TEventArgs e)

在EventHandler<TEventArgs>中,sender代表事件源,e 代表派生自EventArgs类的事件参数。开发人员可以建立派生自EventArgs的类,从中加入需要使用到的事件参数,然后建立 EventHandler<TEventArgs> 委托。

下面的例子中,先建立一个派生自EventArgs的类MyEventArgs作为事件参数,然后在EventManager中建立事件myEvent , 通过 Execute 方法可以激发事件。最后在测试中绑定 myEvent 的处理方法 ShowMessage,在ShowMessage显示myEventArgs 的事件参数 Message。

 1     public class MyEventArgs : EventArgs
2 {
3 private string args;
4
5 public MyEventArgs(string message)
6 {
7 args = message;
8 }
9
10 public string Message
11 {
12 get { return args; }
13 set { args = value; }
14 }
15 }
16
17 public class EventManager
18 {
19 public event EventHandler<MyEventArgs> myEvent;
20
21 public void Execute(string message)
22 {
23 if (myEvent != null)
24 myEvent(this, new MyEventArgs(message));
25 }
26 }
27
28 class Program
29 {
30 static void Main(string[] args)
31 {
32 EventManager eventManager = new EventManager();
33 eventManager.myEvent += new EventHandler<MyEventArgs>(ShowMessage);
34 eventManager.Execute("How are you!");
35 Console.ReadKey();
36 }
37
38 public static void ShowMessage(object obj,MyEventArgs e)
39 {
40 Console.WriteLine(e.Message);
41 }
42 }

运行结果

4.6 为用户控件建立事件

在ASP.NET开发中,页面往往会出现很多类似的控件与代码,开发人员可以通过用户控件来避免重复的代码。但往往同一个用户控件,在不同的页面中需要有不同的响应。此时为用户控件建立事件,便可轻松地解决此问题。
下面例子中,在用户控件 MyControl 中建立存在一个GridView控件,GridView 控件通过 GetPersonList
方法获取数据源。在用户控件中还定义了 RowCommand 事件,在 GridView 的 GridView_RowCommand
方法中激发此事件。这样,在页面使用此控件时,开发人员就可以定义不同的方法处理 RowCommand 事件。

 1 public class Person
2 {
3 public int ID
4 { get; set; }
5 public string Name
6 { get; set; }
7 public int Age
8 { get; set; }
9 }
10
11 <!-- 用户控件 -->
12 <%@ Control Language="C#" AutoEventWireup="true" CodeFile="MyControl.ascx.cs" Inherits="MyControl" %>
13 <script type="text/C#" runat="server">
14 protected void Page_Load(object sender, EventArgs e)
15 {
16 GridView1.DataSource = GetPersonList();
17 GridView1.DataBind();
18 }
19
20 //绑定数据源
21 protected IList<Person> GetPersonList()
22 {
23 IList<Person> list = new List<Person>();
24 Person person1 = new Person();
25 person1.ID = 1;
26 person1.Name = "Leslie";
27 person1.Age = 29;
28 list.Add(person1);
29 ...........
30 return list;
31 }
32
33 public event GridViewCommandEventHandler RowCommand;
34
35 protected void GridView1_RowCommand(object sender, GridViewCommandEventArgs e)
36 {
37 if (RowCommand != null)
38 RowCommand(sender, e);
39 }
40 </script>
41 <div>
42 <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"43 onrowcommand="GridView1_RowCommand">
44 <Columns>
45 <asp:BoundField DataField="ID" HeaderText="ID"/>
46 <asp:BoundField DataField="Name" HeaderText="Name"/>
47 <asp:BoundField DataField="Age" HeaderText="Age"/>
48 <asp:ButtonField CommandName="Get" Text="Select"/>
49 </Columns>
50 </asp:GridView>
51 </div>
52
53 <!-- 页面代码 -->
54 <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default2.aspx.cs" Inherits="Default2" %>
55 <%@ Register Src="~/MyControl.ascx" TagPrefix="ascx" TagName="myControl" %>
56 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
57
58 <html xmlns="http://www.w3.org/1999/xhtml">
59 <head runat="server">
60 <title></title>
61 <script type="text/C#" runat="server">
62 protected void myControl_RowCommand(object sender, GridViewCommandEventArgs e)
63 {
64 if (e.CommandName == "Get")
65 {
66 GridView gridView=(GridView)sender;
67 int index = int.Parse(e.CommandArgument.ToString());
68 label.Text=gridView.Rows[index].Cells[1].Text;
69 }
70 }
71 </script>
72 </head>
73 <body>
74 <form id="form1" runat="server">
75 <div>
76 <ascx:myControl ID="myControl" runat="server" OnRowCommand="myControl_RowCommand"></ascx:myControl>
77 <br />
78 Select Name : <asp:Label ID="label" runat="server"></asp:Label><br />
79 </div>
80 </form>
81 </body>
82 </html>

运行结果

使用控件已有的事件固然简单,但它限制了传送的参数类型,使开发人员无法传送额外的自定义参数。在结构比较复杂的用户控件中,使用已有的控件事件,显然不够方便,此时,您可以考虑为用户控件建立自定义事件。
首先用户控件中包含订单信息与订单明细列表,首先定义一个事件参数 MyEventArgs,里面包含了订单信息与一个 OrderItem
数组。然后建立用户控件的委托MyDelegate 与对应的事件 MyEvent,在 Button 的 Click 事件中激发 MyEvent
自定义事件。这样在页面处理方法 myControl_Click 中就可以通过事件参数 MyEventArgs
获取用户控件中的属性,计算订单的总体价格。

  1 <!--   基础类    -->
2 public class OrderItem
3 {
4 public OrderItem(string id,string goods,double price,int count)
5 {
6 this.OrderItemID = id; //明细单ID
7 this.Goods = goods; //商品名称
8 this.Price = price; //商品单价
9 this.Count = count; //商品数量
10 }
11
12 public string OrderItemID
13 { get; set; }
14 public string Goods
15 { get; set; }
16 public double Price
17 { get; set; }
18 public int Count
19 { get; set; }
20 }
21
22 /// 事件参数
23 public class MyEventArgs:EventArgs
24 {
25 public MyEventArgs(string name,string address,string tel,
26 string orderCode,IList<OrderItem> orderItemList)
27 {
28 Name = name; //买家姓名
29 Address = address; //买家地址
30 Tel = tel; //买家电话
31 OrderCode = orderCode; //订单号码
32 OrderItemList = orderItemList; //订单明细
33 }
34
35 public string Name
36 { get;set; }
37 public string Address
38 { get; set; }
39 public string Tel
40 { get; set; }
41 public string OrderCode
42 { get; set; }
43 public IList<OrderItem> OrderItemList
44 { get; set; }
45 }
46
47 <!-- 用户控件 -->
48 <%@ Control Language="C#" AutoEventWireup="true" CodeFile="MyControl.ascx.cs" Inherits="MyControl" %>
49 <script type="text/C#" runat="server">
50 protected void Page_Load(object sender, EventArgs e)
51 {
52 GridView1.DataSource = GetList();
53 GridView1.DataBind();
54 }
55 56 //模拟数据源
57 protected IList<OrderItem> GetList()
58 {
59 IList<OrderItem> list = new List<OrderItem>();
60 OrderItem orderItem = new OrderItem("1", "Asus N75S", 8800, 2);
61 list.Add(orderItem);
62 ..........
63 return list;
64 }
65 66 //自定义委托
67 public delegate void MyDelegate(object sender,MyEventArgs myEventArgs);
68 //自定义事件
69 public event MyDelegate MyEvent;
70 71 //按下Button时激发自定义事件
72 protected void btn_click(object sender, EventArgs e)
73 {
74 if (MyEvent != null)
75 {
76 MyEventArgs myEventArgs = new MyEventArgs(labelName.Text, labelAddress.Text, labelTel.Text
77 , labelOrderCode.Text, GetList());
78 MyEvent(this,myEventArgs);
79 }
80 }
81 </script>
82 <div>
83 Name : <asp:Label ID="labelName" runat="server">Leslie</asp:Label><br />
84 Address : <asp:Label ID="labelAddress" runat="server">ZhongShan University 2A 501</asp:Label><br />
85 Tel : <asp:Label ID="labelTel" runat="server">13660123456</asp:Label><br />
86 Order Code : <asp:Label ID="labelOrderCode" runat="server">A12012031223B0030</asp:Label><br /><br />
87 <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" CellPadding="5">
88 <Columns>
89 <asp:BoundField DataField="OrderItemID" HeaderText="ID"/>
90 <asp:BoundField DataField="Goods" HeaderText="Goods"/>
91 <asp:BoundField DataField="Price" HeaderText="Price"/>
92 <asp:BoundField DataField="Count" HeaderText="Count"/>
93 </Columns>
94 </asp:GridView>
95 <br />
96 <asp:Button ID="btn" runat="server" Text="Account" OnClick="btn_click"/>
97 </div>
98
99 <!-- 页面处理 -->
100 <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default2.aspx.cs" Inherits="Default2" %>
101 <%@ Register Src="~/MyControl.ascx" TagPrefix="ascx" TagName="myControl" %>
102 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
103
104 <html xmlns="http://www.w3.org/1999/xhtml">
105 <head runat="server">
106 <title></title>
107 <script type="text/C#" runat="server">
108 //在页面定义用户控件MyEvent事件的处理方法
109 protected void myControl_Click(object sender,MyEventArgs e)
110 {
111 //计算订单总体价格
112 double totalPrice=0;
113 IList<OrderItem> list=e.OrderItemList;
114 foreach(OrderItem item in list)
115 totalPrice+=item.Price*item.Count;
116 //展示订单号及总体费用
117 labelOrderCode.Text = e.OrderCode;
118 labelTotalPrice.Text = totalPrice.ToString();
119 }
120 </script>
121 </head>
122 <body>
123 <form id="form1" runat="server">
124 <div>
125 <ascx:myControl ID="myControl" runat="server" OnMyEvent="myControl_Click"></ascx:myControl>
126 <br />
127 OrderCode : <asp:Label ID="labelOrderCode" runat="server"></asp:Label><br />
128 TotalPrice : <asp:Label ID="labelTotalPrice" runat="server"></asp:Label>
129 </div>
130 </form>
131 </body>
132 </html>

运行结果

若对自定义事件不太熟悉的朋友很多时候会使用 UserControl.FindControl 的方式获取用户控件中的属性,但当你深入了解自定义事件的开发过程以后,就能有效简化开发的过程。

回到目录

五、Lambda 表达式

5.1 Lambda 的意义

在Framework 2.0 以前,声明委托的唯一方法是通过方法命名,从Framework 2.0 起,系统开始支持匿名方法。
通过匿名方法,可以直接把一段代码绑定给事件,因此减少了实例化委托所需的编码系统开销。
而在 Framework 3.0 开始,Lambda 表达式开始逐渐取代了匿名方法,作为编写内联代码的首选方式。总体来说,Lambda 表达式的作用是为了使用更简单的方式来编写匿名方法,彻底简化委托的使用方式。

5.2 回顾匿名方法的使用

匿名方法的使用已经在4.4节简单介绍过,在此回顾一下。
使用下面的方式,可以通过匿名方法为Button的Click事件绑定处理方法。

1         static void Main(string[] args)
2 {
3 Button btn = new Button();
4 btn.Click+=delegate(object obj,EventArgs e){
5 MessageBox.Show("Hello World !");
6 };
7 }

总是使用 delegate(){......} 的方式建立匿名方法,令人不禁感觉郁闷。于是从Framework 3.0 起, Lambda 表达式开始出现。

5.3 简单介绍泛型委托

在介绍 Lambda 表达式前,先介绍一下常用的几个泛型委托。

5.3.1泛型委托 Predicate<T>

早在Framework 2.0 的时候,微软就为 List<T> 类添加了 Find、FindAll 、ForEach 等方法用作数据的查找。

public T Find ( Predicate<T> match)
public List<T> FindAll(Predicate<T> match)

在这些方法中存在一个Predicate <T> 表达式,它是一个返回bool的泛型委托,能接受一个任意类型的对象作为参数。

public delegate bool Predicate<T>(T obj)

在下面例子中,Predicate 委托绑定了参数为Person类的方法Match作为查询条件,然后使用 FindAll 方法查找到合适条件的 List<Person> 集合。

 1     class Program
2 {
3 static void Main(string[] args)
4 {
5 List<Person> list = GetList();
6 //绑定查询条件
7 Predicate<Person> predicate = new Predicate<Person>(Match);
8 List<Person> result = list.FindAll(predicate);
9 Console.WriteLine(“Person count is : ” + result.Count);
10 Console.ReadKey();
11 }
12 //模拟源数据
13 static List<Person> GetList()
14 {
15 var personList = new List<Person>();
16 var person1 = new Person(1,"Leslie",29);
17 personList.Add(person1);
18 ........
19 return personList;
20 }
21 //查询条件
22 static bool Match(Person person)
23 {
24 return person.Age <= 30;
25 }
26 }
27
28 public class Person
29 {
30 public Person(int id, string name, int age)
31 {
32 ID = id;
33 Name = name;
34 Age = age;
35 }
36
37 public int ID
38 { get; set; }
39 public string Name
40 { get; set; }
41 public int Age
42 { get; set; }
43 }

5.3.2 泛型委托 Action

Action<T> 的使用方式与 Predicate<T> 相似,不同之处在于 Predicate<T> 返回值为 bool , Action<T> 的返回值为 void。
Action 支持0~16个参数,可以按需求任意使用。

public delegate void Action()
public delegate void Action<T1>(T1 obj1)
public delegate void Action<T1,T2> (T1 obj1, T2 obj2)
public delegate void Action<T1,T2,T3> (T1 obj1, T2 obj2,T3 obj3)
............
public delegate void Action<T1,T2,T3,......,T16> (T1 obj1, T2 obj2,T3 obj3,......,T16 obj16)

 1         static void Main(string[] args)
2 {
3 Action<string> action=ShowMessage;
4 action("Hello World");
5 Console.ReadKey();
6 }
7
8 static void ShowMessage(string message)
9 {
10 MessageBox.Show(message);
11 }

5.3.3 泛型委托 Func

委托 Func 与 Action 相似,同样支持 0~16 个参数,不同之处在于Func 必须具有返回值

public delegate TResult Func<TResult>()
public delegate TResult Func<T1,TResult>(T1 obj1)
public delegate TResult Func<T1,T2,TResult>(T1 obj1,T2 obj2)
public delegate TResult Func<T1,T2,T3,TResult>(T1 obj1,T2 obj2,T3 obj3)
............
public delegate TResult Func<T1,T2,T3,......,T16,TResult>(T1 obj1,T2 obj2,T3 obj3,......,T16 obj16)

 1         static void Main(string[] args)
2 {
3 Func<double, bool, double> func = Account;
4 double result=func(1000, true);
5 Console.WriteLine("Result is : "+result);
6 Console.ReadKey();
7 }
8
9 static double Account(double a,bool condition)
10 {
11 if (condition)
12 return a * 1.5;
13 else
14 return a * 2;
15 }

5.4 揭开 Lambda 神秘的面纱

Lambda 的表达式的编写格式如下:

x=> x * 1.5

当中 “ => ” 是 Lambda 表达式的操作符,在左边用作定义一个参数列表,右边可以操作这些参数。

例子一, 先把 int x 设置 1000,通过 Action 把表达式定义为 x=x+500 ,最后通过 Invoke 激发委托。

1         static void Main(string[] args)
2 {
3 int x = 1000;
4 Action action = () => x = x + 500;
5 action.Invoke();
6
7 Console.WriteLine("Result is : " + x);
8 Console.ReadKey();
9 }

例子二,通过 Action<int> 把表达式定义 x=x+500, 到最后输入参数1000,得到的结果与例子一相同。
注意,此处Lambda表达式定义的操作使用 { } 括弧包括在一起,里面可以包含一系列的操作。

 1         static void Main(string[] args)
2 {
3 Action<int> action = (x) =>
4 {
5 x = x + 500;
6 Console.WriteLine("Result is : " + x);
7 };
8 action.Invoke(1000);
9 Console.ReadKey();
10 }

例子三,定义一个Predicate<int>,当输入值大约等于1000则返回 true , 否则返回 false。与5.3.1的例子相比,Predicate<T>的绑定不需要显式建立一个方法,而是直接在Lambda表达式里完成,简洁方 便了不少。

 1         static void Main(string[] args)
2 {
3 Predicate<int> predicate = (x) =>
4 {
5 if (x >= 1000)
6 return true;
7 else
8 return false;
9 };
10 bool result=predicate.Invoke(500);
11 Console.ReadKey();
12 }

例子四,在计算商品的价格时,当商品重量超过30kg则打9折,其他按原价处理。此时可以使用Func<double,int,double>,参数1为商品原价,参数2为商品重量,最后返回值为 double 类型。

 1         static void Main(string[] args)
2 {
3 Func<double, int, double> func = (price, weight) =>
4 {
5 if (weight >= 30)
6 return price * 0.9;
7 else
8 return price;
9 };
10 double totalPrice = func(200.0, 40);
11 Console.ReadKey();
12 }

例子五,使用Lambda为Button定义Click事件的处理方法。与5.2的例子相比,使用Lambda比使用匿名方法更加简单。

1         static void Main(string[] args)
2 {
3 Button btn = new Button();
4 btn.Click += (obj, e) =>
5 {
6 MessageBox.Show("Hello World!");
7 };
8 Console.ReadKey();
9 }

例子六,此处使用5.3.1的例子,在List<Person>的FindAll方法中直接使用Lambda表达式。
相比之下,使用Lambda表达式,不需要定义Predicate<T>对象,也不需要显式设定绑定方法,简化了不工序。

 1      class Program
2 {
3 static void Main(string[] args)
4 {
5 List<Person> personList = GetList();
6
7 //查找年龄少于30年的人
8 List<Person> result=personList.FindAll((person) => person.Age =< 30);
9 Console.WriteLine("Person count is : " + result.Count);
10 Console.ReadKey();
11 }
12
13 //模拟源数据
14 static List<Person> GetList()
15 {
16 var personList = new List<Person>();
17 var person1 = new Person(1,"Leslie",29);
18 personList.Add(person1);
19 .......
20 return personList;
21 }
22 }
23
24 public class Person
25 {
26 public Person(int id, string name, int age)
27 {
28 ID = id;
29 Name = name;
30 Age = age;
31 }
32
33 public int ID
34 { get; set; }
35 public string Name
36 { get; set; }
37 public int Age
38 { get; set; }
39 }

当在使用LINQ技术的时候,到处都会弥漫着 Lambda 的身影,此时更能体现 Lambda 的长处。
但 LINQ 涉及到分部类,分部方法,IEnumerable<T>,迭代器等多方面的知识,这些已经超出本章的介绍范围。
通过这一节的介绍,希望能够帮助大家更深入地了解 Lambda 的使用。

本章小结

本章主要介绍了委托(Delegate)的使用,委托对象是一个派生自 System.MultcastDelegate 的类,它能通过
Invoke 方式进行同步调用,也可以通过 BeginInvoke,EndInvoke
方式实现异步调用。而事件(Event)属于一种特殊的委托,它与委托类型同步使用,可以简化的开发过程。

C#--深入分析委托与事件的更多相关文章

  1. C#综合揭秘——深入分析委托与事件

    http://www.cnblogs.com/leslies2/archive/2012/03/22/2389318.html 引言 本篇文章将为你介绍一下 Delegate 的使用方式,逐渐揭开 C ...

  2. [转载]C#深入分析委托与事件

    原文出处: 作者:风尘浪子 原文链接:http://www.cnblogs.com/leslies2/archive/2012/03/22/2389318.html 同类链接:http://www.c ...

  3. [转]C#综合揭秘——深入分析委托与事件

    引言 本篇文章将为你介绍一下 Delegate 的使用方式,逐渐揭开 C# 当中事件(Event)的由来,它能使处理委托类型的过程变得更加简单.还将为您解释委托的协变与逆变,以及如何使用 Delega ...

  4. .NET面试题系列[7] - 委托与事件

    委托和事件 委托在C#中具有无比重要的地位. C#中的委托可以说俯拾即是,从LINQ中的lambda表达式到(包括但不限于)winform,wpf中的各种事件都有着委托的身影.C#中如果没有了事件,那 ...

  5. .NET基础拾遗(4)委托、事件、反射与特性

    Index : (1)类型语法.内存管理和垃圾回收基础 (2)面向对象的实现和异常的处理基础 (3)字符串.集合与流 (4)委托.事件.反射与特性 (5)多线程开发基础 (6)ADO.NET与数据库开 ...

  6. [转载]C#委托和事件(Delegate、Event、EventHandler、EventArgs)

    原文链接:http://blog.csdn.net/zwj7612356/article/details/8272520 14.1.委托 当要把方法作为实参传送给其他方法的形参时,形参需要使用委托.委 ...

  7. C#委托与事件

    一.在控制台下使用委托和事件 我们都知道,C#中有"接口"这个概念,所谓的"接口"就是定义一套标准,然后由实现类来具体实现其中的方法,所以说"接口,是 ...

  8. C#委托与事件的简单使用

    前言:上一篇博文从原理和定义的角度介绍了C#的委托和事件.本文通过一个简单的小故事,来说明C#委托与事件的使用方法及其方便之处. 在阅读本文之前,需要你对委托和事件的基本概念有所了解.如果你是初次接触 ...

  9. C#之委托与事件

    委托与事件 废话一堆:网上关于委托.事件的文章有很多,一千个哈姆雷特就有一千个莎士比亚,以下内容均是本人个人见解. 1. 委托 1.1 委托的使用 这一小章来学习一下怎么简单的使用委托,了解一些基本的 ...

随机推荐

  1. 【原创】省市二级联动纯javascript

    // 北京 上海 天津 重庆 河北 山西 内蒙古 辽宁 吉林 黑龙江 江苏 浙江 安徽 福建 江西 山东 河南 湖北 湖南 广东 广西 海南 四川 贵州 云南 西藏 陕西 甘肃 宁夏 青海 新疆 香港 ...

  2. DCEF3 相关资料

    DCEF3 调用 js http://www.cnblogs.com/Delphi-Farmer/p/4103708.html interface uses ceflib;//其它 type //这里 ...

  3. Python 数据类型

    数据类型计算机顾名思义就是可以做数学计算的机器,因此,计算机程序理所当然地可以处理各种数值.但是,计算机能处理的远不止数值,还可以处理文本.图形.音频.视频.网页等各种各样的数据,不同的数据,需要定义 ...

  4. Response.Redirect 打开新窗体的两种方法

    普通情况下,Response.Redirect 方法是在server端进行转向,因此,除非使用 Response.Write("<script>window.location=' ...

  5. Codeforces Gym 100531I Instruction 构造

    Problem I. Instruction 题目连接: http://codeforces.com/gym/100531/attachments Description Ingrid is a he ...

  6. 转换到 COFF 期间失败: 文件无效或损坏 解决方法

    转自csdn 终极解决方案:VS2010在经历一些更新后,建立Win32 Console Project时会出“error LNK1123” 错误,解决方案为将 项目|项目属性|配置属性|清单工具|输 ...

  7. C#实现一个最简单的HTTP服务器

    简介 本文用C#实现了一个最简单的HTTP服务器类,你可以将它嵌入到自己的项目中,或者也可以阅读代码来学习关于HTTP协议的知识. 背景 高性能的WEB应用一般都架设在强大的WEB服务器上,例如IIS ...

  8. Android创建文件夹及文件并写入数据

    package elwin.fei.mobileaudio; import java.io.BufferedWriter; import java.io.File; import java.io.Fi ...

  9. 剑指 offer set 10 栈的压入、弹出序列

    总结 1. 通过按位对比来判断, 没有更优的方法了

  10. MYSQL 博客

    DavidYang的博客 - CSDN.NET DimitriK's (dim) Weblog Xaprb · Stay Curious! 飞鸿无痕的博客 - ChinaUnix博客 何登成的技术博客 ...