《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)声明的方法

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

  ①具名参数

using System;

class Program
{
   public static int Add20( int x )
   {
      return x + 20;
   }

   delegate int OtherDel( int InParam );
   static void Main()
   {
      OtherDel del = Add20;

      Console.WriteLine( "{0}", del( 5 ) );
      Console.WriteLine( "{0}", del( 6 ) );
   }
}

   ②匿名参数

using System;

class Program
{
  delegate int OtherDel(int InParam);

  static void Main()
  {
    OtherDel del = delegate(int x)
                   {
                     return x + 20;
                   };
    Console.WriteLine("{0}", del(5));
    Console.WriteLine("{0}", del(6));
  }
}

三、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/

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏猿人谷

memcpy的函数

网新恒天2014校园招聘笔试编程题 已知memcpy的函数为: void* memcpy(void *dest , const void* src , s...

23180
来自专栏听雨堂

Python基础语法学习整理

1、基础 r’  ‘:原始字符串 pow、round是内建函数 2、序列通用操作: 索引:d[] 分片:[:] 相加:d+[] 乘法:[1,2]*3...

22760
来自专栏Kiba518

C#语法——委托,架构的血液

微软用delegate关键字来声明委托,delegate与int,string,double等关键字一样。都是声明用的。

7430
来自专栏yl 成长笔记

c# typeof 与 GetType 作用与区别

Used to obtain the "System.Type" object for a type. A 'typeof‘ expression takes ...

32510
来自专栏决胜机器学习

PHP数据结构(二十五) ——并归排序

PHP数据结构(二十五)——并归排序 (原创内容,转载请注明来源,谢谢) 一、概述 并归排序是将两个或两个以上的有序表组合成一个新的有序表。采用并归的思想进...

41880
来自专栏Java技术栈

屌炸天,JDK8的排序大法!!

首先祝大家端午节快乐! 今天总结了下JDK中排序的方法,包括JDK8中强大的lambda表达式及函数式接口运用,不废话,请看下面示例。 public class...

26960
来自专栏大闲人柴毛毛

剑指offer代码解析——面试题14调整数组顺序使奇数在偶数之前

本题详细解析都已在代码中注释了: /** * 题目:输入一个数组,要求将奇数放在数组的前半段,偶数放在数组的后半段 * @author 大闲人柴毛毛 *...

32350
来自专栏大内老A

yield在WCF中的错误使用——99%的开发人员都有可能犯的错误[下篇]

昨天写了《yield在WCF中的错误使用——99%的开发人员都有可能犯的错误[上篇]》,引起了一些讨论。关于yield关键字这个语法糖背后的原理(C#编译器将它...

20180
来自专栏Hongten

python开发_函数的参数传递

在这个用例中,我们要讨论的是关于函数的传参问题 我所使用的python版本为3.3.2

11240
来自专栏Python小屋

Python组合列表中多个整数得到最小整数(一个算法的巧妙实现)

'''程序功能: 给定一个含有多个整数的列表,将这些整数任意组合和连接, 返回能得到的最小值。 代码思路: 将这些整数变为相同长度(按最...

36260

扫码关注云+社区

领取腾讯云代金券