• C#基础原理拾遗——面试都爱问的委托和事件(纠正)


          这篇博客是我昨天写的,文中的观点有些问题,后经过网友留言和个人学习发现错误,原文还是保留,更改补在后面,不怕贻笑大方,唯恐误人子弟。不知道还能不能放在首页,让被误导的同学再被反误导一次。

    一、原文

          几乎所有的面试题都会问:事件是委托吗,说说委托和事件的联系和区别?每次答这个题都很蛋疼,因为把它们的关系说简单了就描述不准确,想说清楚就不是一两句话的事了。我通常在回答中加这么一句:委托与事件的关系好比字段与属性的关系。很多人理解它们的关系时也做这样的类比,虽然简单一句话概括了它们的关系,但总不能让我感到满意。

    1、委托与事件到底什么关系?

          当我们谈委托与事件的关系时,是说委托这种类型和事件这种类型的关系呢,还是具体的委托对象和事件对象之间的关系?我以为是前者。那么委托和事件是两种类型,而字段和属性是具体的对象,虽然都是封装,我觉得两者之间还是有区别的,事件对委托的封装是在类级别的、抽象层次、稳定的封装,而属性对字段的封装是在对象级别的、具体的、可自定义的封装(个人理解,后来证明有偏差)由此造成的最直观的区别就是,在发布者类中使用事件时,不需要提供对应的委托对象;而在类中使用属性时,一般要提供对应的字段让属性来进行封装。

          Reflector查看类之间的继承关系如下:

    捕获

    MSDN中有这么一句:“事件是特殊类型的多路广播委托,仅可从声明它们的类或结构(发行者类)中调用。”如此看来事件与委托的关系应该是继承关系,在继承的过程中在EventHandler类中进行的封装。

          以上是从理论的角度分析了事件和委托的关系,下面通过代码加强认识。

    2、委托模拟事件

         实现一个非常简单的功能:控制台输入一个数,如果输入100的话就显示”Game Over”。用事件和委托模拟的事件分别实现该功能。

    class Program
        {
            static void Main(string[] args)
            {
                //事件实现
                Game1 game = new Game1();
                game.GameOverEvent += game_GameOver;
    
                ////委托实现
                //Game2 game = new Game2();
                //game.AddMethod(game_GameOver);
    
                while (true)
                {
                    Console.WriteLine("输入:");
    
                    game.Scroe = Convert.ToInt32(Console.ReadLine());
                }
            }
    
            static void game_GameOver(object sender, EventArgs e)
            {
                Console.WriteLine("Game Over");
            }
    
            static void game_GameOver()
            {
                Console.WriteLine("Game Over");
            }
        }
    
        //事件实现
        class Game1
        {
            //event 关键字用于在发行者类中声明事件。
            public event EventHandler GameOverEvent;
    
            private int scroe;
    
            public int Scroe
            {
                get { return scroe; }
                set
                {
                    this.scroe = value;
                    if (this.GameOverEvent != null)
                    {
                        if (value == 100)
                        {
                            this.GameOverEvent(this, new EventArgs());//触发事件
                        }
                    }
                }
            }
        }
    
        //委托实现
        class Game2
        {
            public delegate void OverDelegate();
    
            //将委托声明为private,防止订阅者直接调用,使用new等功能
            private OverDelegate GameOverDelegate;
    
            //AddMethod和RemoveMethod模拟事件的+=和-=赋值
            public void AddMethod(OverDelegate over)
            {
                this.GameOverDelegate += over;
            }
    
            public void RemoveMethod(OverDelegate over)
            {
                this.GameOverDelegate -= over;
            }
    
            private int scroe;
    
            public int Scroe
            {
                get { return scroe; }
                set
                {
                    this.scroe = value; 
                    if (this.GameOverDelegate != null)
                    {                    
                        if (value == 100)
                        {
                            this.GameOverDelegate();//触发委托
                           }
                    }
                }
            }
        }

          以上Game2类中,委托对事件的模拟即是在对象级别做的封装,AddMethod和RemoveMethod方法是在Game2类中实现的,而不是在OverDelegate委托中实现的,因此事件的模拟依赖了OverDelegate和Game2两个类。而Game1中EventHandler类本身就封装了委托,限制了其在外部(订阅者)的实例化,这种功能的实现没有依赖于Game1。所以事件的这种内部封装机制减少了依赖,符合松耦合要求。

          以上是我个人的一点理解,有失偏颇之处还望批评指正。

          然而EventHandler内部是怎么封装的呢?我的思路尚不清晰,Reflector查看也没看出个所以然,我自己会认真学习探索,在此也向各位请教,希望大家能告诉我答案或提供一些思路。为谢!

    二、改正

    3、其实不是EventHandler一人的功劳

          在上文中我以为事件对委托的封装是EventHandler类一人的功劳,其实没有深入理解的话很容易这么想,因为在声明事件的时候并没有声明对应的委托,直观上就感觉这个封装是发生在EventHandler类内部的。看了 @小AI 给我推荐的http://www.cnblogs.com/JimmyZhang/archive/2007/09/23/903360.html一文,发现问题所在。

          在Reflector中反编译上面的例子,Game1类的结构如下:

    捕获

    Game1在此就是发布者类,对事件GameOverEvent的封装还是在发布者类中完成的,并不是在EventHandler类中就已经完成。这种封装模式和属性对字段的封装模式是完全一样的,只不过这个封装过程是由于有了event关键字在编译阶段由编译器自动完成的,在发布者类中将EventHandler类型的GameOverEvent委托设为私有,并加上Add和Remove方法,和上面用委托模拟的事件是一样的,也就是事件的本质就是在小标题2中用委托模拟的事件。这么说来,事件对委托的封装是可以类比为属性对字段的封装的,如果说有区别,那就是前者是编译阶段完成的、不可自定义的封装,后者是coding阶段实现的、可自定义的封装,但它们都是在对象级别完成的。

          虽然这次出了错,但并没有使我灰心,相反,如果我不把自己的想法拿出来和大家交流,可能问题永远得不到解决。唯一的弊端就是大胆的写自己的想法,如果是错误的话,很可能误导比我还菜的同学,我能做的就是尽量多方求证,并且出错后及时改正。也勉励和我一样的菜鸟多写博客,别怕暴漏错误,大家对于大牛中规中矩的blog已经看的boring了,小菜错误的想法里可能偶尔就有着不被规则约束的创新。hehe

  • 相关阅读:
    RS232串口通信详解
    VS2010 常用的快捷键
    Qt .pro文件详解
    Qt 编译出现 error LNK2019: 无法解析的外部符号
    LabVIEW部分视觉函数中文解说
    NI Vision 介绍
    LabVIEW的优点
    VisionPro和Halcon的详细对比
    康耐视软件VisionPro-max-u与VisionPro-plus-u的区别
    TensorFlow 介绍
  • 原文地址:https://www.cnblogs.com/jacktag/p/3175200.html
Copyright © 2020-2023  润新知