C#学习总结 事件(一)深入多播委托

  好久没写文章了,之前说要总结一下事件这个概念,今天趁着工作闲暇,谈谈我对C#事件事件的理解,如果有什么不对的地方,望各位大神指点。

  真正的理解事件,还是要很好的理解委托,不然你永远不会明白很多教科书上的一句话:事件是一个特殊的委托,今天我们就来探寻一下:为什么说事件是一个特殊的委托,之前我谈论了委托的一些相关基础知识,其实还有一些关于委托更深层次的东西没有说,那么在今天谈事件这个知识之前还是继续从委托说起:

  不知道有没有人了解C语言的函数指针这个概念,使用函数指针来创建回调函数,使用回调可以把函数回调给程序中的另一个函数。但函数指针只是简单地把地址指向另一个函数,并不能传递其他额外信息。而在.NET中,在大部分时间里都没有指针的身影,因为指针被封闭在内部函数当中。可是回调函数却依然存在,它是以委托的方式来完成的。委托可以被视为一个更高级的指针,它不仅仅能把地址指向另一个函数,而且还能传递参数,返回值等多个信息。系统还为委托对象自动生成了同步、异步的调用方式,开发人员使用BeginInvoke、EndInvoke 方法就可以抛开 Thread 而直接使用多线程调用 。其实这才是C#委托的本质由来,之前总结了那么多关于委托的概念,我始终没有从C语言的角度去解析委托,是因为一开始就吧上面的概念展示给初学者,其实是很不利于我们更好的,更深刻的去学习委托这个概念的。对于一个.net程序猿来说,适当的学习C语言是更有利于我们去理解C#高级的语法的。废话不多说,我们定义了一个名为MyDel委托,通过反射来遍历委托内部的属性和方法:

            PropertyInfo[] pro = typeof(MyDel).GetProperties();
            MethodInfo[] med = typeof(MyDel).GetMethods();
            //MyDel a = new MyDel((name) => { return ""; });
            Console.WriteLine("--------------------属性");
            foreach (var item in pro)
            {
                Console.WriteLine(item.Name);
            }
            Console.WriteLine("--------------------方法");
            foreach (var item in med)
            {
                Console.WriteLine(item.Name);
            }            

得到如下结果:

我们还可以通过反射得到更多关于委托的元数据,这里就不一一列出了,所以我们可以看出委托其实也是一个类,它的内部也有对应的成员(属性,方法等),而且它派生自System.MulticastDelegate类。这是我从资料上截取的关于委托内部成员的介绍:

MulticastDelegate具有两个常用属性:Method、Target。其中Method 用于获取委托所表示的方法Target 用于获取当前调用的类实例。

MulticastDelegate有以下几个常用方法:

方法名称 说明
 Clone   创建委托的浅表副本。
 GetInvocationList   按照调用顺序返回此多路广播委托的调用列表。
 GetMethodImpl   返回由当前的 MulticastDelegate 表示的静态方法。
 GetObjectData   用序列化该实例所需的所有数据填充 SerializationInfo 对象。
 MemberwiseClone   创建当前 Object 的浅表副本。
 RemoveImpl   调用列表中移除与指定委托相等的元素

  
多播委托:委托是可以同时赋值多个方法的,即委托对象可以绑定多个方法。当输入参数后,每个方法会按顺序进行迭代处理,并返回最后一个方法的计算结果。,如下:

    public delegate int MyDel(int name);
    class Program
    {
        static int Add1(int a) {
            int b = 10 + a;
            Console.WriteLine(b);
            return b;
        }
        static int Add2(int a)
        {
            int b = 10 - a;
            Console.WriteLine(b);
            return b;
        }
        static void Main(string[] args)
        {
            MyDel add = new MyDel(Add1);
            add += new MyDel(Add2);
            Console.WriteLine(add(10));
        }
    }

很显然多播委托有很大的缺陷,无论绑定多少个方法,该委托最终都只会返回最后一个方法的结果,不能实现它使用一对多的方式,可以让多个观察者同时关注同一个事物,并作出不同的响应。因此我们要对上面的代码做如下更改:

    public delegate int Handler(int a);
    public class Manager
    {
        private Handler Handler;

        //加入观察者
        public void Attach(Handler Handler1)
        {
            Handler += Handler1;
        }

        //删除观察者
        public void Detach(Handler Handler1)
        {
            Handler -= Handler1;
        }

        //通过GetInvodationList方法获取多路广播委托列表,如果观察者数量大于0即执行方法
        public void Execute(int basicNum)
        {
            if (Handler != null)
                if (Handler.GetInvocationList().Count() != 0)
                    Handler(basicNum);
        }
    }
    class Program
    {
        static int Cul1(int a)
        {
            int b = 10 + a;
            Console.WriteLine(b);
            return b;
        }
        static int Cul2(int a)
        {
            int b = 10 - a;
            Console.WriteLine(b);
            return b;
        }
        static void Main(string[] args)
        {
            Manager manager = new Manager();
            //加入Add1观察者
            Handler HandlerA = new Handler(Cul1);
            manager.Attach(HandlerA);

            //加入Add2观察者
            Handler HandlerB = new Handler(Cul2);
            manager.Attach(HandlerB);

            //同时加入10,分别进行计算
            manager.Execute(10);
            Console.ReadKey();
        }
    }

上面会在控制台返回20和0,上面的代码其写法的非常巧妙,而且也使用了设计模式中的观察者模式,如果你仔细研究了代码,你可能有个疑惑,我们之前为委托绑定多个方法时就只能返回最后一个方法的执行结果,为什么在此处就可以对应不同的方法返回不同的结果呢?我们在此处的委托绑定方法的方式与之前委托的绑定方式是有区别的,我们把上面观察者模式的代码简化,结果还是输出20和0:

    public delegate int Handler(int a);
    class Program
    {
        static int Cul1(int a)
        {
            int b = 10 + a;
            Console.WriteLine(b);
            return b;
        }
        static int Cul2(int a)
        {
            int b = 10 - a;
            Console.WriteLine(b);
            return b;
        }
        static void Main(string[] args)
        {
            //Manager manager = new Manager();
            //加入Add1观察者
            Handler HandlerA = new Handler(Cul1);

            //加入Add2观察者
            Handler HandlerB = new Handler(Cul2);

            Handler Handler;
            Handler = HandlerA;
            Handler += HandlerB;
            Handler(10);
            Console.ReadKey();
        }
    }

对比我们最开始写的为委托绑定方法,我们是将两个方法绑定到同一个委托之中。而在此处,我们是将两个不同方法的分别赋值给了HandlerA和HandlerB两个委托,然后再将HandlerA和HandlerB依次绑定给了Handler,这样写与之前有什么区别呢?我们可以从指针的角度取理解它,最开始的第一种绑定方式将两个方法绑定到同一个委托之中:两个方法的指针是指向同一个委托(你可以理解为两个方法指向同一片内存空间),而无论绑定多少个方法,一个委托最终都只会返回最后一个方法的结果。而之后的第二种绑定方式在第一部的基础上加了一部,先将两个方法分别赋值给了HandlerA和HandlerB两个不同的委托,所以两个方法指向不同的委托(因为不是赋值给相同的委托,所以可以理解为两个方法指向不同的内存空间),然后再将绑定了两个不同的委托依次绑定给Handler委托,这样再次绑定的好处是可以对不同的观察者加以区分,因而可以对不同方法返回不同的结果。理解这一点非常重要,对理解之后的事件有很大的帮助。下面我们再将之前观察者模式的Handler定义到Main方法之外:

    public delegate int Handler(int a);
    class Program
    {
        public static Handler EventHandler;
        static void Main(string[] args)
        {
            //Manager manager = new Manager();
            //加入Add1观察者
            Handler HandlerA = new Handler(Cul1);

            //加入Add2观察者
            Handler HandlerB = new Handler(Cul2);

            EventHandler += HandlerA;
            EventHandler += HandlerB;
            EventHandler(10);
            Console.ReadKey();
        }
    }

然后把Handler委托改成事件

    public delegate int Handler(int a);
    class Program
    {
        public static event Handler EventHandler;
        static void Main(string[] args)
        {
            //Manager manager = new Manager();
            //加入Add1观察者
            Handler HandlerA = new Handler(Cul1);

            //加入Add2观察者
            Handler HandlerB = new Handler(Cul2);

            EventHandler+= HandlerA;
            EventHandler+= HandlerB;
            EventHandler(10);
            Console.ReadKey();
        }
    }

哈哈,仔细对比两段代码,你会发现下面除了代码在申明时多了一个event关键字之外,其他地方竟然完全一样,所以写到这里你就会明白大多数书上为什么说写到的事件一个特殊委托了吧!其实你可以理解为事件就一个委托的实例,它的作用在于再次绑定委托,以区分同一委托对应所绑定的不同的方法,实现对绑定了同一事件的不同的观察者方法做出不同的处理。今天就到此为止吧,下次继续来讲解委托与事件更多的作用与关系。

时间: 2024-10-13 02:31:26

C#学习总结 事件(一)深入多播委托的相关文章

委托、Lambda表达式、事件系列04,委托链是怎样形成的, 多播委托

在"委托.Lambda表达式.事件系列01,委托是什么,委托的基本用法,委托的Method和Target属性"中,反编译委托,发现委托都是多播委托. 既然委托是多播委托,我们可以通过"+="把多个方法赋给委托变量,这样就形成了一个委托链, 它是怎样形成的?来看下面的例子: namespace ConsoleApplication3 { internal delegate void MySayDel(string msg); class Program { stati

多播委托和事件

事件的创造规则是类型必须是一个委托的类型,也就是说是委托实例化了事件 事例代码如下: 这是两个以后要被委托的方法: public class EventManager { //第一个方法 public void Add(int i,int j) { int res = i + j; MessageBox.Show(res.ToString()); } //第二个方法 public void SayHello(int i,int j) { MessageBox.Show("hello word&q

从多播委托到事件

一.多播委托 前文提到的委托只是在一个委托类型中存储了一个方法(函数),实际上一个委托变量可以同时绑定多个方法,这些委托形成了一个委托链,每一个委托(实际上是方法)都顺序指向下一个委托,这个委托链就是多播委托. 每一个绑定的方法就像是订阅者一样,等着发布者的消息,而触发委托变量的那个就像是发布者,将出发的信号传给所有的订阅者. 1.订阅者 考虑一个温度控制器的例子,这个控制器拥有两个调温器,一个加热器,当温度低于指定的值时,启动,一个冷却器,当温度高于指定的温度时,启动.二者的类设计如下: 1

Net中委托之二多播委托

本篇主要讲解多播委托 1.多播委托的实例 public class MyDelegate { private delegate int NoParameterWithReturn();//1.声明委托 public static void Show() { NoParameterWithReturn method = new NoParameterWithReturn(ShowSomething);//2.委托的实例化 //多播委托 method += ShowSomething;//按顺序添加

委托和多播委托

刚刚学习了委托的内容,先来说一下自己对委托的理解,权当是复习吧: 委托可以定义在类里面,也可以定义在类外面,如果定义在类里面就只能被这个类调用,不论声明它是public 还是private.如果定义在类外面就能被所有类调用.如果程序是多线程的,委托方法希望能在UI层调用,那么可以在命名空间下定义. //委托的声明 delegate关键字,这个委托只能代表返回值是空.参数是一个string类型的一个方法public delegate void GreetingDelegate(string nam

委托、Lambda表达式、事件系列05,Action委托与闭包

来看使用Action委托的一个实例: static void Main(string[] args) { int i = 0; Action a = () => i++; a(); a(); Console.WriteLine(i); } 结果是期望能的2.但令人好奇的是:栈上的变量i是如何传递给Action委托的? 反编译进行查看,首先看Main方法对应的IL代码: 再看c_DisplayClass1的IL代码: 从中可以看出:→在托管堆上创建了一个名为c_DisplayClass1的实例→把

Android学习按键事件监听与Command模式

Android学习按键事件监听与Command模式 - Dufresne - 博客园 ? 一 Command模式 意图: 将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化: 对请求排队或记录请求日志,以及支持可撤销的操作. 将请求被封装成一个对象,当向某对象提交请求时,使我们可以不用去知道被具体的请求的操作或者请求的接收者, 实现了动作的请求者对象和动作的执行者对象之间的解耦合. 适用性: 使用Command模式代替callback形式的回调应用: 在不同的时刻指定.排列和执行请

C#基础学习之事件的理解和应用

事件的使用和委托类似,也是分四步来实现:声明委托.定义事件.注册事件.调用事件 我们先看一下事件的定义 //定义委托 public delegate void PublishEventHandler(string msg); //定义事件 public event PublishEventHandler OnPublish; 事件注册和取消注册是用+=和-=方法名来完成的,如 publisher.OnPublish += method; 最后就可以调用事件了 OnPublish(msg); 我们

委托应用及泛型委托和多播委托

一.委托一般作为方法的参数或者返回值,或者使用多播委托(注册多个方法,可以全部触发) 1.示例:根据对于字符串不同的处理方法逻辑 private delegate void PrintString(string str); static void PrintStr( PrintString print,string str ) { print(str); } static void Method1(string str) { Console.WriteLine(str); } static vo