20 分离应用程序逻辑并处理事件

1.理解委托

定义:是对方法的引用。

平时调用方法方式:

Processor p = new Processor();

p.performCalculation();

委托对象引用了方法。和将int值赋给int变量样,是将方法引用赋给委托对象。

下例创建performCalculationDelegate 委托来引用Processor对象的performCalculation方法。这里故意省略了委托的声明,因为当前应该关注概念而非语法(稍后就会学到完整语法)。

Processor p = new Processor();

delegate ... performCalculationDelegate ..;

performCalculationDelegate = p.performCalculation;

将方法引用赋给委托时,并不是马上就运行方法。方法名之后没有圆括号,也不指定

任何参数。这纯粹就是一个赋值语句。

 

将对Processor对象的performCalculation方法的引用存储到委托中之后,应用程序就可通过委托来调用方法了,如下所示:

performCalculationDelegate();

 

看起来和普通方法调用无异:不知情的话还以为运行的是名performCalculationDelegate的方法。但CLR知道它是委托,所以自动获取引用的方法并运行之。之后可以更改委托引用的方法,使调用委托的语句每次执行都运行不同的方法。另外,委托可一次引用多个方法(把它想象成方法引用集合)。一且调用委托,所有方法都会运行。

 

1.1.NET Framework类库的委托例子

.NET Framework 类库在它的许多类型中广泛运用了委托,之前讲集合的时候,提到的List<T>类的Find和Exists方法。这两个方法搜索List<T>集合,返回匹配项或测试匹配项是否存在。设计List<T> 类时肯定不知道何谓“匹配”,所以要让开发人员自己定义,以“谓词”的形式指定匹配条件。谓词其实就是委托,只不过它恰好返回Boolean值而已。以下代码帮你复习Find方法的用法。

struct Person
{
    public int ID {get;set;}
    public string Name {get;set;}
    Public int Age {get;set;}
}
…
List<Person> personnel = new List<Person>()
new Person() { ID= 1, Name = "John", Age =47 },
new Person(){ ID= 2, Name = "Sid", Age = 28 },
new Person() { ID= 3,Name = "Fred", Age = 34 },
new Person() { ID= 4, Name = "Paul", Age = 22 },
};
...
//查找ID为3的第一个列表成员
Person match = personnel.Find(p =>p.ID == 3);

List<T>类利用委托执行操作的其他方法还有Average, Max, Min, Count和Sum.这些方法获取一个Func委托作为参数。Func 委托引用的是要返回值的一个方法(-一个函数)。

下例使用Average方法计算personnel 集合中的人的平均年龄(FunC<T>委托只是返回集合中每一项的Age字段的值),使用Max方法判断ID最大的人,并用Count方法计算多少个人年龄在30到39岁(含)之间。

double averageAge = personnel .Average(p => p.Age);
Console.WriteLine($"Average age is {averageAge}");
...
int id = personnel.Max(p => p.ID);
Console.WriteLine($"Person with highest ID is ({id)");
int thirties = personnel.Count(p => p.Age >= 30 && p.Age <= 39);
Console.NriteLine($"Number of personnel in their thirties is {thirties)");

代码输出如下:

Average age is 32.75

Person wdth highest ID is 4

Number of personnel in their thirties is 1

 

1.2自动化工厂的例子

假定要为一间自动化工厂写控制系统。工厂包含大量机器。生产时,每台机器都执行不同的任务:切割和折叠金属片、将金属片焊接到-起以及印刷金属片等。每台机器都由一家专业厂商制造和安装。机器均由计算机控制,每个厂商都提供了一套API,可利用这些API来控制他们的机器。你的任务是将机器使用的不同的系统集成到单独一个控制程序中。作为控制程序的- 部分,你决定提供在必要时快速关闭所有机器的一一个机制。每台机器都有自己的、由计算机控制的过程(和函数)来实现安全停机。具体如下:

StopFolding(); // 折叠和切剧机

FinishWelding(); // 焊接机

PaintOff();//彩印机

1.3不用委托实现工厂控制系统

为了在控制程序中实现停机功能,可采用以下简单的方式:

class Controller
{

//代表不同机器的字段
private FoldingMachine folder;
private WeldingMachine welder;
private PaintingMachine painter;
…
publlc vold ShutDown()
{
    folder .StopFolding();
    welder .FinishWelding();
    paInter.Paintoff();
}
...
}

虽然这种方式可行,但扩展性和灵活性都不好。如果工厂采购了新机器,就必须修改这些代码,因为Controller类和机器是紧密联系在一起的。

 

1.4用委托实现工厂控制系统

虽然每个方法的名称不同,但都具有相同的“形式”,即都不获取参数,也都不返回

值(以后会解释如果情况不是这样会发生什么)。所以,每个方法的常规形式如下:

void methodName();

这正是委托可以发挥作用的时候。使用和上述形式匹配的委托,就可引用任何停机方法。像下面这样声明委托:

delegate vold stopMachineryDelegate();

注意以下几点。

(1)声明委托要使用 delegate关键字。

(2)委托定义了 它所引用的方法的“形式”。要指定返回类型(本例是void)、委托名

称(stopMachineryDelegate)以及任何参数(本例无参数)。

 

定义好委托后,就可创建它的实例,并用+=操作符让该实例引用匹配的方法。在

Controller类的构造器中,可以像下面这样写:

class Controller
{
    delegate vold stopMachineryDelegate();   //声明委托类型
    private stopMachineryDelegate stopMachinery; // 创建委托实例
    …
    public Controller()
    {
        this. stopMachinery += folder .StopFolding;
    }    
    ...
}

上述语法需要一段时间来熟悉。 它只是将方法加到委托中:此时并没有实际调用方法。操作符+已进行了重载,所以在随同委托使用时,才具有了这个新的含义。(操作符重载的主题将在之后的博客中进行讨论。)注意,只需指定方法名,不要包含任何圆括号或参数。

可安全地将操作符+=用于未初始化的委托。该委托将自动初始化。还可使用new关键字显式初始化委托,让它引用一个特定的方法,示例如下:

this.stopMachinery = new stopMachineryDelegate(folder. stopFolding);

可通过调用委托来调用它引用的方法,示例如下:

public void ShutDown()
{
    this. stopMachinery();
    …
}

委托调用语法与方法完全相同。如果引用的方法要获取参数,应在圆括号内指定。

 

注意:调用没有初始化而且没有引用任何方法的委托会抛出NullReferenceException异常。

 

委托主要优势在于它能引用多个方法,使用操作符+=将这些方法添加到委托中即可,就像下面这样:

public Controller()

{

    this.stopMachinery += folder .StopFolding;

    this. stopMachinery += welder.Finishivelding;

    this. stopMachinery += painter .Paintoff;

}

在Controller类的Shutdown方法中调用this .stopMachinery(),将自动依次调用上述每一个方法。Shutdown 方法不需要知道具体有多少台机器,也不需要知道方法名。使用复合赋值操作符-=,则可从委托中移除一个方法:

this.stopMachinery -= folder .StopFolding;

我们当前的方案是在Controller类的构造器中将机器的停机方法添加到委托中。为

了使Controller类完全独立于各种机器,需要使stopMachineryDelegate成为公共,井

提供一种方式允许Controller外部的类向委托添加方法。有以下几个选项。

(1)将委托变量 stopMachinery声明为公共:

public stopMachineryDelegate stopMachinery;

(2)保持stopMachinery委托变量私有,但提供可读/可写属性来访问它:

private stopMachineryDelegate stopMachinery;

…

public stopMachineryDelegate StopMachinery
{
    get
    {
        return this. stopMachinery;
    }

    set
    {
        this.stopMachinery = value;
    }

}

(3)实现单独的Add和Remove方法来提供完全的封装性。Add方法获取一个方法作为参数,并把它添加到委托中: Remove 则从委托中移除指定的方法(注意,添加或

移除的方法要作为参数来传递,参数的类型就是委托类型):

public void Add(stopMachineryDelegate stopMethod)
{
    this.stopMachinery += stopMethod;
}

public void Remove(stopMachineryDelegate stopMethod)
{
    this. stopMachinery -= stopMethod;
}

如果坚持面向对象的编程原则,或许会倾向于Add/Remove方案.但其他方案同样可行,也同样被广泛运用,所以这里列出了全部方案。

 

无论采用哪个方案,在Controller构造器中都应移除将机器方法添加到委托的代码。

然后可以实例化Controller,并实例化代表其他机器的对象,如下所示(采Add/Remove

方案):
 

Controller control = new Controller();
FoldingMachine folder = new FoldingMachine();
Weldingachine welder = new WeldingMachine();
PaintinpMachine painter = new Paintingachine();
…
control ,Add(folder . StopFolding);
control .Add(welder .Finishwelding);
control .Add(painter ,Paintoff);
…
control . ShutDown();
…

2.Lambda表达式和委托

迄今为止在向委托添加方法的所有例子中,都只是使用方法名。例如前面的自动化工厂例子,为了将folder对象的StopFolding方法添加到stopMachinery 委托中,我们是这样写的:

this.stopMachinery += folder.StopFolding;

简单的方法和委托签名匹配,这种写法是合适的。但是,如果情况并非如此又该怎么办呢?假定StopFolding方法实际的签名如下所示:

void StopFolding(int shutDownTime); //在指定秒数后停机

它的签名现在有别于FinishWelding及Paintoff方法,所以,不能再拿同一个委托处理全部三个方法。

 

创建方法适配器

一个解决方案是创建另一个方法,在内部调用StopFolding.自身不获取任何参数:

void FinishFolding()
{

    folder . StopFolding(0); //立即停机

}

然后,就可以将FinishFolding方法(而不是StopFolding方法)添加到stopMachinery

委托中。语法和以前一样:

this. stopMachinery += folder.FinishFolding;

调用stopMachinery委托实际会调用FinishFolding,后者又会调用StopFolding方法并传递参数值0。

 

许多时候,像这样的适配器方法非常小,很难在方法的“汪洋大海”中找到它们(尤其

是在一个很大的类中)。此外,除了适配StopFolding方法供委托使用,其他地方一般用不上。C#针对这种情况提供了Lambda表达式。

在工厂的例子中,可以使用以下Lambda表达式:

this.stopMachinery +=(() => folder .StopFolding(0));

调用stopMachinery委托时会运行Lambda表达式定义的代码,后者调用StopFolding

方法并传递恰当的参数。

 

3.启用事件通知

本章前面展示了如何声明委托类型、调用委托以及创建委托实例。但工作只完成了一半。虽然委托允许间接调用任意数量的方法,但仍然必须显式调用委托。许多时候需要在发生某事时自动运行委托。例如,在自动化工厂的例子中,如果-台机器过热,就应该自动调用stopMachinery 委托来关闭设备。

 

.NET Framework提供了事件。可定义并捕捉特定的事件,并在事件发生时调用委托来进行处理。.NET Framework的许多类都公开了事件。能放到UWP应用的窗体上的大多数控件以及Window类本身,都允许在发生特定事件(例如单击按钮或输入文字)时运行代码。还可声明自己的事件。

 

3.1声明事件

事件在准备作为事件来源的类中声明。事件来源类监视其环境,在发生某件事情时引发事件。在自动化工厂的例子中,事件来源是监视每台机器温度的一个类。检测到机器超出热辐射上限(过热),温度监视器类就引发“机器过热”事件。事件维护着方法列表,引发事件将调用这些方法。有时将这些方法称为订阅者。这些方法应准备好处理“机器过热”事件并能采取必要的纠正行动:停机!

 

声明事件和声明字段相似。但由于事件随同委托使用,所以事件的类型必须是委托,而且必须在声明前附加event前缀。用以下语法声明事件:

event delegateTypeName eventName // delegateTypeName是委托类型名称。

   // eventName是事件名称

 

例如,以下是自动化工厂的StopMachineryDelegate 委托。它现在被转移到新类

TemperatureMonitor(温度监视器)中。该类为监视设备温度的各种电子探头提供了接口(相较于Controller类,这是放置事件的一个更合理的地方)。

class TemperatureMonltor
{
    public delegate vold StopMachineryDelegate();
    …
}

可以定义MachineOverheating事件,该事件将调用stopMachineryDelegate,就像下面这样:

class TemperatureMonitor
(

    publlc delegate vold StopMachineryDelegate();
    public event StopMachineryDelegate MachineOverheating;
    …

}

3.2订阅事件

类似于委托,事件也用+=操作符进入就绪状态。我们使用+=操作符订阅事件。在自动工厂的例子中,一旦引发MachineOverheating事件就调用各种停机方法,如下所示:

class TemperatureMonitor
{

    public delegate void StopMachineryDelegate();
    public event StopMachineryDelegate MachineOverheating;
    …
}

…
TemperatureMonitor tempMonitor = new TemperatureMonitor();
…
tempMonitor. MachineOverheating +=() => { folder .StopFolding(0); };
tempMonitor .MachineOverheating += welder. inishwelding;
tempMonitor .MachineOverheating += painter.Paintoff;

注意,语法和将方法添加到委托中的语法相同。甚至可以使用Lambda表达式来订阅。tempMonitor .MachineOverheating事件发生时,会调用所有订阅了该事件的方法,从而关停所有机器。

 

3.3取消订阅事件

+=操作符用于订阅事件:对应地,-=操作符用于取消订阅。一=操作符将一个方法从事件的内部方法集合中移除。该行动通常称为取消订阅事件或者从事件注销(unsubscribing from a event)。

 

3.4引发事件

事件可像方法一-样调用来引发。引发事件后,所有和事件关联的委托会被依次调用。例如,TemperatureMonitor 类声明私有方法Notify来引发MachineOverheating事件:

class TemperatureMonitor
{
    public delegate void StopMachineryDelegate;
    publilc event StopMachineryDelegate MachineOverheating;
    …
    private vold Notify()
    {
        if (this.MachineOverheating != nul1)
        {
            this .MachineOverheating();
        }
    }
    …

}

这是一种常见的写法。null检查是必要的,因为事件字段隐式为null, 只有在一个

方法使用+=操作符来订阅它之后,才会变成非null.引发null事件将抛出

NullReferenceException异常。如果定义事件的委托要求任何参数,引发事件时也必须提供。

 

参考书籍:《Visual C#从入门到精通》

发布了46 篇原创文章 · 获赞 53 · 访问量 3702

猜你喜欢

转载自blog.csdn.net/qq_38992372/article/details/105027860
20)
$20
20
今日推荐