• 面试都爱问的委托和事件(纠正)


    面试都爱问的委托和事件(纠正)

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

    一、原文

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

    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

  • 相关阅读:
    Json对象与Json字符串互转(4种转换方式)
    Web.config配置文件详解
    jQuery BlockUI Plugin Demo 6(Options)
    jQuery BlockUI Plugin Demo 5(Simple Modal Dialog Example)
    jQuery BlockUI Plugin Demo 4(Element Blocking Examples)
    jQuery BlockUI Plugin Demo 3(Page Blocking Examples)
    jQuery BlockUI Plugin Demo 2
    <configSections> 位置引起的错误
    关于jQuery的cookies插件2.2.0版设置过期时间的说明
    jQuery插件—获取URL参数
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3180169.html
Copyright © 2020-2023  润新知