C#基础-委托与事件

1、委托

委托(delegate)是一种数据结构,提供类似C++中函数指针的功能。不同的是,C++的函数指针只能够指向静态的方法,而委托除了可以指向静态的方法之外,还可以指向对象实例的方法。其实,最大的差别在于delegate是完全的面向对象且实用安全的类型。另外,delegate允许编程人员可以在执行时期传入方法的名称,动态地决定欲调用的方法。

委托的最大特点是,它不知道或不关心自己引用的对象的类。任何对象中的方法都可以通过委托动态地调用,只是方法的参数类型和返回类型必须与委托的参数类型和返回类型相匹配。这使得委托完全适合“匿名”调用。
委托主要用在两个方面:其一是CallBack(回掉)机制;其二是事件处理。
建立和使用delegate类型可按照下面的步骤进行,其后的例子给出了完整的程序代码。
1)声明样板。
首先要声明一个delegate类型:

public delegate string MyDelegate(string name);
代码中先定义一个delegate类型,名为MyDelegate,它包含一个string类型的传入参数name,一个string类型的返回值。当C#编译器编译这行代码时,会生成一个新的类,该类继承自System.Delegate类,而类的名称为MyDelegate。
从语法形式上看,定义一个委托非常类似于定义一个方法。即:
访问修饰符 delegate 类型 委托名(参数序列)
但是,方法有方法体,而委托没有方法体。因为它执行的方法是在使用委托时动态指定的。
2)定义准备调用的方法。
由于这个方法是通过delegate调用的,因此,此方法的参数类型、个数以及参数的顺序都必须和delegate类型相同。
下面的程序中定义了两个方法:FunctionA与FunctionB。这两个方法的参数和MyDelegate的类型一样,有一个string类型的传入参数,有一个string类型的返回值。
public static string FunctionA(string name)
  {
    ......
  }
public static string FunctionB(string name)
  {
    ......
  }
3)定义delegate类型的处理函数,并在此函数中通过delegate类型调用定义的方法。
在下面的例子中,处理函数的功能比较简单,仅仅输出一个字符串,字符串中包含通过MyDelegate类型调用的方法得到的输出内容。
public static void MethodA(MyDelegate Me)
{
  Console.WriteLine(Me("张三"));
}
由于MyDelegate类型的定义中有一个string类型的传入参数,所以使用时也必须传入一个字符串,即:Me("张三")。
因此,如果Me指向的FunctionA,则会执行FunctionA内的程序代码,如果Me指向的是FunctionB,则会执行FunctionB内的程序代码。
4)创建实例,传入准备调用的方法名。
由于声明了一个delegate类型在编译时期会被转换成一个继承自System.Delegate的类,因此要使用delegate类型时,必须先建立delegate的实例,并把它关联到一个方法。
MyDelegate a = new MyDelegate(FunctionA);
本行代码的含义是:a指向FunctionA方法的程序代码段。
建立delegate类型的实例后,就可以直接调用处理函数,并传入delegate类型的变量。
例如:
MethodA(a);
由于a指向FunctionA的引用,所以实际执行的FunctionA中的程序代码。
【例】使用delegate

using System;

namespace test
{
    //第一步:声明委托
    public delegate string MyDelegate(string name);
    public class test
    {
        //第二步:定义被调用的方法
        public static string FunctionA(string name)
        {
            return "A say Hello to " + name;
        }
        public static string FunctionB(string name)
        {
            return "B say hello to " + name;
        }
        //第三步:定义delegate类型的处理函数,并在此函数中通过delegate类型调用第二步定义的方法
        public static void MethodA(MyDelegate Me)
        {
            Console.WriteLine(Me("张三"));
        }
        public static void Main()
        {
            //第四步:创建实例,传入准备调用的方法名
            MyDelegate a = new MyDelegate(FunctionA);
            MyDelegate b = new MyDelegate(FunctionB);
            MethodA(a);
            MethodA(b);
            Console.ReadLine();
        }
    }
}
程序输出结果为:

A say Hello to 张三

B say Hello to 张三

2、事件

“事件”是指当对象发生某些事情时,向其他对象提供通知的一种方法。在C#中,事件是通过delegate实现的。
事件有两个角色:一个是事件发送方,一个是事件接收方。事件发送方是指触发事件的对象,事件接收方是指注册想在某种事件发生时被通知的对象。
举例说明,目前有很多期刊杂志,而你可以只订购感兴趣的杂志。一旦你订购了指定的杂志,当这些杂志发行时,就会将这些杂志送到你指定的地方(单位或者代办处)。此时发行杂志的出版社就称为事件发送方,你就是事件接收方。而每当杂志发行时,就触发一个发行事件。但出版社并不是直接将杂志送给你,而是委托邮局做这件事,或者说邮局是出版社的委托。
事件发送方其实就是一个对象,这个对象会自行维护本身的状态信息。当本身的状态信息变动时,便触发一个事件,并通知所有的事件接收方。
事件接收方可以注册感兴趣的事件。一般提供一个事件处理程序,以便在事件发送方触发一个事件后,会自动执行这段程序代码的内容。
事件最常见的用途是用于图形用户界面。一般情况下,每个控件都有一些事件,当用户对控件进行某些操作(如单击某个按钮)时,系统就会将相关信息告诉这些事件。

2.1声明一个事件

事件是通过delegate机制实现的,因此若要声明一个事件,首先要声明一个delegate类型,然后使用event保留字声明一个事件,并将事件名称和delegate类型关联在一起。

【例】事件处理

using System;

namespace MyCollections
{
    using System.Collections;
    public delegate void ChangeEventHandler(object sender,EventArgs e);
    public class ListWithChangeEvent:ArrayList
    {
        public event ChangeEventHandler Changed;
        protected virtual void OnChanged(EventArgs e)
        {
            if (Changed != null)
            {
                Changed(this, e);
            }
        }
        public override int Add(object value)
        {
            int i = base.Add(value);
            OnChanged(EventArgs.Empty);
            return i;
        }
        public override void Clear()
        {
            base.Clear();
            OnChanged(EventArgs.Empty);
        }
        public override object this[int index]
        {
            set
            {
                base[index] = value;
                OnChanged(EventArgs.Empty);
            }
        }
    }
}
using System;

namespace TestEvents
{
    using MyCollections;
    class EventListener
    {
        private ListWithChangeEvent List;
        public EventListener(ListWithChangeEvent list)
        {
            List = list;
            List.Changed += new ChangeEventHandler(ListChanged);
        }
        private void ListChanged(object sender, EventArgs e)
        {
            Console.WriteLine("开始让我处理了!");
        }
        public void Detach()
        {
            List.Changed -= new ChangeEventHandler(ListChanged);
            List = null;
        }
    }
    class Test
    {
        public static void Main()
        {
            ListWithChangeEvent list = new ListWithChangeEvent();
            EventListener listener = new EventListener(list);
            list.Add("item 1");
            list.Clear();
            listener.Detach();
            Console.ReadKey();
        }
    }
}

输出结果为:

开始让我处理了!

开始让为处理了!

例子中声明了一个delegate类型,名称为ChangedEventHandler,并带有两个参数,这有点像先指定一个邮局,并告诉邮局出版社是谁(object sender),出版社都有哪些信息(EventArgs e)。

public delegate void ChangedEventHandler(object sender,EventArgs e);

然后声明一个事件,名称为Changed,并指明用ChangedEventHandler作为委托。

public event ChangedEventHandler Changed;

这样做的目的是,当触发Changed事件时,可以通过ChangedEventHandler这个委托进行事件处理。就好像订购杂志时先要指明和哪个邮局(出版社的委托)建立联系,以便邮局接到杂志时(触发事件时)可以将杂志送给你。此处的ChangedEventHandler相当于邮局,Changed相当于订购杂志者。

2.2调用事件

调用事件的代码很简单,它的语法和调用一个方法类似,直接使用事件的名称,并传入事件的参数就可以了。
        protected virtual void OnChanged(EventArgs e)
        {
            if (Changed != null)
            {
                Changed(this, e);
            }
        }
需要注意的是,在该方法中要判断是否有对象将委托与该事件关联,如果没有对象将委托与事件关联,则委托的事件为null。调用事件时先检查是否为null,如果不为null,再调用该事件。
谁来调用这个事件呢?由于本例子的目的是当ListWithChangedEvent的实例list的内容改变时触发事件,所以应该在ListWithChangedEvent的Add方法和Clear方法中调用这个事件。
public class ListWithChangedEvent::ArrayList
{
  ......
  public override int Add(object value)
  {
    int i = base.Add(value);
    OnChanged(EventArgs.Empty);
    return i;
  }
  ......
}
这样,当调用Add方法时,该方法就会调用OnChanged方法,而OnChanged方法中调用了Changed事件,所以也可以说当调用Add方法时调用了Change事件。

2.3声明事件发生时处理的方法

对于接收事件的对象而言,需要编写一个方法,以便在感兴趣的事件出发时,能够执行这个方法内的代码。
下面的例子定义了一个方法ListChanged。需要注意的是,事件处理方法的参数类型和个数必须和delegate类型定义的一致,不过参数的名称可以相同也可以不同,例子中使用了相同的名称:
private void ListChanged(object sender, EventArgs e)
{
  Console.WriteLine("开始让我处理了!");
}
这个方法有什么用呢?开始说过,当邮局接收到杂志后,它要进行处理,比如将杂志送到订户指定的单位等。也就是说,这个方法就是告诉邮局如何处理的。

2.4将事件和委托关联在一起

邮局已经知道如何处理了,可还没有指明是哪个邮局作这个处理,还必须指明哪个邮局、如何处理。也就是说,需要将事件和委托关联在一起。
使用+=将事件和委托关联在一起,使用-=接触关联。一旦建立起这种关联,委托就可以调用事件发生时处理的方法。
例子中的事件Changed和委托ChangedEventHandler关联在一起,并指明当事件发生时,调用参数中指明的方法ListChanged。
List.Changed += new ChangedEventHandler(ListChanged);
这样,委托ChangedEventHandler就知道,当对象的状态改变(调用Add方法或者Chear方法)时,就调用Changed事件,调用CHanged事件时就执行ListChanged这个方法。
在.net开发环境中,当控件的状态改变时,比如用户单击了按钮控件,控件就会发出一个事件,编程者只需要声明触发事件时执行的方法就可以了。例子只是为了让读者理解事件的工作原理,以及进行复杂编程时自定义事件的方法。实际上,一般情况下利用.net提供的事件进行编程就已经足够了,而且使用也非常简单。但是有时还确实需要利用事件实现一些特殊功能。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

广哥教打杂

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值