zoukankan      html  css  js  c++  java
  • 庖丁解牛——深入解析委托和事件

    这篇博文我不讲委托和事件的概念,因为大段的文字概念没有任何意义。

    具体想了解,委托和事件的概念可以MSDN查阅。

    我这篇文章的主题思路是委托如何一步步进化成事件:

    何为委托--->委托来实现事件--->用方法对委托的封装--->Event的

    add,remove方法的引入--->标准事件写法--->反编译探究竟。

    用几个例子以及Reflector反编译探究委托和事件的关系。不足之处,还望多多指教... 

    何为委托:

    首先,委托是一种类型,是一种定义了方法签名的类型。

    委托可以理解为函数指针(安全),并且委托约束了方法的签名(由返回类型和参数组成),

    所以实例化委托时,可以将其实例与任何具有相同签名(由返回类型和参数组成)得方法相关联,

    如果按照C语言函数指针理解,即委托实例指向某个方法。

    为什么要用委托:

    举个简单的例子:

    例如,需要判断一个数是为奇数还是偶数?

    可能我们会这样实现:

         static void Main(string[] args)
            {
                Console.WriteLine((4 % 2 == 0) ? "偶数" : "奇数");
                Console.WriteLine((5 % 2 == 0) ? "偶数" : "奇数");
                Console.ReadKey();
            }

    上面例子很简单,但是很不灵活,我们稍加改进:

    static void Main(string[] args)
    {
         Console.WriteLine("请输入一个数字:");
         int i = int.Parse(Console.ReadLine());
         Console.WriteLine((i%2==0)?"偶数":"奇数");
         Console.ReadKey();
    }

    上面这个简单的例子,也是挺有玄机的。对于程序员,我们不关心客户端用户传过来是奇数还是偶数,况且我们也不知道传过来的参数到底是多少,

    我们只关心怎样来实现功能。对于用户来说,他们不必关心底层到底是怎样实现功能的,他们只负责输入数字即可“坐享其成”。这个例子,

    可以理解成一个最简单的解耦。

     

     

      看了上面这个例子,我们再举一个例子来演示委托怎么替做什么:

      

        //委托是一种定义方法签名的类型。 当实例化委托时,可以将其实例与任何具有兼容签名的方法相关联。 可以通过委托实例调用方法。
        //一个委托声明:
        public delegate void ChangeDelegate(int i);
        class Program
        {
            static void Main(string[] args)
            {
                ChangeDelegate c = null;
                if (DateTime.Now.Second%2==0)
                {
                    c = Even;//委托可以理解为函数指针(安全),并且委托类型ChangeDelegate约束了参数类型,所以c可以指向Even方法
                    c(DateTime.Now.Second);
                }
                else
                {
                    //c = Odd;
                    c = new ChangeDelegate(Odd);//标准写法
                    c(DateTime.Now.Second);
                }
                Console.ReadKey();
               
            }
            static void Even(int i)
            {
                Console.WriteLine("{0}是偶数",i);
            }
            static void Odd(int i)
            {
                Console.WriteLine("{0}是奇数",i);
             
            }
        }

    上面代码可以看出,程序员并不知道委托实例到底指向那个函数,但他可以确定,指向的那个方法必定是受ChangeDelegate约束的。

    再看一个例子,我们要对数组进行操作:

     class Program
        {
            static void Main(string[] args)
            {
                int[] arr = { 1,2,3,4,5,6,7,8,9};
                List<int> newList=Filter(arr,IsOdd);
                Console.WriteLine(string.Join("-",newList.ToArray()));
                Console.ReadKey();
    
            }
            /// <summary>
            /// 判断是否为偶数
            /// </summary>
            /// <param name="i"></param>
            /// <returns></returns>
            static bool IsEven(int i)
            { 
                return i%2==0;
            }
            /// <summary>
            /// 判断是否为奇数
            /// </summary>
            /// <param name="i"></param>
            /// <returns></returns>
            static bool IsOdd(int i)
            { 
                return i%2==1;
                
            }
            
            static List<int> Filter(int[] value, FilterDelegate f)
            {
                List<int> list=new List<int> ();
                foreach (var item in value)
                {
                    if (f(item))
                    {
                        list.Add(item);
                    }
                }
                return list;
            }
        }
        delegate bool FilterDelegate(int i);

     

     所以,static List<int> Filter(int[] value, FilterDelegate f),这儿只需要传过来一个方法,

    而我们程序员并不关心这是个什么方法,能替我们做什么,这就是委托的好处。

     

     

     

      事件和委托的联系:学习事件之前,先来用委托来模拟实现事件:

     

        class Program
        {
            static void Main(string[] args)
            {
                Counter c = new Counter();
                c.onCount = Count;//相当于订阅了一个事件
                c.onCount += Count_2;//可以多个人同时监听了
                int j = 0;
                while (j<=100)
                {
                    j++;
                    c.Next();
                }
                Console.ReadKey();
            }
            static void Count(int value)
            {
                Console.WriteLine("Count监听了{0}是偶数", value);
            }
            static void Count_2(int value)
            {
                Console.WriteLine("Count_2监听了{0}是偶数", value);
            }
        }
        class Counter
        {
            public OnCountDelegate onCount;//把委托对象声明为一个字段
            private int i = 0;
            public void Next()
            {
                i++;
                if (i%2==0)
                {
                    if (onCount!=null)
                    {
                        //解耦:解除耦合。
                        //不用关心到底指向谁,调用就行
                        onCount(i);//触发事件,调用onCount指向的函数,相当于把事件通知出去
                    }
                }
            }
        }
        public delegate void OnCountDelegate(int value);
      c.onCount = Count;
      c.onCount += Count_2;
     相当于监听事件,触发事件时,只要哦调用onCount指向的函数,这样相当于把事件通知出去。所以上面这个Demo之后,我们再可以对委托来实现事件进行扩展:
     我们自定义一个双击火箭手按钮[用户控件]:
     
    namespace 双击火箭手按钮
    {
        //声明一个委托
        public delegate void DoubleClickDelegate();
        public partial class DoubleClickButton : UserControl
        {
            public DoubleClickButton()
            {
                InitializeComponent();
            }
           
            private int i;
            //把一个的委托对象定义为Public字段
            public DoubleClickDelegate OnDoubleClick;
    
            private void button1_Click(object sender, EventArgs e)
            {
    
                i++;
                if (i==2)
                {
                    i = 0;
                    if (OnDoubleClick!=null)
                    {
                        OnDoubleClick();//调用事件的响应函数
                    }
                }
            }
        }
    }

    然后我们在Form1中托入一个我们自定义的DoubleClickButton:

    这儿我们可以仿照普通Button按钮监听Click事件:  this.button1.Click += new System.EventHandler(this.button1_Click);来那样做:对DoubleClickButton的

    OnDoubleClick事件进行监听:
            private void Form1_Load(object sender, EventArgs e)
            {
                doubleClickButton1.OnDoubleClick = doFire;
                doubleClickButton1.OnDoubleClick += doIce;        }
            void doFire()
            {
                MessageBox.Show("双击火枪手开火");
            }
               void doIce()
            {
                MessageBox.Show("双击火枪手下冰雨");
            }

     

    这样一个简单的用户控件就完成了,双击两下触发了OnDoubleClick事件,并且去执行相关联的响应函数(doFire,doIce)。

    接着我们在对这个程序进行修改,来干扰我们的双击火枪手按钮:

    我们在捣乱这个按钮的中写入:

            private void button1_Click(object sender, EventArgs e)
            {
                doubleClickButton1.OnDoubleClick = null;
            }
    上面的操作意味着我们把委托变量指向了NULL,这就破坏了之前我们的监听事件。
    再比如:在模拟执行这个按钮中写入:
            private void button2_Click(object sender, EventArgs e)
            {
                doubleClickButton1.OnDoubleClick();
            }

    上面代码模拟执行了双击火枪后按钮,本来需要双击两下才能触发事件,而这儿可以直接去执行事件的响应函数。

     

    所以为了防止外界对我的事件的干扰,我们把

    public OnCountDelegate onCount;//把委托对象声明为一个字段

    改为:

       //把委托申明为Private,防止外界直接=NULL或者OnDoubleClick()仿照事件
       private DoubleClickDelegate OnDoubleClick;

    再对私有的委托用一个AddDoubleClick进行对外界的过滤,所以完整代码应该是这样的:

     //把委托申明为Private,防止外界直接=NULL或者OnDoubleClick()仿照事件
            private DoubleClickDelegate _OnDoubleClick;
            public void AddDoubleClick(DoubleClickDelegate d)
            {
                _OnDoubleClick += d;
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
    
                i++;
                if (i==2)
                {
                    i = 0;
                    if (_OnDoubleClick!=null)
                    {
                        _OnDoubleClick();//调用事件的响应函数
                    }
                }
            }

    上面这样处理之后,我们的控件就不会被外界干扰了。

    但是这样操作太复杂,所以微软为我们提供了更为简便的方式既 Event:

    对上面的双击火枪手按钮在做稍微修改:

    namespace 三击暴走按钮
    {
        public delegate void RampageDelegate();
        public partial class RampageThreeClickButton : UserControl
        {
    
            //定义一个私有的委托类型字段
            private RampageDelegate onRampage;
            //类似于属性那样对委托进行了封装
            public event RampageDelegate OnRampage 
            {
                add { onRampage += value; }
                remove { onRampage -= value; }
                
            }
            private int count;
            public RampageThreeClickButton()
            {
                InitializeComponent();
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
                count++;
                if (count==3)
                {
                    if (onRampage!=null)
                    {
                        onRampage();
                    }
                    count = 0;
                }
            }
        }
    }

    再将定义好的暴走控件拖到Form1中:

    后台代码是这样的:

           public Form1()
            {
                InitializeComponent();
            }
    
            private void rampageThreeClickButton1_Load(object sender, EventArgs e)
            {
                //对事件进行注册,即监听事件,只能用+=,不用用=,这样防止了外界=NULL干扰
                rampageThreeClickButton1.OnRampage += doFire;
                //同理
                rampageThreeClickButton1.OnRampage += new RampageDelegate(doThunder);
                rampageThreeClickButton1.OnRampage += new RampageDelegate(doIce);
    
            }
            void doFire()
            {
                MessageBox.Show("烈焰之怒");
            }
            void doThunder()
            {
                MessageBox.Show("上古雷霆");
            }
            void doIce()
            {
                MessageBox.Show("极地冰雨");
            }

    现在还能干扰吗?当然不行:

     

     

    OK,到目前为止,大家对委托和事件肯定有一个深刻的认识:

    我们对事件进行反编译之后,相信一切疑问皆是浮云:

    我们举一个标准的事件案例:

    namespace DoubleKissinUButton
    {
        public delegate void KissinUDelegate();//委托别忘记
        public partial class KissinUButton : UserControl
        {
            public event KissinUDelegate onKissinU;//用Event关键字来申明一个事件
            public KissinUButton()
            {
                InitializeComponent();
            }
            private int i = 0;
    
            private void button1_Click(object sender, EventArgs e)
            {
                i++;
                if (i==2)
                {
                    if (onKissinU!=null)
                    {
                        onKissinU();
                    }
                }
            }
        }
    }

    界面:

     public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
                kissinUButton1.onKissinU += new KissinUDelegate(kissinUButton1_onKissinU);
            }
    
            void kissinUButton1_onKissinU()
            {
                MessageBox.Show("点我我就吻吻你");
            }
    
            private void Form1_Load(object sender, EventArgs e)
            {
    
            }
        }

    通过反编译看看:

     

    点击进入看看:

     

    委托和事件没有可比性,因为委托是类型,事件是对象,上面说的是委托的对象(用委托方式实现的事件)和(标准的event方式实现)事件的区别。事件的内部是用委托实现的。 因为对于事件来讲,外部只能“注册自己+=、注销自己-=”,外界不可以注销其他的注册者,外界不可以主动触发事件,因此如果用Delegate就没法进行上面的控制,因此诞生了事件这种语法。add、remove。

    事件是用来阉割委托实例的。事件只能add、remove自己,不能赋值。事件只能+=、-=,不能=、不能外部触发事件。

  • 相关阅读:
    2020-10-03:java中satb和tlab有什么区别?
    2020-10-02:golang如何写一个插件?
    2020-10-01:谈谈golang的空结构体。
    2020-09-30:谈谈内存对齐。
    2020-09-29:介绍volatile功能。
    2020-09-28:内存屏障的汇编指令是啥?
    2020-09-27:总线锁的副作用是什么?
    2020-09-26:请问rust中的&和c++中的&有哪些区别?
    自定义刷新控件的实现原理
    scrollView的bounds
  • 原文地址:https://www.cnblogs.com/OceanEyes/p/delegateandevent.html
Copyright © 2011-2022 走看看