C#综合揭秘——深入分析委托与事件
引言
本篇文章将为你介绍一下 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:MulticastDelegate2 {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 Worker36 {37 public abstract double GetWages(double basicWages);38 }39 40 public class Manager:Worker41 {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 : Worker52 {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 Assistant14 {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 WageManager24 {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 else15 return GetPriceHandler();16 }17 return -1;18 }19 }20 21 class Program22 {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 else15 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 EventTest2 {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 Program16 {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 Program10 {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 Message11 {12 get { return args; }13 set { args = value; }14 }15 }16 17 public class EventManager18 {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 Program29 {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 Person29 {30 public Person(int id, string name, int age)31 {32 ID = id;33 Name = name;34 Age = age;35 }36 37 public int ID38 { get; set; }39 public string Name40 { get; set; }41 public int Age42 { 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 else14 return a * 2;15 }
5.4 揭开 Lambda 神秘的面纱
Lambda 的表达式的编写格式如下:
当中 “ => ” 是 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 Person25 {26 public Person(int id, string name, int age)27 {28 ID = id;29 Name = name;30 Age = age;31 }32 33 public int ID34 { get; set; }35 public string Name36 { get; set; }37 public int Age38 { get; set; }39 }
当在使用LINQ技术的时候,到处都会弥漫着 Lambda 的身影,此时更能体现 Lambda 的长处。
但 LINQ 涉及到分部类,分部方法,IEnumerable<T>,迭代器等多方面的知识,这些已经超出本章的介绍范围。
通过这一节的介绍,希望能够帮助大家更深入地了解 Lambda 的使用。
回到目录
本章小结
本章主要介绍了委托(Delegate)的使用,委托对象是一个派生自 System.MultcastDelegate 的类,它能通过 Invoke 方式进行同步调用,也可以通过 BeginInvoke,EndInvoke 方式实现异步调用。而事件(Event)属于一种特殊的委托,它与委托类型同步使用,可以简化的开发过程。
最后,本文还介绍了匿名方法的使用方式,以及 Lambda 表达式的由来。
对 .NET 开发有兴趣的朋友欢迎加入QQ群:230564952 共同探讨 !
C#综合揭秘
通过修改注册表建立Windows自定义协议
Entity Framework 并发处理详解
细说进程、应用程序域与上下文
细说多线程(上)
细说多线程(下)
细说事务
深入分析委托与事件
作者:风尘浪子
http://www.cnblogs.com/leslies2/archive/2012/03/22/2389318.html
原创作品,转载时请注明作者及出处
原文链接:http://www.cnblogs.com/leslies2/archive/2012/03/22/2389318.html
转载于:https://www.cnblogs.com/Percy_Lee/p/5146333.html
C#综合揭秘——深入分析委托与事件相关推荐
- C#综合揭秘——深入分析委托与事件(上)
引言 本篇文章将为你介绍一下 Delegate 的使用方式,逐渐揭开 C# 当中事件(Event)的由来,它能使处理委托类型的过程变得更加简单. 还将为您解释委托的协变与逆变,以及如何使用 Deleg ...
- [转]C#综合揭秘——细说多线程(上)
C#综合揭秘--细说多线程(上) 引言 本文主要从线程的基础用法,CLR线程池当中工作者线程与I/O线程的开发,并行操作PLINQ等多个方面介绍多线程的开发. 其中委托的BeginInvoke方法以及 ...
- C#综合揭秘——细说多线程(上)
引言 本文主要从线程的基础用法,CLR线程池当中工作者线程与I/O线程的开发,并行操作PLINQ等多个方面介绍多线程的开发. 其中委托的BeginInvoke方法以及回调函数最为常用. 而 I/O线程 ...
- [转]C#综合揭秘——细说进程、应用程序域与上下文之间的关系
引言 本文主要是介绍进程(Process).应用程序域(AppDomain)..NET上下文(Context)的概念与操作. 虽然在一般的开发当中这三者并不常用,但熟悉三者的关系,深入了解其作用,对提 ...
- C#笔记(二):委托与事件
本文内容: 1. 委托定义 2. 申明委托 3. 委托的实例化 4. 委托的调用 5. 匿名方法 6. 委托程序实例 7. 事件定义 8. 事件的申明 9. 事件的调用 10. 事件订阅与移除 ...
- java委托事件与观察者模式_多播委托与观察者模式联合使用,以及委托与事件的区别...
首先我们先看一下多播委托: 使用委托时,首先我们声明委托,委托语法一共有四种类型,分别时有参,无参,以及有无参数返回值. 1 public class DelegateShow //: System. ...
- 如鹏网 net高级技术 第二章 委托和事件(复习)
委托 委托是一种数据类型,可以声明委托类型变量. 委托是一种可以指向方法的数据类型. 声明委托的方式:delegate返回值类型 委托类型名(参数) 比如 delegate void MyDel( ...
- C# 委托与事件总结
C# 委托与事件总结 推荐资料 委托 使用 C# 内置委托:Action 和 Func 自定义委托 委托的综合实例 多播委托 事件 事件声明的完整格式 事件声明的简略格式 事件的本质 使用 Event ...
- C#综合揭秘——细说事务
原文: http://www.cnblogs.com/leslies2/archive/2012/01/05/2289106.html 风尘浪子 只要肯努力,梦想总有一天会实现 笔记: 事务的定义: ...
最新文章
- 一个注解搞定接口防刷!还有谁不会?
- ESX虚拟机文件列表详解
- Spring Boot配置@spring.profiles.active配置
- SQL Server 2008安装配置说明书+简单使用 亲测可用
- 简易贪吃蛇小游戏java版_用GUI实现java版贪吃蛇小游戏
- IOS 调用系统照相机和相册
- 前端学习(97):psd切图流程
- linux那些事之page fault(AMD64架构)(user space)(2)
- windows下搭建SSH隧道内网映射
- css布局的漂浮、position定位
- 信号检测与估计理论_又又又送书啦!视觉SLAM十四讲:从理论到实践(第2版)...
- modbus协议的常用测试工具
- 怎么清理计算机磁盘空间,怎样清理电脑磁盘空间
- 开博尔智能android播放器,高端安卓播放器的选择——开博尔Q10Plus 二代 4K高清播放器...
- 11年小伙被武汉大学“录取”,上学4年后蒙了,学校:没录取你啊
- 移动硬盘装Ubuntu系统小记
- coreldraw怎么画猴子_小猴子的画法
- Excel图表—超级好用的Bullet图(KPI考核图)
- 单商户商城系统功能拆解35—分销应用—分销概览
- truncate表后恢复方法总结