委托
委托是一个类,它定义了方法的类型,可以将方法作为另一个方法的参数进行传递,这种将方法动态地赋给参数的做法,可以避免在程序中大量使用If-else(Switch)语句,同时使得程序具有更好的可拓展性。
委托的声明格式: public delegate void 委托名(参数类型 参数);
我们先举个例子,假设我们遇到朋友要问好,如早上好,但是外国友人可能听不懂中文,需要用对应的语言进行沟通,如使用英语,所以要打招呼就需要知道对方的名字以及他的语言,我们可以使用方法来实现:
class Program
{
public static void GreetPeople(string name,string language)
{
if(language == "Chinese")
{
GreetChinese(name);
}
else if(language == "English")
{
GreetEnglish(name);
}
}
public static void GreetChinese(string name)
{
Console.WriteLine("早上好," + name);
}
public static void GreetEnglish(string name)
{
Console.WriteLine("Good Morning," + name);
}
static void Main(string[] args)
{
GreetPeople("小赵","Chinese");
GreetPeople("York","English");
Console.ReadKey();
}
}
运行结果:早上好,小赵
Good Morning,York
OK,方法很简单,实现了对不同国家的人问好,但是如果遇到的人很多而且来自不同国家,我们是不是还要在GreetPeople中使用if-else进行不断判断,这样就会牵一发而动全身,并且可拓展性会非常差。
我们再回来仔细看GreetPeople方法的内容,发现我们是通过判断language来选择不同的方法,我们能不能直接跳过这个步骤呢?直接将GreetChinese方法以及GreetEnglish方法作为参数放到GreetPeople岂不是很方便,这样我们添加更多的方法也不用修改GreetPeople,只需要传进去不同参数就可以了,这其实就已经是委托事件了,我们可以看一下实现方式:
//定义委托事件
public delegate void GreetingDelegate(string name);
class Program
{
public static void GreetPeople(string name,GreetingDelegate MakeGreet)
{
MakeGreet(name);
}
public static void GreetChinese(string name)
{
Console.WriteLine("早上好," + name);
}
public static void GreetEnglish(string name)
{
Console.WriteLine("Good Morning," + name);
}
static void Main(string[] args)
{
GreetPeople("小王", GreetChinese);
GreetPeople("York", GreetEnglish);
Console.ReadKey();
}
}
运行结果:早上好,小王
Good Morning,York
是不是很简单,我们回头看定义,委托是一个类,它定义了方法的类型,可以将方法作为参数进行传递,如string对于字符串。
聪明的你可能已经想到了,我们可以像string一样直接定义一个委托变量,如:
static void Main(string[] args)
{
GreetingDelegate greet1 = GreetChinese;
GreetingDelegate greet2 = GreetEnglish;
GreetPeople("小王",greet1);
GreetPeople("York", greet2);
Console.ReadKey();
}
如你所料,这样也是正确的,这里我想说的是委托不同于string的一个特性是:可以将多个方法赋予同一个委托,当调用该委托时可以依次调用其它所绑定的方法。如:
static void Main(string[] args)
{
GreetingDelegate greet1 = GreetChinese;
greet1 += GreetEnglish;
GreetPeople("小王",greet1);
Console.ReadKey();
}
运行结果:早上好,小王
Good Morning,小王
这里需要注意的是,委托需要先通过“=”进行赋值,才能够使用“+=”进行绑定多个方法,否则会出现“使用了未赋值的局部变量”的编译错误。
如果委托绑定了多个方法,再重新赋值为null或者其他方法,则会将前面的方法全部清空。
委托实例化,我们可以直接绕过GreetPeople。如:
static void Main(string[] args)
{
GreetingDelegate greet1 = new GreetingDelegate(GreetChinese);
greet1 += GreetEnglish;
greet1("小王");
Console.ReadKey();
}
运行结果:早上好,小王
Good Morning,小王
委托总结
(1)委托是一个类,它定义了方法的类型,可以将方法作为另一个方法的参数进行传递。
(2)委托变量可以绑定多个方法,当调用此变量时,可以依次调用所有绑定的方法。
(3)委托变量可以通过“+=”增加绑定,通过“-=”解除绑定。
事件
继续上面的例子,在实际应用中GreetPeople应该放在一个单独的类中,然后对代码进行修改,修改之后变为:
public delegate void GreetingDelegate(string name);
class GreetManage
{
public GreetingDelegate delegate1;
public void GreetPeople(string name)
{
if (delegate1 != null)
{
delegate1(name);
}
}
}
class Program
{
public static void GreetChinese(string name)
{
Console.WriteLine("早上好," + name);
}
public static void GreetEnglish(string name)
{
Console.WriteLine("Good Morning," + name);
}
static void Main(string[] args)
{
GreetManage g = new GreetManage();
g.delegate1 = GreetChinese;
g.GreetPeople("小钢");
Console.ReadLine();
}
}
声明委托就是为了将其暴露在类的客户端进行方法的注册,所以只能声明为Public。同时我们也可以看到,在客户端可以对委托进行任意赋值的操作,会严重破坏对象的封装性。 类似于声明为Public的string类型,我们可以使用属性对字段进行封装,那么委托是否也能够进行封装呢?当然是可以的,所以我们就需要用到Event(事件),它可以封装委托类型的变量。
事件的声明格式: public event 委托名 事件名;
我们从声明格式可以看到,事件跟委托是分不开的,其实事件也非常好理解,无非就是声明了一个进行了封装的委托类型的变量。
如:
//先声明委托类型
public delegate void BoilerLogHandler(string status);
//声明基于上面的委托定义事件
public event BoilerLogHandler BoilerEvent;
我们修改GreetManage类:
class GreetManage
{
public event GreetingDelegate MakeGreet;
public void GreetPeople(string name)
{
MakeGreet(name);
}
}
然后修改Main方法:
static void Main(string[] args)
{
GreetManage g = new GreetManage();
g.MakeGreet = GreetEnglish;//该行会报错
g.MakeGreet += GreetChinese;
g.GreetPeople("小钢");
Console.ReadLine();
}
这里我们会发现报错,说明事件无法进行赋值,只能通过+=或-=进行方法绑定。从这里我们可以看出事件能够保护对象的封装性。
为什么要使用事件而不是委托变量
除了事件能够保护对象的封装性,还有最重要的一点是:事件应由发布者触发,而不应该由客户端来触发。
Ps:事件中各模块可以分为发布者(publisher)、订阅者(subscriber)、客户端(client)。
GreetingDelegate greet1 = new GreetingDelegate(GreetChinese);
greet1 += GreetEnglish;
greet1("小王");
我们可以看到前面的例子,发现委托可以在客户端中越过发布者,直接触发订阅者的响应事件。其实这是非常不合理的,因为你直接使用委托变量触发事件,会影响到所有已经注册了该事件的订阅者。
我们再来探究下事件的定义,事件是对象用于(向系统中的所有相关组件)广播已发生事情的一种方式。任何其他组件都可以订阅事件,并在事件引发时得到通知。简单地说,就比如用户的操作如按键、移动鼠标等,应用程序需要在事件发生时响应事件。
由此可见,事件应该是由事件发布者在其本身的某个行为中触发的,只能被其他组件订阅,而不能在客户端中直接触发事件。所以我们就需要用event关键字来发布事件,能够禁止在客户端直接触发事件,只能通过调用发布者的方法来触发事件。如:
GreetManage g = new GreetManage();
g.delegate1 = GreetChinese;
g.GreetPeople("小钢");