zoukankan      html  css  js  c++  java
  • 学习笔记对委托、匿名方法、Lama表达式及事件的理解

    .NET是采用”委托事件模型”来处理事件的, 委托事件模型的特点是: 将事件的处理委托给独立的对象, 而不是事件源本身, 从而将使用者界面与程序逻辑分开. 整个”委托事件模型”由产生事件的对象(事件源)、事件参数对象及事件监听者对象之间的关系所组成.

     

    产生事件的对象(事件源)会在事件产生时, 将与该事件相关的信息封装在一个称之为”事件参数对象”的对象中, 并将该对象传递给监听者对象, 监听者对象根据该事件参数对象内的信息决定适当的处理方式.

    监听者要收到事件发生的通知, 就必须要在程序中向事件源注册, 当事件发生时, 事件源就会主动通知监听者对象, 监听者对象就可以根据产生事件的对象来决定处理事件的方法.监听者对象就是用来处理事件的对象, 监听者对象等候事件的发生并在事件发生时收到通知.

    换句话讲, 事件是处理通知过程的对象, 也是.Net开发人员监视应用程序执行时出现的各种Windows消息的方式. 如果没有事件就必须监视WndProc捕获类似于WM_MOUSE_DOWN的消息, 而不是捕获按钮的鼠标Click事件.

    可见, 在事件处理中, 委托非常重要. 要想理解事件, 必须知道委托是什么.

    委托是方法的类型, 是具有相同返回值, 相同参数类型的一类方法的类型. 如何理解呢?

    我们知道, 计算机只能顺序处理指令, 由CPU根据内部的指令寄存器(IP)逐条获取指令地址后执行指令. 我们在程序中定义变量、类以及方法等的代码, 在CPU看来只是一条条的地址, CPU通过指令寄存器依次读取并执行, 而程序中的变量的读取等操作其实就是值的拷贝和地址的传递.

    既然委托代表方法的类型, 那么我来看一下方法. 方法也称为函数, 计算机要执行方法首先需要找到要执行的方法, 而这个操作是通过函数指针来完成的, 函数指针就是内存中存放该方法的内存地址. 组成方法的要素有返回值、方法名称及参数列表, CPU根据方法名(内存地址)找到方法后, 先将实参push到堆栈, 然后开始执行方法中的代码, 执行过程中需要参数时将会从堆栈中pop实参, 当方法执行完后将返回值push到堆栈, 此时我们用变量接收方法的返回值时, 将会从堆栈pop返回值, 我们也就拿到了方法的执行结果.

    以上是方法或者说函数执行的过程, 但是这个过程中无时无刻充满了风险, 稍有不慎就会出现堆栈越界, 造成程序崩溃甚至系统的宕机. 当然, 堆栈越界不是一定会出现错误, 像堆栈溢出攻击就利用了堆栈的越界访问, 执行未经授权的方法或程序段.

    .Net支持的C#语言是强类型的语言, 如果有一种机制能够安全的调用方法, 则即排除了风险又提高了程序执行的效率, 于是产生了委托.

    委托的定义格式: public delegate 返回值 委托名称 (参数列表). 前面提到过方法的三要素: 返回值、方法名及参数. 委托规定了返回值和参数的类型, 在执行方法时会进行类型检查, 防止堆栈越界调用的问题. 那么还有一个要素(方法名), 委托如何处理呢? 委托处理方法名的手段体现了委托一个巨大的优势: 只需要很少的代码改动就能为程序更换方法, 程序的基本逻辑不变(有点类似于多肽的手法).

    委托不能够直接调用(委托是类的类型), 可以直接调用的是委托实例, 而委托成为实例必须要绑定符合委托定义的方法. 下面看个小示例:

    代码
    namespace DelegateDemo
    {
    public delegate int MathDelegate(int one, int two); //定义委托, 委托属于5大类型(类、结构、枚举、委托和接口)之一

    public class MathClass
    {
    public int AddTwoNum(int onenum, int twonum) //符合委托定义的方法
    {
    return onenum + twonum;
    }

    public int MultiTwoNum(int onenum, int twonum)
    {
    return onenum * twonum;
    }
    }

    class Program
    {
    static void Main(string[] args)
    {
    //创建类实例, 以便访问实例方法
    MathClass mc = new MathClass();

    //创建委托实例
    MathDelegate md = new MathDelegate(mc.AddTwoNum);
    Console.WriteLine(md(
    6,7));

    //改变与委托绑定的方法
    md = new MathDelegate(mc.MultiTwoNum);
    Console.WriteLine(md(
    6,7));
    }
    }
    }

    现在我们来分析下委托的执行过程:

    首先类属于引用类型, 我们定义的类会存放在堆中, 类中有两个引用分别指向类中定义的两个方法. 当我们用new创建实例的时候, 将在内存中开辟另外一块空间存放math类的实例(只为实例的数据成员创建空间), 同时将该实例的引用赋给栈中的变量m, 此时就可以通过m访问Math类中的方法成员.

    委托也是引用类型, 因此同样存放在堆中, 委托类型中存放方法的返回值和参数类型的定义. 委托不能单独调用, 且创建委托实例时必须绑定方法. 当我们创建委托实例md时, 也会在内存中开辟空间, 委托实例首先从委托类型中获取方法返回值和参数的定义, 然后创建绑定方法的引用(该引用实际上是类中方法的引用), 之后将对被引用方法的进行检查.

    匿名方法和Lambda表达式是C#3.0的新特性, Lambda表达式其实就是匿名方法换个写法而已. 那么关键点就变成--匿名方法是什么.

    匿名方法就是没有方法名的委托实例, 匿名方法允许我们不用定义方法名就可以快速创建委托实例, 格式: delegate(参数列表){方法体}, 如实例代码:

    MathDelegate md3 = delegate(int a, int b)
    {
    return a + b;
    };
    Console.WriteLine(md3(
    6, 7));

    Lambda表达式就是匿名方法的不同写法而已. 格式为: 参数列表 => 方法体

    如下列代码:

    MathDelegate md4 = (x, y) => x + y;
    Console.WriteLine(md4(
    6, 7));

    md4
    = (x,y) => x * y;
    Console.WriteLine(md4(
    6,7));

    关于事件:

    事件就是当对象或类的状态发生改变时, 对象或类发出的信息或通知. 通俗点讲, 所谓事件就是由某个对象发出的消息, 这个消息标志着某个特定的行为发生了, 或者某个特定的条件成立了.发出信息的对象或类称为”事件源”, 对事件进行处理的方法称为”事件监听者”, 通常”事件监听者”监听事件源发出的信息或通知, 一旦收到通知将执行相应的方法.

    从本质上讲, 事件就是附加了安全限制的委托, 事件对委托做了一些限制和改进, 如: 禁止外界直接调用委托所绑定的方法, 禁止覆盖委托绑定的方法, 以及扩展了委托绑定方法的限制, 使委托可以绑定多个方法等.

    那么, 如何用程序表示一个事件呢? 有5个步骤.

    1.  定义委托: 事件的定义必须要指定一个委托类型.

    2.  定义事件: 可以看做是委托变量加上一个event的前缀

    3.  注册事件(很多时候也将注册事件放在构造函数中进行)

    4.  定义响应事件的方法, 该方法需要符合委托定义, 即解决实际问题的方法.

    5.  定义事件的触发方法: On事件名

    代码示例如下:

    代码
    namespace EventDemo
    {
    //1.定义委托
    public delegate void KillEngine();
    public delegate void ThiefEventHandler(object sender, EventArgs e); //符合.Net命名规范的委托

    public class ComputerRoom
    {
    //2. 定义事件
    public event KillEngine PowerOff;
    public event ThiefEventHandler ThiefEvent; //符合.Net命名规范的事件

    //3.1 构造函数中注册事件
    public ComputerRoom()
    {
    this.PowerOff += new KillEngine(ShutPc);
    this.PowerOff += new KillEngine(ShutWaterMachine);
    this.PowerOff += new KillEngine(ShutLightsOff);
    }

    //4.1 响应事件的处理方法
    void ShutPc()
    {
    Console.WriteLine(
    "所有计算机已关闭! ");
    }
    void ShutWaterMachine()
    {
    Console.WriteLine(
    "饮水机已关闭! ");
    }
    void ShutLightsOff()
    {
    Console.WriteLine(
    "所有照明设备已关闭! ");
    }

    //5. 定义事件的触发方法
    public void OnPowerOff()
    {
    if (this.PowerOff != null)
    {
    this.PowerOff();
    }
    }
    public void OnThiefEvent(EventArgs e)
    {
    if (this.ThiefEvent != null)
    {
    this.ThiefEvent(this, EventArgs.Empty);
    }
    }
    }


    class Program
    {
    static void Main(string[] args)
    {
    ComputerRoom cr
    = new ComputerRoom();

    //3.2 通过类的实例注册事件
    cr.ThiefEvent += new ThiefEventHandler(Call110);
    cr.ThiefEvent
    += new ThiefEventHandler(ShutDoors);

    //发生了事件, 事件被触发
    cr.OnPowerOff();
    Console.WriteLine(
    "\n------------------------------------\n");
    cr.OnThiefEvent(
    null);
    }

    //4.2 在类外的响应事件的方法
    static void Call110(object sender, EventArgs e)
    {
    //throw new NotImplementedException();
    Console.WriteLine("机房被非法入侵, 已通知110! ");
    }

    static void ShutDoors(object sender, EventArgs e)
    {
    Console.WriteLine(
    "所有门窗已关闭! ");
    }

    }
    }
  • 相关阅读:
    LeetCode Convert Sorted List to Binary Search Tree
    LeetCode Convert Sorted Array to Binary Search Tree
    LeetCode Restore IP Addresses
    Linux-NoSQL之memcached
    Linux-LAMP虚拟主机配置
    Linux-Discuz安装LAMP
    python-Django与Nginx整合gunicorn模块
    python-Django与Apache整合wsgi模块
    python-Django收集主机信息json格式
    Linux-监控与安全运维之zabbix
  • 原文地址:https://www.cnblogs.com/cs_net/p/1863084.html
Copyright © 2011-2022 走看看