《C#图解教程》读书笔记之五:委托和事件

本篇已收录至《C#图解教程》读书笔记目录贴,点击访问该目录可获取更多内容。

一、委托初窥:一个拥有方法的对象

  (1)本质:持有一个或多个方法的对象;委托和典型的对象不同,执行委托实际上是执行它所“持有”的方法。如果从C++的角度来理解委托,可以将其理解为一个类型安全的、面向对象的函数指针

  (2)如何使用委托?

    ①声明委托类型(delegate关键字)

    ②使用该委托类型声明一个委托变量

    ③为委托类型增加方法

    ④调用委托执行方法

  (3)委托的恒定性:

  组合委托、为委托+=增加方法以及为委托-=移除方法让我们看起来像是委托被修改了,其实它们并没有被修改。事实上,委托是恒定的

  在为委托增加和移除方法时实际发生的是创建了一个新的委托,其调用列表是增加和移除后的方法结果。

  (4)委托实例:

  ①简单带参数委托DEMO

  delegate void MyDel(int value); //声明委托类型

    class Program
    {
        void PrintLow(int value)
        {
            Console.WriteLine("{0} - LowValue", value);
        }

        void PrintHigh(int value)
        {
            Console.WriteLine("{0} - HighValue", value);
        }

        static void Main(string[] args)
        {
            Program program = new Program();

            MyDel myDel; //声明委托类型

            //获取0~99之间的一个随机数
            Random random = new Random();
            int randomValue = random.Next(99);

            //创建一个包含具体方法的委托对象并将其赋值给myDel变量
            myDel = randomValue < 50 ?
                new MyDel(program.PrintLow) : new MyDel(program.PrintHigh);

            //执行委托
            myDel(randomValue);

            Console.ReadKey();
        }
    }

  ②简单无参数多方法列表委托DEMO

delegate void PrintFunction();

class Test
{
   public void Print1()
   {
      Console.WriteLine( "Print1 -- instance" );
   }

   public static void Print2()
   {
      Console.WriteLine( "Print2 -- static" );
   }
}

class Program
{
   static void Main()
   {
      Test t = new Test();
      PrintFunction pf;
      pf = t.Print1; 

      pf += Test.Print2;
      pf += t.Print1;
      pf += Test.Print2;

      if ( pf != null )
      {
          pf();
       }
      else
       {
           Console.WriteLine( "Delegate is empty" );
       }
   }
}            

  ③带返回值的委托DEMO

delegate int MyDel(); 

class MyClass
{
   int IntValue = 5;

   public int Add2()
   {
      IntValue += 2;
      return IntValue;
   }

   public int Add3()
   {
      IntValue += 3;
      return IntValue;
   }
}

class Program
{
   static void Main()
   {
      MyClass mc = new MyClass();

      MyDel mDel = mc.Add2;
      mDel += mc.Add3;
      mDel += mc.Add2;          

      Console.WriteLine( "Value: {0}", mDel() );
   }
}

二、匿名方法:不好意思,我匿了

  在委托所持有的方法中,如果某个方法只被使用一次,这种情况下,除了创建委托语法的需要,没有必要创建独立的具名方法。因此,匿名方法应运而生。

  匿名方法是在初始化委托时内联(inline)声明的方法

  下面来看看在两个版本的代码:具名方法和匿名方法的比较,匿名方法是不是简洁得多?

  ①具名参数

 

   ②匿名参数

 

三、Lambda表达式:好吃的语法糖

  (1)本质:简化语法的”语法糖“;

Lambda来源:1920年到1930年期间,数学家Alonzo Church等人发明了Lambda积分。Lambda积分是用于表示函数的一套系统,它使用希腊字母Lambda(λ)来表示无名函数。近年来,函数式编程语言(如Lisp)使用这个术语来表示可以直接描述函数定义的表达式,表达式不再需要有名字了。

  (2)要点:

    ①Lambda表达式中的参数列表(参数数量、类型和位置)必须与委托相匹配;

    ②表达式中的参数列表不一定需要包含类型,除非委托有ref或out关键字(此时必须显示声明);

    ③如果没有参数,必须使用一组空的圆括号;

  (3)语法:

四、事件初窥:发布者和订阅者模式

  发布者订阅者模式定义了一种一对多的依赖关系,让多个订阅者对象同时监听某一个主题对象。这个主题对象在自身状态变化时,会通知所有订阅者对象,使它们能够自动更新自己的状态。

  由订阅者提供的方法称为回调方法,因为发布者通过执行这些方法来”往回调用订阅者的方法“。还可以将它们称为事件处理程序,因为它们是为处理事件而调用的代码。

  下面通过一段经典的代码来看看这个模式的应用:

using System;

delegate void Handler();

class Incrementer
{
   public event Handler CountedADozen;

   public void DoCount()
   {
      for ( int i=1; i < 100; i++ )
         if ( i % 12 == 0 && CountedADozen != null )
            CountedADozen();
   }
}

class Dozens
{
   public int DozensCount { get; private set; }

   public Dozens( Incrementer incrementer )
   {
      DozensCount = 0;
      incrementer.CountedADozen += IncrementDozensCount;
   }

   void IncrementDozensCount()
   {
      DozensCount++;
   }
}

class Program
{
   static void Main()
   {
      Incrementer incrementer = new Incrementer();
      Dozens dozensCounter    = new Dozens( incrementer );

      incrementer.DoCount();
      Console.WriteLine( "Number of dozens = {0}",
                              dozensCounter.DozensCount );
   }
}

五、事件全过程:声明、订阅和触发

  (1)声明事件:

      ①事件声明在一个类中;

    ②附加的方法需与委托类型的签名和返回类型匹配;

    ③声明为public;

    ④无法new;

  (2)订阅事件:

    ①使用+=为事件增加事件处理程序;

    ②可以使用匿名方法和Lambda表达式;

  (3)触发事件:

    ①使用事件名称,后面跟的参数列表包含在圆括号中;

    ②参数列表必须与事件的委托类型相匹配;  

六、走向标准之路:EventHandler

  程序的异步处理是使用C#事件的绝佳场景。Windows GUI广泛地使用了事件,对于事件的使用,.NET框架提供了一个标准模式:EventHandler委托类型。

  (1)第一个参数保存触发事件的对象的引用(object类型,可以匹配任何类型的实例);

  (2)第二个参数保存状态信息(EventArgs类的实例),指明什么程序适用于该应用程序;

  (3)返回类型为void;

  现在我们来重构刚刚的订阅者类,使用标准的EventHandler委托类型:

class Dozens
{
   public int DozensCount { get; private set; }

   public Dozens( Incrementer incrementer )
   {
      DozensCount = 0;
      incrementer.CountedADozen += IncrementDozensCount;
   }

   void IncrementDozensCount( object source, EventArgs e )
   {
      DozensCount++;
   }
}

  那么,刚刚看到为了保持标准模式,我们只能有两个参数,第一个是触发事件的对象引用,第二个是EventArgs类的实例,如何在事件中传递数据呢?答案肯定是在第二个参数上找到切入点。我们可以声明一个派生自EventArgs的子类,在其中声明我们要传递的参数所对应的属性来保存我们需要传入的数据。TIPS:这个自定义子类的名称建议以EventArgs结尾。

public class IncrementerEventArgs : EventArgs
{
   public int IterationCount { get; set; }
}

  既然使用了自定义类,那么在事件的其他几部分中要使用该自定义类还必须改为泛型委托和声明自定义类对象。

class Incrementer
{
   public event EventHandler<IncrementerEventArgs> CountedADozen;

   public void DoCount()
   {
      IncrementerEventArgs args = new IncrementerEventArgs();
      for ( int i=1; i < 100; i++ )
         if ( i % 12 == 0 && CountedADozen != null )
         {
            args.IterationCount = i;
            CountedADozen( this, args );
         }
   }
}

  为了在执行程序中获取到传递的数据值,便可以直接通过派生自EventArgs的自定义类的属性的到。

class Dozens
{
   public int DozensCount { get; private set; }

   public Dozens( Incrementer incrementer )
   {
      DozensCount = 0;
      incrementer.CountedADozen += IncrementDozensCount;
   }

   void IncrementDozensCount( object source, IncrementerEventArgs e )
   {
      Console.WriteLine( "Incremented at iteration: {0} in {1}",
                                             e.IterationCount, source.ToString() );
      DozensCount++;
   }
}

本章思维导图

附件

  思维导图(jpg、pdf以及mmap源文件)下载:http://pan.baidu.com/s/1hqA7KH2

作者:周旭龙

出处:http://www.cnblogs.com/edisonchou/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

时间: 2024-10-10 10:23:13

《C#图解教程》读书笔记之五:委托和事件的相关文章

SQL Server2012 T-SQL基础教程--读书笔记(5-7章)

SQL Server2012 T-SQL基础教程--读书笔记(5-7章) SqlServer T-SQL 示例数据库:点我 Chapter 05 表表达式 5.1 派生表 5.1.1 分配列别名 5.1.2 使用参数 5.1.3 嵌套 5.1.4 多个引用 5.2 公用表表达式 5.2.1 分别列别名 5.2.2 使用参数 5.2.3 定义多个CTE 5.2.4 CTE的多次引用 5.2.5 递归CTE 5.3 视图 5.3.1 视图和ORDER BY 子句 5.3.2 视图选项 5.4 内嵌表

SQL Server2012 T-SQL基础教程--读书笔记(8 - 10章)

SQL Server2012 T-SQL基础教程--读书笔记(8 - 10章) 示例数据库:点我 CHAPTER 08 数据修改 8.1 插入数据 8.1.1 INSERT VALUES 语句 8.1.2 INSERT SELECT 语句 8.1.3 INSERT EXEC 语句 8.1.4 SELECT INTO 语句 8.1.5 BULK INSERT 语句 8.1.6 标识列属性和序列对象 8.1.6.1 标识列属性 8.1.6.2 序列对象 8.2 删除数据 8.2.1 DELETE 语

SQL Server2012 T-SQL基础教程--读书笔记

SQL Server2012 T-SQL基础教程--读书笔记 SqlServer Chapter 01 T-SQL 查询和编程背景 1.3 创建表和定义数据的完整性 1.3.1 创建表 1.3.2 定义数据的完整性 1. 主键约束 2. 唯一约束 3. 外键束约 4. CHECK约束 5. 默认约束 Chapter 02 单表查询 2.1 SELECT 语句元素 2.1.7 TOP和OFFSET-FETCH 1. TOP筛选 2. OFFSET-FETCH 2.1.8 开窗函数速览 2.2 谓词

图解HTTP读书笔记(十)

图解HTTP读书笔记(十) Web的攻击技术 HTTP协议本身并不存在安全性问题,因此协议本身几乎不会成为攻击对象.应用HTTP协议的服务器和客户端,以及运行在服务器上的Web应用资源才是攻击目标. 在客户端即可篡改请求 在Web应用中,从浏览器那接收到的HTTP请求的全部内容,都可以在客户端自由的变更.篡改. 在HTTP请求报文内加载攻击代码,就能发起对Web应用的攻击.通过URL查询字段或表单.HTTP首部,Cookie等途径把攻击代码传入,若这时Web应用存在安全漏洞,那内部信息就会遭到窃

读书笔记(委托)

委托回调静态方法和实例方法有何区别 当委托绑定静态方法时,内部的对象成员变量_target会被设置成null 当委托绑定实例方法是,_target将会设置成指向该实例方法所属的一个实例对象 当委托被执行时,该对象实例将会用来调用实例方法 1 class DelegateReturn 2 { 3 public delegate String GetStringDelegate(); 4 5 static string GetTypeName() 6 { 7 return typeof(Delega

图解算法读书笔记

区别于以往的读书笔记 这次采用了思维导图的模式 PHP js Linux  也相继整理自己的思维导图 便于理解和记忆 主要分为 算法  和数据结构两部分 结合书中python的demo,全部敲了一遍 一 算法 提到算法 绕不过去的肯定是大O表示法  也是各种面试问时间复杂度的重点考察的基础问题 二 数据结构 由堆排序再引出 二叉树 红黑树 等等内容 下次再更新 原文地址:https://www.cnblogs.com/Sherlock09/p/9052912.html

C#图解教程 第十三章 委托

委托 什么是委托委托概述声明委托类型创建委托对象给委托赋值组合委托为委托添加方法从委托移除方法调用委托委托示例调用带返回值的委托调用带引用参数的委托匿名方法 使用匿名方法匿名方法的语法 Lambda 表达式 委托 什么是委托 可以认为委托是持有一个或多个方法的对象.当然,正常情况下你不想"执行"一个对象,但委托与典型对象不同.可以执行委托,这时委托会执行它所"持有"的方法. 我们从下面的示例代码开始.具体细节将在本章剩余内容介绍. 代码开始部分声明了一个委托类型My

[读书笔记]C#学习笔记二: 委托和事件的用法及不同.

前言:  C#委托是什么 c#中的委托可以理解为函数的一个包装, 它使得C#中的函数可以作为参数来被传递, 这在作用上相当于C++中的函数指针. C++用函数指针获取函数的入口地址, 然后通过这个指针来实现对函数的操作. 委托的定义和方法的定义类似, 只是在定义的前面多了一个delegate关键字. 正文: 委托可以被视为一个更高级的指针,它不仅仅能把地址传指向另一个函数,而且还能传递参数,返回值等多个信息. 系统还为委托对象自动生成了同步,异步的调用方式,开发人员使用BeginInvoke,E

java web轻量级开发面试教程读书笔记:建索引时我们需要权衡的因素

场景一,数据表规模不大,就几千行,即使不建索引,查询语句的返回时间也不长,这时建索引的意义就不大.当然,若就几千行,索引所占的空间也不多,所以这种情况下,顶多属于"性价比"不高. 场景二,某个商品表里有几百万条商品信息,同时每天会在一个时间点,往其中更新大概十万条左右的商品信息,现在用where语句查询特定商品时(比如where name = 'XXX')速度很慢.为了提升查询效率可以建索引,但当每天更新数据时,又会重建索引,这是要耗费时间的. 这时就需要综合考虑,甚至可以在更新前删除