委托与事件

一、委托部分

 “委托”是一种指向一个类的静态方法,或者实例方法的数据结构,委托类似于 C++ 函数指针,但它是类型安全的。委托允许将方法作为参数进行传递,一旦为委托分配了方法,委托就将与该方法具有完全相同的行为。
委托主要用在两个方面:其一是CallBack(回调)机制;其二是事件处理机制。什么是回调,在上一篇文章中介绍过了。关于事件处理,在本文的后面,也将进行介绍。
委托可以链接在一起;例如,可以对一个事件调用多个方法。

委托实现回调的时候,可以直接传方法名,也可以传委托实例,如下:

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

namespace ConsoleApplication1
{
    class Program
    {
        public delegate void DelCout(string str);

        static void Main(string[] args)
        {   //直接传方法,在编译的时候,还是会帮我们new,这就是一个语法糖。
            DelMethod(Cout,"直接传方法");
           
            DelMethod(new DelCout(Cout),"传委托实例");

            Console.Read();
        }

        private static void DelMethod(DelCout delCout,string str)
        {
            delCout(str);
        }

        private static void Cout(string str)
        {
            Console.WriteLine(str);
        }
    }
}

关于委托的两个主要作用,可能你明白事件要通过委托来实现,这是C#内部定义成这样的,但不清楚为什么回调机制也要用委托。
在C\C++中,实现回调是通过函数指针来实现的,函数指针其实就是个内存地址,由于该地址不会携带任何其他信息,如函数期望的参数个数、参数类型、返回值类型等,所以这时的回调函数是非类型安全的.C#为回调函数提供了称为委托的机制,其能提供所期望的参数个数、参数类型、返回值类型等信息,因此委托是类型安全的.

二、事件

先给个例子:(代码来源:http://www.cnblogs.com/donghaiyiyu/archive/2007/07/29/828738.html)

using System;
 using System.Collections.Generic;
 using System.Text;
 
 namespace EventDemo
 {
        public delegate void SalaryCompute();        //声明一个代理类
 
        public class Employee
       {
              public event SalaryCompute OnSalaryCompute;         //定义事件,将其与代理绑定

              public virtual void FireEvent()       //触发事件的方法
              {
                    if (OnSalaryCompute != null)
                     {
                            OnSalaryCompute();      //触发事件
                     }
              }
       }

       public class HumanResource
       {
              public void SalaryHandler()          //事件处理函数
              {
                     Console.WriteLine("Salary");     //只是打印一行字而已
              }

              public static void Main()
              {
                     Employee ep = new Employee();
                     HumanResource hr = new HumanResource();
                     ep.OnSalaryCompute+=new SalaryCompute(hr.SalaryHandler);       //注册
                     ep.FireEvent();        //触发事件
                     Console.Read();
              }
       }
}

其实,从上面的代码看来,事件就是回调。所触发的事件,就是回调方法。

三、事件伪造

“事件”是指当对象发生某些事情时,向其它对象提供通知的一种方法。在C#中,事件是通过delegate来实现的。
事件有两个角色:一是事件发送方,一是事件接收方。事件发送方是指触发事件的对象,事件接收方是指注册事件发生时被通知的对象。
这里,我想重点说下事件伪造事件的解决方法。下面先看下什么是伪造事件。
在一个类中我们定义一个委托,把它做公有类型。如下:

public delegate void DelDbClick();
public DelDbClick delDbClick;

那么,外部的一个类,可以做一个方法,把这个委托设置成NULL(清除监听),也可以主动触发这个委托所关联的事件(假冒事件)。这种时候就可以造成“伪造事件”。
这个时候,我们可以用事件来对它进行包装,以消除上面的这两种情况。

public delegate void DelDbClick();
        private DelDbClick delDbClick;

        public event DelDbClick AddEvent
        {
            add
            {
                delDbClick += value;
            }
            remove
            {
                delDbClick -= value;
            }
        }

像这样,把委托定义成私有的,然后定义一个这个委托类型的事件来把它向外开放。那么外部就既不能直接把委托设置成NULL,也不能直接调用委托了。
当然,我们可以直接写一个方法来做这种包装,就象用属性来包装私有字段一样,但这样定起来更简单。

四、事件与委托之间的关联

这本来不应该单独写,但这个问题,被问得太多次了~~
首先应该明白,委托得一种类型(书上也说是一种数据结构),它与类平级,而事件是与方法属性同级别的,事件是用委托来实现的。有人说事件是一种特殊的委托,我觉得这样理解也可以。通过上面的例子,我们也知道,事件是对委托封装,方便了事件注册(多播委托),还可以消除误操作(事件伪装)。

五、为会么线程交叉通信要用委托

这是我一次面试的时候,被问到的。当时,面试官问我对于多线程了解多少。我确实不清楚,但也不能一点也不说。
说到时交叉通信要用委托时,面试官问:为什么要用委托,一下子懵了。
我现在的理解是:交叉通信的时候,用到了一个名为“invok”的方法,而这个方法中要求提供一个委托过去。其实,这个地方就是一个回调。那么,为什么用委托就清楚了。因为在C#中不能向C/C++一样,直接把方法作为参数传递。因为C/C++中传递方法名是一个指针,C++中的指针不通过MSIL而是直接和内存打交道,这便是指针不安全的原因所在。而委托则不同。C#中的委托不与内存打交道,而是把这一工作交给CLR去完成成。CLR无法阻止将不安全的代码调用到本机(非托管)代码中或执行恶意操作。然而当代码的类型安全时,CLR的安全性强制机制将确保代码不会访问本机代码,除非它有访问本机代码的权限。此外,委托作为参数进行传递的时候,传递的信息包括参数个数,参数类型,返回值类型等。因此委托是类型安全 。
我觉得这也就是为什么要用委托的原因了。

原文地址:https://www.cnblogs.com/La5DotNet/p/2820701.html