C#委托与事件

Delegate


  C# 中的委托(Delegate)类似于 C 或 C++ 中函数的指针。委托 是存有对某个方法的引用的一种引用类型变量。引用可在运行时被改变。委托特别用于实现事件和回调方法。所有的委托(Delegate)都派生自 System.Delegate 类。

声明委托

  声明委托的语法如下:

delegate return-type delegate-name (parameter-list);

  其中:return-type是返回类型,delegate-name是委托名字,parameter-list是参数列表。

public delegate int MyDelegate (string s); //声明一个全局的委托,参数为string类型

方法名作为参数


  委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递,这种将方法动态地赋给参数的做法,可以避免在程序中大量使用If-else(switch)语句,同时使得程序具有更好的可扩展性。

using System;
using System.Collections.Generic;
using System.Text;

namespace Delegate
{
    //定义委托,它定义了可以代表的方法的类型
    public delegate void GreetingDelegate(string name);
    class Program
    {
        private static void EnglishGreeting(string name)
        {
            Console.WriteLine("Morning, " + name);
        }

        private static void ChineseGreeting(string name)
        {
            Console.WriteLine("Zao Shang Hao, " + name);
        }

        //注意此方法,它接受一个GreetingDelegate类型的方法作为参数
        private static void GreetPeople(string name, GreetingDelegate MakeGreeting)
        {
            MakeGreeting(name);
        }

        static void Main(string[] args)
        {
            GreetPeople("Jimmy Zhang", EnglishGreeting); //方法名作为参数传递
            GreetPeople("Zhang Ming", ChineseGreeting);
        }
    }
}

实例化委托


  一旦声明了委托类型,委托对象必须使用 new 关键字来创建,且与一个特定的方法有关。当创建委托时,传递到 new 语句的参数就像方法调用一样书写,但是不带有参数。例如:

public delegate void printString(string s);
//...
printString ps1 = new printString(WriteToScreen);//实例化委托WriteToScreen方法
printString ps2 = new printString(WriteToFile);

实例如下:

using System;
namespace DelegateApp
{
    delegate int NumberChanger(int n);
    class TestDelegate
    {
        static int num = 10;
        public static int AddNum(int p)
        {
            num += p;
            return num;
        }
        public static int MultNum(int q)
        {
            num *= q;
            return num;
        }
        public static int getNum()
        {
            return num;
        }
        static void Main(string[] args)
        {
            // 创建委托实例
            NumberChanger nc1 = new NumberChanger(AddNum);
            NumberChanger nc2 = new NumberChanger(MultNum);
            // 使用委托对象调用方法
            nc1(25);
            Console.WriteLine("Value of Num: {0}", getNum());// 35
            nc2(5);
            Console.WriteLine("Value of Num: {0}", getNum()); //175
            Console.ReadKey();
        }
    }
}

将方法绑定到委托


  可以将多个方法赋给同一个委托,或者叫将多个方法绑定到同一个委托,当调用这个委托的时候,将依次调用其所绑定的方法。委托对象可使用 "+=" 运算符进行合并。只有相同类型的委托可被合并。"-=" 运算符可用于从合并的委托中移除组件委托。

using System;
namespace DelegateAppl
{
    delegate int NumberChanger(int n);
    class TestDelegate
    {
        static int num = 10;
        public static int AddNum(int p)
        {
            num += p;
            return num;
        }

        public static int MultNum(int q)
        {
            num *= q;
            return num;
        }
        public static int getNum()
        {
            return num;
        }

        static void Main(string[] args)
        {
            // 创建委托实例
            NumberChanger nc;
            NumberChanger nc1 = new NumberChanger(AddNum);
            NumberChanger nc2 = new NumberChanger(MultNum);
            nc = nc1;
            nc += nc2; //合并委托
            nc(5);
            Console.WriteLine("Value of Num: {0}", getNum()); //75
            nc -= nc1;
            nc(5);
            Console.WriteLine("Value of Num: {0}", getNum()); //375
        }
    }
}

事件


  事件(Event) 基本上说是一个用户操作,如按键、点击、鼠标移动等等,或者是一些出现,如系统生成的通知。应用程序需要在事件发生时响应事件。例如,中断。事件是用于进程间通信。

  事件在类中声明且生成,且通过使用同一个类或其他类中的委托与事件处理程序关联。包含事件的类用于发布事件。这被称为 发布器(publisher) 类。其他接受该事件的类被称为 订阅器(subscriber) 类。事件使用 发布-订阅(publisher-subscriber) 模型。

发布器(publisher) 是一个包含事件和委托定义的对象。事件和委托之间的联系也定义在这个对象中。发布器(publisher)类的对象调用这个事件,并通知其他的对象。

订阅器(subscriber) 是一个接受事件并提供事件处理程序的对象。在发布器(publisher)类中的委托调用订阅器(subscriber)类中的方法(事件处理程序)。

事件声明


  事件和方法一样具有签名,签名包括名称和参数列表。事件的签名通过委托类型来定义,然后向类中添加事件需要使用 event 关键字,并提供委托类型和事件名称。例如:

public delegate void TestEventDelegate(object sender, System.EventArgs e);
public class EventSource
{
    public event TestEventDelegate TestEvent; //基于上面的委托
    private void RaiseTestEvent() { /* ... */ }
}

  事件可标记为 public、private、protected、internal 或 protected internal。这些访问修饰符定义类的用户访问事件的方式。  

自定义事件


  C#中的事件处理实际上是一种具有特殊签名的delegate,象下面这个样子:

public delegate void MyEventHandler(object sender, MyEventArgs e);

  其中的两个参数,sender代表事件发送者,e是事件参数类。MyEventArgs类用来包含与事件相关的数据,所有的事件参数类都必须从System.EventArgs类派生。

  结合delegate的实现,我们可以将自定义事件的实现归结为以下几步:

  1. 定义delegate对象类型,它有两个参数,第一个参数是事件发送者对象,第二个参数是事件参数类对象。
  2. 定义事件参数类,此类应当从System.EventArgs类派生。如果事件不带参数,这一步可以省略。
  3. 定义"事件处理方法,它应当与delegate对象具有相同的参数和返回值类型"。
  4. 用event关键字定义事件对象,它同时也是一个delegate对象。
  5. 用+=操作符添加事件到事件队列中(-=操作符能够将事件从队列中删除)。
  6. 在需要触发事件的地方用调用delegate的方式写事件触发方法。一般来说,此方法应为protected访问限制,既不能以public方式调用,但可以被子类继承。名字是OnEventName。
  7. 在适当的地方调用事件触发方法触发事件。

  以下代码的功能是输入'a'触发事件。

using System;
public class EventTest
{
    // 步骤1,定义delegate对象
    public delegate void MyEventHandler(object sender, System.EventArgs e);
    // 步骤2(定义事件参数类)省略
    public class MyEventCls
    {
        // 步骤3,定义事件处理方法,它与delegate对象具有相同的参数和返回值类型
        public void MyEventFunc(object sender, System.EventArgs e)
        {
            Console.WriteLine("My event is ok!");
        }
    }
    // 步骤4,用event关键字定义事件对象
    private event MyEventHandler myevent;
    private MyEventCls myecls;
    public EventTest()
    {
        myecls = new MyEventCls();
        // 步骤5,用+=操作符将事件添加到队列中
        this.myevent += new MyEventHandler(myecls.MyEventFunc);
    }
    // 步骤6,以调用delegate的方式写事件触发函数
    protected void OnMyEvent(System.EventArgs e)
    {
        if (myevent != null)
            myevent(this, e);
    }
    public void RaiseEvent()
    {
        EventArgs e = new EventArgs();
        // 步骤7,触发事件
        OnMyEvent(e);
    }
    public static void Main()
    {
        EventTest et = new EventTest();
        Console.Write("Please input ''a'':");
        string s = "a";
        Console.WriteLine(s);
        if (s == "a")
        {
            et.RaiseEvent();
        }
        else
        {
            Console.WriteLine("Error");
        }
    }
}

匿名方法


  委托是用于引用与其具有相同标签的方法。换句话说,使用委托对象调用可由委托引用的方法。

  匿名方法(Anonymous methods) 提供了一种传递代码块作为委托参数的技术。匿名方法是没有名称只有主体的方法,不需要指定返回类型,它是从方法主体内的 return语句推断的。

  匿名方法是通过使用 delegate 关键字创建委托实例来声明的。例如:

delegate void NumberChanger(int n);
NumberChanger nc = delegate(int x)
{
    Console.WriteLine("Anonymous Method: {0}", x);
};

  委托可以通过匿名方法调用,也可以通过命名方法调用,即,通过向委托对象传递方法参数。如上面的可以调用为:

nc(1);

  下面通过实例来了解一下匿名方法。

using System;
namespace test
{
    delegate void Printer(string s); //声明委托
    class TestClass
    {
        static void Main()
        {
            // 初始化匿名方法
            Printer p = delegate(string j)
            {
                System.Console.WriteLine(j + " anonymous method");
            };
            p("The delegate using the method is called:"); //调用匿名方法
            p = new Printer(TestClass.DoWork); //实例化委托
            p("using the named method is called: ");//调用DoWork方法
        }
        static void DoWork(string k)
        {
            System.Console.WriteLine(k + "dowork");
        }
    }
}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏大内老A

通过实例模拟ASP.NET MVC的Model绑定机制:简单类型+复杂类型

总的来说,针对目标Action方法参数的Model绑定完全由组件ModelBinder来实现,在默认情况下使用的ModelBinder类型为DefaultMod...

2528
来自专栏Create Sun

利用委托与Lambada创建和调用webapi接口

前言   现在项目中用的是webapi,其中有以下问题:       1.接口随着开发的增多逐渐增加相当庞大。     2.接口调用时不好管理。   以上是主要...

3579
来自专栏恰同学骚年

.NET基础拾遗(3)字符串、集合和流

  众所周知,在.NET中String是引用类型,具有不可变性,当一个String对象被修改、插入、连接、截断时,新的String对象就将被分配,这会直接影响到...

961
来自专栏Java大联盟

Spring Data MongoDB:Repository

使用Spring Data可以帮助我们快速构建项目,非常方便,Spring Data在数据持久层已经写好了常用功能,我们只需要定义一个接口去继承Spring D...

2161
来自专栏互联网开发者交流社区

HashTable vs HashMap(三)

953
来自专栏屈定‘s Blog

Java--Java中的四种引用

Java中存在四种引用,StrongReference(强引用) 、SoftReferenc(软引用) 、WeakReferenc(弱引用)、PhantomRe...

1214
来自专栏大内老A

WCF技术剖析之十三:序列化过程中的已知类型(Known Type)

DataContractSerializer承载着所有数据契约对象的序列化和反序列化操作。在上面一篇文章(《数据契约(Data Contract)和数据契约序列...

26810
来自专栏逸鹏说道

C# 温故而知新:Stream篇(—)

目录: 什么是Stream? 什么是字节序列? Stream的构造函数 Stream的重要属性及方法 Stream的示例 Stream异步读写 Stream ...

3519
来自专栏林德熙的博客

C# 很少人知道的科技

本文来告诉大家在C#很少有人会发现的科技。即使是工作了好多年的老司机也不一定会知道,如果觉得我在骗你,那么请看看下面。

1232
来自专栏GreenLeaves

C# Encoding

之前做公司项目的时候,对于C#编码这块总是一知半解,所以打算通过这篇笔记对C#编码(Encoding)进行彻底的扫盲,关于编码和字符集的基础知识,请参考字符集和...

2647

扫码关注云+社区

领取腾讯云代金券