参考书:《 visual C# 从入门到精通》
第三部分 用C#定义可扩展类型
第20章 分离应用程序逻辑并处理事件
文章目录
20.1 理解委托
委托是对方法的引用。如:
Processor p=new Processor();
delegate ...performCalculationDelegate...;
performCalculationDelegate=p.performCalculation;
performCalculationDelegate();
应用程序通过委托来调用方法:performCalculationDelegate()
,看起来就像是运行一个名为performCalculationDelegate
的方法,但CLR知道它是委托。后面还可以更改委托引用的方法。委托可以一次引用多个方法。
20.1.1 .NET Framework类库的委托例子
第18章中,List<T>
类的Find
和Exists
方法,它搜素集合然后返回匹配项或者测试匹配项是否存在,但我们事先是不知道怎么匹配的,匹配方式是要我们自己定义,通过谓词的形式指定匹配方式。这个谓词就是委托。
List<T>
类中的Average
、Max
,Count
和其他方法获取的参数实际上是泛型Func<T,TResult>
委托,两个类型参数分别是传给委托的类型和返回值的类型,如int Thirties=personnel.Count(p=>p.Age>=30&&p.Age<=39);
,其中Count
方法期待的委托类型是Func<Person,int>
。另外,还有Action
委托类型,Action
委托引用的是采取行动而不是返回值的方法,即void
方法。
20.1.2 自动化工厂的例子
下面我们假定为一间自动化工厂写控制程序。工厂包含很多机器。我们的任务是将机器使用的不同的系统集成到一个控制程序中。为此,你需要提供在必要时快速关闭所有机器的一个机制。每台机器都有自己的方法来实现安全停机:
StopFolding();//折叠和切割机
FinishWelding();//焊接机
PaintOff();//彩印机
20.1.3 不用委托实现工厂控制系统
可以采用下面简单的方法实现停机功能:
class Controller{
private FoldingMachine folder;
private WeldingMachine welder;
private PaintingMachine painter;
...;
public void ShutDown(){
folder.StopFolding();
welder.FinishWelding();
painter.PainrtOff();
}
...;
}
这种方法是可行的,但扩展性和灵活性不好。Controller
类是和机器绑定的,如果购入新机器,又需要修改这些代码了。
20.1.4 用委托实现工厂控制系统
首先像这样声明委托:
delegate void stopMachineryDelegate();
注意:
- 用
delegate
来声明委托 - 委托定义了它引用的方法的形式。指定返回类型、委托名称、和所有的参数
定义委托之后就可以创建它的实例了,并用+=
操作符让该实例引用匹配的方法。如:
class Controller{
delegate void stopMachineryDelegate();
private stopMachineryDelegate stopMachinery;
...;
public Controller(){
this.stopMachinery+=folder.StopFolding;
}
...;
}
上述代码将方法加到委托中,但还没有实际调用方法。这里的+
操作已经重载了的,所以它具有特殊的含义。注意指定方法名是不要加圆括号和任何参数。
可以将+=
用于任何未初始化的委托,该委托会自动初始化。也可以用new
来显式初始化委托,让它引用一个特定的方法:
this.stopMachinery=new stopMachineryDelegate(floder.stopFolding);
然后可以通过调用委托来调用引用:
public void ShutDown(){
this.stopMachinery();
}
委托的一个优势是它可以引用多个方法:
public Controller(){
this.stopMachinery+=folder.StopFloding;
this.stopMachinery+=welder.FinishWelding;
this.stopMachinery+=painter.PaintOff;
}
调用this.stopMachinery()
将自动依次调用这些方法。也可以用-=
从委托中移除一个方法:
this.stopMachinery-=folder.StopFloding;
20.1.5 声明和使用委托
pass;
20.2 Lambda 表达式和委托
我们前面这样来向委托添加方法:this.stopMachinery+=folder.StopFolding;
,但如果StopFolding
方法的签名是:void StopFolding(int ShutDownTime);
,这样我们就不能用同一个委托处理三个方法了。这时的一个解决方案是创建另一个方法:
void FinshFolding(){
folder.StopFolding(0);
}
这样就可以将FinishFolding
添加到委托中了。这是适配器的一个典型的例子。C#还提供了另一个方案,用Lambda
表达式:this.stopMachinery+=(()=>folder.StopFolding(0));
20.3 启用事件通知
.NET Framework
提供了事件。可以定义并捕捉事件,并在事件发生时调用委托来处理。
20.3.1 声明事件
和声明字段相似,由于事件和委托一起使用,所以事件的类型必须是委托,且在声明前附加event
前缀:event delegateTypeName eventName
如,对于自动化工厂的StopMachineryDelegate
委托。它现在被转移到新类TemperatureMonitor
温度监视器中:
class TemperatureMonitor{
public delegate void StopMachineryDelegate();
public event StopMachinery MachineOverheating;
...;
}
我们要把方法家倒事件中,即订阅事件或者**向事件登。
20.3.2 订阅事件
我们也是使用+=
来订阅事件:
class TemperatureMonitor{
public delegate void StopMachineryDelegate();
public event StopMachinery MachineOverheating;
...;
}
...;
TemperatureMonitor tempMonitor=new TemperatureMonitor();
...;
tempMonitor.MachineOverheating+=()=>{folder.StopFolding(0);};
tempMonitor.MachineOverheating+=welder.Finishwelding;
tempMonitor.MachinerOverheating+=painter,paintOff;
20…3.3 取消订阅事件
用-=
来取消订阅事件。
20.3.4 引发事件
引发事件后,所有和事件关联的委托会被依次调用:
class TemperatureMonitor{
public delegate void StopMachineryDelegate();
public event StopMachinery MachineOverheating;
...;
private void Notify(){
if(this.MachineOverheating!=null){
this.MachineOverheating();
}
}
...;
}
null
检查是必要的,因为事件没有订阅方法的时候是null
的,引发null
的事件会抛出NullReferenceException
异常。
20.4 理解用户界面事件
用于构造GUI
的.NET Framework
类和控件广泛运用了事件。如从ButtonBase
类派生的Button
类继承了RoutedEventHandler
类型的公共事件Click
。RoutedEventHandler
委托要求两个参数:一个是引发事件的对象的引用,另一个是EventArgs
对象,它包含关于事件的额外信息:
public delegate void RoutedEventHandler(object sender,RoutedEventArgs e);
Button
类的定义如下:
public class ButtonBase:...{
public event RoutedEventHanderler Click;
...;
}
public class Button:ButtonBase{
...;
}
单击按钮,Button
类将引发Click
事件。
partial class MainPage:
global::Windows.UI.Xaml.Controls.Page,
global::Windows.UI.Xaml.Markup.IComponentConnector,
global::Windows.UI.Xaml.Markup.IComponentConnector2{
...;
public void Connect(int connectionId,object target){
switch(conncetionId){
case 1:{
this.okay=global::Windows.UI.Xaml.Controls.Button)(target);
...;
((global::Windows.UI.Xaml.Controls.Button)this.okay).Click+=this.okayClick;
...;
}
break;
default;
break;
}
this._contentLoaded=true;
}
...;
}
这些代码都是隐藏起来的,在VS中操作时他会自动生成,我们要做的其实只是写事件处理方法:
public sealed partial class MainPage:page{
...;
private void okayClick(object sender,RoutedEventArgs args){
//Click事件的处理
}
}
实际上这些东西只有在真正的做一些项目时才能感受到它的价值的,一般情况下不一定用的上。下面例子强行用到这一章的委托和事件:
在VS中新建一个空白应用,在设计视图中将控件布局成这样:
比较麻烦的是需要放入四个GridView
控件。MainPage.xaml.cs
中的代码如下:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
// https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x804 上介绍了“空白页”项模板
namespace c_20_1_5
{
/// <summary>
/// 可用于自身或导航至 Frame 内部的空白页。
/// </summary>
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
public string n1 = "PowerDrill", n2 = "Hammer";
public double price1 = 75.5, price2 = 18.35;
public delegate void showMessage();
public event showMessage showEvent;
private showMessage showmess;
public void addDelegate()
{
this.showmess += this.n1Show;
this.showmess += this.n2Show;
}
private void powerDrillClick(object sender, RoutedEventArgs e)
{
this.quantity1++;
updata();
}
private void Notify()
{
if (this.showEvent != null)
this.showEvent();
}
private void HammerClick(object sender, RoutedEventArgs e)
{
this.quantity2++;
updata();
}
private void CheckoutClick(object sender, RoutedEventArgs e)
{
message.Text = "";
this.showmess = null;
this.addDelegate();
this.showmess();
this.showEvent = null;
this.showEvent += this.n1Show;
this.showEvent += this.n2Show;
this.Notify();
}
private void updata()
{
n1text.Text = $"{this.n1}--{this.quantity1}";
n2_text.Text = $"{this.n2}--{this.quantity2}";
total_text.Text = $"Total: {this.quantity1 * this.price1 + this.quantity2 * this.price2}";
}
private void n1Show()
{
message.Text += $"Item: {this.n1}, Quantity: {this.quantity1}, " +
$"Money: {this.price1}*{this.quantity1}={this.quantity1 * this.price1}\n";
}
private void n2Show()
{
message.Text += $"Item: {this.n2}, Quantity: {this.quantity2}, " +
$"Money: {this.price2}*{this.quantity2}={this.quantity2 * this.price2}\n";
}
public int quantity1 = 0, quantity2 = 0;
}
}
运行效果如下: