委托与事件(1)

原创链接



网上讲C#委托和事件的博文已经非常多了,其中也不乏一些深入浅出、条理清晰的文章。我之所以还是继续写,主要是借机整理学习笔记、归纳总结从而理解更透彻,当然能够以自己的理解和思路给其他人讲明白更好。
另外,太长的文章会让很多读者失去兴趣,所以我决定把这篇分成四个部分来介绍。分别是委托的基础、委托的进阶、事件的基础和事件的进阶。对使用委托与事件要求不高的同学可以跳过进阶部分。

首先,本小节我们来介绍一下委托最最基础的部分,在列举这些基础知识之前,我们先从实例出发看看为什么要使用委托,以及什么情况下需要使用委托。


1. 为什么要使用委托?

假设我们有一个这样的需求,写一个MakeGreeting函数,这个函数在被调用的时候需要告诉它两点:跟谁greet怎么greet
我们的第一反应可能是,很简单呀,给这个函数传两个参数,就传跟谁greet怎么greet。如果怎么greet只是一个string,当然可以这样做,可万一它们没那么简单呢?
继续假设,假设怎么greet只有两种情况HelloGoodbye,分别是下面代码中的两个函数。(虽然函数里只写了一句输出,但是我们假设它们还要做一些其他事情,只是没有写出来而已。要不然可能有人会疑问为什么要搞这么复杂啦)

根据上面的需求描述,完成第一版程序:

namespace TestDelegate
{
    public enum Greeting
    {
        Hello, Goodbye
    }
    class Program
    {
        public static void Hello(string s)
        {
            Console.WriteLine("  Hello, {0}!", s);
            // do something (hug or shake hand...)
        }
        public static void Goodbye(string s)
        {
            Console.WriteLine("  Goodbye, {0}!", s);
            // do something (hug or wave hand...)
        }
        static void MakeGreeting(string name, Greeting greeting)
        {
            switch (greeting)
            {
                case Greeting.Hello: Hello(name); break;
                case Greeting.Goodbye: Goodbye(name); break;
            }
        }
        static void Main(string[] args)
        {
            MakeGreeting("May", Greeting.Hello);
            MakeGreeting("April", Greeting.Goodbye);
        }
    }
}

输出内容:

 Hello, May!
 Goodbye, April! ```

这样写当然是可以的,只是扩展性并不好,如果需要再加更多的`Greeting`就需要改三个地方:(1) 新增`Greeting`相关的方法、(2) `Greeting`枚举里添加值、(3) 在`MakeGreeting`函数的`switch`语句里添加对新增`Greeting`的处理。
也就是说每增加一个`Greeting`方法时,还需要增加枚举并在`MakeGreeting`里面把新增的方法与枚举值关联起来。

那么问题来了,我们可不可以直接把`Greeting`方法(如`Hello`, `Goodbye`)传进`MakeGreeting`函数里呢?像`C++`里的函数指针那样。这样就不需要`Greeting`枚举,也不需要在`MakeGreeting`函数里面进行`switch`选择了。
答案当然是可以的,委托就可以是一系列类似方法(这里类似是指参数值列表和返回值可以用一个模板表示出来)的类,它的对象就是不同的方法,所以可以用委托把这一系列`Greeting`方法(对象)的共性(类)定义出来,然后给`MakeGreeting`函数传递一个该委托(共性类)的对象(就是一个`Greeting`方法)。

于是,就有了下面利用委托来完成上述需求的第二版程序:

namespace TestDelegate
{
delegate void GreetingDelegate(string s); //声明委托,定义Greeting方法的类
class Program
{
public static void Hello(string s)
{
Console.WriteLine(” Hello, {0}!”, s);
// do something (hug or shake hand…)
}
public static void Goodbye(string s)
{
Console.WriteLine(” Goodbye, {0}!”, s);
// do something (hug or wave hand…)
}
static void MakeGreeting(string name, GreetingDelegate d)
{
d(name);
}
static void Main(string[] args)
{
GreetingDelegate d1 = Hello; //定义委托的一个对象(将方法绑定到委托)
GreetingDelegate d2 = Goodbye; //定义委托的另一个对象
MakeGreeting(“May”, d1);
MakeGreeting(“April”, d2);
}
}
}“`
输出内容:

 Hello, May! 
Goodbye, April! `</span><span class="hljs-string">

小结:如何实现一个委托
(1) 声明一个</span>delegate<span class="hljs-string">对象,它与我们想要定义的一系列方法具有相同的参数和返回值类型。如:
</span>public delegate void GreetingEventHandler(string name)<span class="hljs-string">
(2) 委托的实例化。创建</span>delegate<span class="hljs-string">对象,并将我们想要使用的方法绑定到委托。(下面会在基础知识里面细讲委托实例化与类实例化的区别,以及讲方法绑定到委托的不同方法)
(3) 使用委托。使用委托中绑定的方法时,直接传递绑定有该方法的委托对象,或者直接通过委托对象调用绑定的方法。
例如:上例中我们可以把绑定有</span>Hello<span class="hljs-string">方法的委托对象</span>d1<span class="hljs-string">传递给</span>MakeGreeting<span class="hljs-string">函数,在函数内实现方法的调用。还可以直接通过</span>d1<span class="hljs-string">来调用方法,即</span>d1(<span class="hljs-string">"May"</span>);<span class="hljs-string">会直接输出</span>Hello, May!<span class="hljs-string">

2. 什么情况下使用委托?

上面的例子已经给出了一种情况,就是我们需要在运行时动态地确定具体的调用方法。其实这种简单的情况用接口也可以实现,因为接口也是一系列相似方法的抽象,类的继承与多态也可以实现运行时调用不同的方法。所以像上例中的情况下,何时该用委托呢?

a) 当使用事件设计模式时。

就是</span>Observer<span class="hljs-string">设计模式,它定义了对象之间一对多的关系,并且通过事件触发机制关联它们。当一个对象中发生了某事件后,依赖它的其他对象会被自动触发并更新。在事件部分我们会更细致地介绍。

b) 当需要封装静态方法时。

委托绑定的方法可以是静态方法、非静态方法和匿名方法,而C#中接口不能是静态的。

c) 当调用方不需要访问实现该方法的对象中的其他属性、方法或接口时。

我们可以把一个类中的某个成员函数绑定到委托,调用该方法时只与这个成员函数有关,与该类里的其他属性无关。

d) 当需要方便的组合时。

一个委托绑定的多个方法可以自由组合。一个委托的对象可以绑定多个方法,而且这多个方法是没有限制可以任意组合的,委托灵活的绑定和解绑定策略使得使用非常方便。

e) 当类可能需要该方法的多个实现时。

一个委托的对象可以绑定多个方法,当我们运行时需要的不是单一的方法时,接口很难实现。

举例说明使用委托发送对象状态通知。我直接以</span>《精通C<span class="hljs-comment">#》中的一个例子来说明这种用法。
我们有一个Car类型,在Car中定义一个委托并封装一个委托的对象(这可以用事件实现,目前先这样写,实际上这样做是不对的)。然后我们通过委托来向外界发送对象状态的通知。

namespace TestDelegate2
{
public class Car
{
public int CurrentSpeed { get; set; }
public int MaxSpeed { get; set; }
public string PetName { get; set; }
public Car() { MaxSpeed = 100; }
public Car(string name, int maxSp, int currSp)
{
CurrentSpeed = currSp;
MaxSpeed = maxSp;
PetName = name;
}
// declare a delegate type
public delegate void CarEngineHandler(string message);
// Create a new delegate object*
private CarEngineHandler listOfHandlers;
// associate with method*
public void RegisterWithCarEngine(CarEngineHandler methodToCall)
{
listOfHandlers += methodToCall;
}
public void Accelerate(int delta)
{
if(CurrentSpeed >= MaxSpeed)
{
if(listOfHandlers != null)
{
listOfHandlers(“Error: Current speed is greater than the max speed!”);
}
} else {
CurrentSpeed += delta;
Console.WriteLine(“Current speed is : {0}”, CurrentSpeed);
if (MaxSpeed - CurrentSpeed <= 10 && listOfHandlers != null)
{
listOfHandlers(“Warning: Current speed is closing to the max speed!”);
}
}
}
}
class Program
{
public static void OnCarEngineEvent(string message)
{
Console.WriteLine(“=> {0}”, message);
}
static void Main(string[] args)
{
Car c = new Car(“Test”, 100, 10);
c.RegisterWithCarEngine(new Car.CarEngineHandler(OnCarEngineEvent));
for (int i = 0; i < 6; ++i)
{
c.Accelerate(20);
}
Console.ReadLine();
}
}
}“`
输出:

Current speed is : 30
Current speed is : 50
Current speed is : 70
Current speed is : 90
=> Warning: Current speed is closing to the max speed!
Current speed is : 110
=> Warning: Current speed is closing to the max speed!
=> Error: Current speed is greater than the max speed!
3. 委托的基础知识

(1) 委托所实现的功能与C/C++中的函数指针十分相似。Using a delegate allows the programmer to encapsulate a reference to a method inside a delegate object. 从实际使用的角度来看,委托的作用是将方法作为方法的参数。
(2) 与C/C++中函数指针的不同:函数指针只能指向静态函数,而委托既可以引用静态函数,又可以引用非静态成员函数;与函数指针相比,委托是面向对象、类型安全、可靠的受控(managed)对象。
(3) 委托的声明。Delegates run under the caller’s security permissions, not the declarer’s permissions.
(4) 委托的实例化。委托可以像类一样直接定义对象,也可以通过关键字new创建新的对象。
(5) 将方法绑定到委托。可以直接采用赋值符号=,也可以在new的时候将方法名作为创建委托对象的参数,但与类不同的是a)委托对象一旦创建就要绑定方法,不能创建空的委托对象,b)委托可以通过+=来绑定多个方法,还可以通过-=来解除对某个方法的绑定。
(6) 对于绑定了多个方法的委托,在调用时会依次调用所有绑定的方法。一旦出现异常会终止方法列表中后面的方法的调用。

参考文献:

  1. 《精通C#》
  2. 张子阳的《C# 中的委托和事件》
  3. C# 中的委托和事件
  4. Delegates Tutorial
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值