定义:委托是一种引用类型,表示对具有特定参数列表和返回类型的方法的引用。 在实例化委托时,你可以将其实例与任何具有兼容签名和返回类型的方法相关联

目的:方法声明和方法实现的分离,使得程序更容易扩展

一、对委托的理解

1. 为什么将方法作为另一个方法的参数

先不解释定义,看一段代码

  public void Method1(object obj)
   {
      //内部可以访问obj的成员
   }

这是随便写的一个方法,没有实际意义,但是,根据我们已掌握的关于类型的基础知识,应该明白,这里的obj(引用类型)作为形参,存放的是对象的引用,既然获取到了对象的引用,那么我们可以在run方法内部对obj的成员进行访问(一段废话)。好了,现在我要问一个问题:为什么要将obj作为参数?

问题先慢慢想着,我们再次看一下委托的定义,"是引用类型,对方法的引用",看下面的代码

  public void Method2(delegate del)
   {
      //内部可以访问del的什么?
      //只能执行方法
      del();
   }

delegate 作为一种引用类型,引用的是个方法,我们能对方法做什么,只能执行方法。

下面我们回答刚才的问题,obj作为参数(类型声明),将类型的声明和类型的实例分离。当然这样做的目的是为了封装变化,所有类型都可以作为实参来使用Method1,因为Object是基类,当然也可以将Object换成其他接口类型,只要实现了该接口的类型都可以作为Method1的实参。

虽然Method1和Method2参数类型不一样,但是目的是一致的,委托是将方法的声明和实现分离。

下面看一个网上使用广泛的例子

    示例1
     //定义委托,与任何具有兼容签名和返回类型的方法相关联
     public delegate void GreetingDelegate(string name);
    class Program
    {
        private static void EnglishGreeting(string name)
        {
            Console.WriteLine("Morning, " + name);
        }
        private static void ChineseGreeting(string name)
        {
            Console.WriteLine("早上好, " + name);
        }
        //将委托类型GreetingDelegate作为形参声明
        private static void GreetPeople(string name, GreetingDelegate MakeGreeting)
        {
            //这里可以完成其他的业务逻辑
            //然后调用委托
            MakeGreeting(name);
        }
        static void Main(string[] args)
        {
             GreetPeople("hanmeimei", EnglishGreeting);//使用静态方法初始化委托
             GreetPeople("韩梅梅", new Program().ChineseGreeting);//使用实例方法初始化委托
             Console.ReadKey();
        }
    }

委托类型GreetingDelegate作为形参声明,只要是具有兼容签名和返回类型的方法都可以作为GreetPeople方法的实参。这样一来,GreetPeople方法不仅可以通过中文和英文问好了,所有与委托类型GreetingDelegate相关联的语言(方法)都可以问好了,程序更容易扩展了。

2. 委托是一种引用类型

既然委托是一种类型,应该包含类型的成员,我们将代码编译完成后,借助反编译工具看下,编译后的样子:



下面这两种方式是等同的:

 MakeGreeting(name);
 MakeGreeting.Invoke(name);

而BeginInvoke和EndInvoke是属于异步调用的范畴,我们稍后再说。

3.Lambda表达式比匿名方法简约

从示例1中可以看到分别显示调用了静态方法和实例方法初始化了委托,但是对于那些只使用一次的方法,就没有必要创建具名方法了。C#2.0提出了使用匿名方法代替具名方法的解决方案

  GreetingDelegate MakeGreeting = delegate (string name)
  {
     Console.WriteLine("早上好, " + name);
  };
  GreetPeople("韩梅梅", MakeGreeting);

匿名方法仍然比较繁琐,C#3.0引入了Lambda表达式

    //去掉delegate关键字并添加 =>运算符
  GreetingDelegate MakeGreeting = (string name) =>
   {
        Console.WriteLine("早上好, " + name);
   };
  GreetPeople("韩梅梅", MakeGreeting);
  //根据委托的参数类型推断,string类型也去掉了
  GreetingDelegate MakeGreeting = name =>
  {
      Console.WriteLine("早上好, " + name);
  };
  GreetPeople("韩梅梅", MakeGreeting);         
4. 委托也是类型安全的
  GreetingDelegate MakeGreeting1 = (int name) =>
  {
      Console.WriteLine("早上好, " + name);
  };

5. 泛型委托的协变和逆变

常用泛型委托

  • Action
  • Func

关于泛型委托的协变和逆变可以阅读这篇文章《C#基本功之泛型》

有了常用泛型委托,我们就不需要自己定义GreetingDelegate委托类型了,对委托的使用进一步的简化了。

     //用泛型委托声明形参,Func于此类似,只不过有返回值而已
     private static void GreetPeople(string name,Action<string> action)
     {
            //这里可以完成其他的业务逻辑
            //然后调用委托
            action.Invoke(name);
     }
   //调用
   GreetPeople("韩梅梅", name => Console.WriteLine("早上好, " + name))

到目前为止,我们将方法的变化抽象,并用委托封装,实现了委托的简单使用。但委托还有很大的用处。

委托是类的成员,我们看一个作为类的成员使用的例子:现在生活中智能设备越来越普及,以前需要自己动手拉开窗帘、打开热水器等等,现在只需要设定场景,利用智能设备就可以完成这些操作。

当时间定格为早上7点的时候,闹钟想起,窗帘自动打开,热水器开始烧水,加湿器关闭.....

 //先不用管EventArgs参数,object 类型的sender,可以理解为任何类型都可以传递
 public delegate void EventHandler(object sender, EventArgs e);
 /// <summary>
 /// 控制中心
 /// </summary>
 public class ControlCore
  {
         public DateTime Time { get; set; } = DateTime.Now;
        /// <summary>
        /// 执行任务
        /// </summary>
        public event EventHandler Task;
 }
 static void Main(string[] args)
 {
         var controlCore= new ControlCore();
         controlCore.Task= new EventHandler(AlarmClock);
         //怎么操作委托?
         //添加、移除
         controlCore.Task+=....;
        controlCore.Task-=....;
        //或者直接覆盖掉
       controlCore.Task=....;
       controlCore.Task(null, null);
  }
  /// <summary>
  /// 闹钟
  /// </summary>
 public static void AlarmClock(object sender, EventArgs e)
  {
      Console.WriteLine("起床了,亲");
 }

作为类的成员时,要怎么操作委托?我们都知道委托还可以添加或移除方法,所以我们不仅可以直接调用委托,还可以添加、移除或者直接覆盖委托,对委托的操作没有任何限制。如此一来,破坏了类的封装性。我们希望对委托有一些限制,就像用属性去限制字段的输入输出一样;

        //将委托的访问级别改为private
        private EventHandler Task;
       //为委托添加方法
        public void AddTask(EventHandler handler)
        {
            if (this.Task== null)
                this.Task= new EventHandler(handler);
            else
                this.Task+= new EventHandler(handler);
        }
       //移除方法
        public void RemoveTask(EventHandler handler)
        {
            System.Delegate.Remove(this.Task, handler);
        }
       //因为委托定义为private,所以需要提供调用委托的接口
        public void OnTask(EventArgs e)
        {
            //7点了
            if (Time.Hour == 7)
            {
                if (this.Task!= null)
                {
                    this.Task(this, e);
                }
            }
        }

如果对委托的使用仅仅是添加或移除方法,然后执行委托的调用列表,我相信用事件会更简单容易;

事件是以委托为基础,可以理解为对委托的进一步封装。

二、对事件的理解

1. 事件是将委托封装,并对外公布了订阅和取消订阅的接口

将委托private EventHandler Task;修改为事件public event EventHandler Task;而我们创建的方法AddTask和RemoveTask也需要去掉了,重新生成代码,通过反编译工具可以看到:

事件编译后生成的两个方法,与我们的示例中AddClick和RemoveClick方法类似;同时可以看到EventHandler委托,已经字段变为private的访问级别了(小锁表示私有);这样一来,事件帮我们完成了对委托的“限制“;

在客户端访问事件也只能+=(订阅)或者 -=(取消订阅)了,如果直接用“=“运算符赋值就会报错(在Control类内容还是可以的);

2. 事件使用 发布-订阅(publisher-subscriber) 模型

发布者:包含事件的类用于触发事件,而这个类称为事件的“发布者”。

通过声明委托类型的事件,将委托与事件关联。发布者对象调用这个事件,并通知订阅者对象

订阅者:其他注册该事件的类称为“订阅者”。

订阅者注册事件并提供事件处理程序(闹钟、打开窗帘、热水器烧水等),在发布者类中通过委托调用订阅者的事件处理程序。

```

public delegate void EventHandler(object sender, EventArgs e);

///



/// 控制中心

///

public class ControlCore

{

public DateTime Time { get; set; } = DateTime.Now;

///



/// 执行任务

///

public event EventHandler Task;

///



/// 触发事件

///

///

public void OnTask(EventArgs e)

{

//7点了

if (Time.Hour == 7)

{

if (this.Task != null)

{

this.Task(this, e);

}

}

}

}

订阅者类中的事件处理程序
    //下面的方法分别属于订阅者类中的方法,篇幅有限,没有单独声明每一个订阅者类
    /// <summary>
    /// 闹钟响起
    /// </summary>
    public static void AlarmClock(object sender, EventArgs e)
    {
        var core = (ControlCore)sender;
        Console.WriteLine(core.Time.Hour+"点了,起床了,亲");
    }
    /// <summary>
    /// 打开窗帘
    /// </summary>
    public static void OpenWindow(object sender, EventArgs e)
    {
        var core = (ControlCore)sender;
        Console.WriteLine(core.Time.Hour + "点了,打开窗帘");
    }
    /// <summary>
    /// 热水器烧水
    /// </summary>
    public static void BoilWater(object sender, EventArgs e)
    {
        var core = (ControlCore)sender;
        Console.WriteLine(core.Time.Hour + "点了,热水器开始烧水");
    }
客户端代码
    static void Main(string[] args)
    {
        var controlCore = new ControlCore();
        controlCore.Task += new EventHandler(AlarmClock);
        controlCore.Task += OpenWindow;
        controlCore.Task += BoilWater;
        while (true)
        {
            controlCore.OnTask(null);
            Console.ReadKey();
        }
    }
执行结果

7点了,起床了,亲

7点了,打开窗帘

7点了,热水器开始烧水

##### 3. 关于sender和EventArgs
在示例代码中,可以看到将ControlCore本身作为参数传递给订阅者,既然订阅了发布者的动态,那么关于发布者的某些信息或许感兴趣(比如ControlCore中的Time)。
而EventArgs是作为发布者信息之外的信息传递

//自定义参数类型

public class CustomEventArgs: EventArgs

{

public string Arg1 { get; set; }

public string Arg2 { get; set; }

}

用CustomEventArgs替换委托中的参数EventArgs。```public delegate void EventHandler(object sender, CustomEventArgs e)```
那么在客户端调用时传入更多的信息

controlCore.OnTask(new ButtonEventArgs()

{

Arg1="",

Arg2=""

});

```

总结:一直想写一篇关于委托和事件的文章,但是网上已经有很多这类优秀的文章了,不乏一些佼佼者,由浅入深,从无到有的风格将知识点讲的很透彻。如果我再按照这个类型去写,实在没有意思。

所以我想,我们可不可以从已知到未知这条路径来将知识点讲明白,比如,我们知道将具有相同属性和行为的对象抽象为类型。那么我是不是可以将具有相同签名和返回类型的方法抽象为委托?再比如,我们知道属性封装了字段,并对字段的输入输出进行了限制。那么我是不是可以将委托封装,控制委托的注册或取消,这样就引出了事件。

希望可以帮助到朋友们

:.NET关于委托和事件的编码规范

  • 委托类型的名称都应该以EventHandler结束。
  • 事件的命名为 委托去掉 EventHandler之后剩余的部分。
  • 委托的原型定义:有一个void返回值,并接受两个输入参数:一个Object 类型,一个 EventArgs类型(或继承自EventArgs)。
  • 继承自EventArgs的类型应该以EventArgs结尾。

C#基本功之委托和事件的更多相关文章

  1. [ASP.NET MVC 大牛之路]02 - C#高级知识点概要(1) - 委托和事件

    在ASP.NET MVC 小牛之路系列中,前面用了一篇文章提了一下C#的一些知识点.照此,ASP.NET MVC 大牛之路系列也先给大家普及一下C#.NET中的高级知识点.每个知识点不太会过于详细,但 ...

  2. [ASP.NET 大牛之路]02 - C#高级知识点概要(1) - 委托和事件

    在ASP.NET MVC 小牛之路系列中,前面用了一篇文章提了一下C#的一些知识点.照此,ASP.NET MVC 大牛之路系列也先给大家普及一下C#.NET中的高级知识点.每个知识点不太会过于详细,但 ...

  3. .NET面试题系列[7] - 委托与事件

    委托和事件 委托在C#中具有无比重要的地位. C#中的委托可以说俯拾即是,从LINQ中的lambda表达式到(包括但不限于)winform,wpf中的各种事件都有着委托的身影.C#中如果没有了事件,那 ...

  4. .NET基础拾遗(4)委托、事件、反射与特性

    Index : (1)类型语法.内存管理和垃圾回收基础 (2)面向对象的实现和异常的处理基础 (3)字符串.集合与流 (4)委托.事件.反射与特性 (5)多线程开发基础 (6)ADO.NET与数据库开 ...

  5. [转载]C#深入分析委托与事件

    原文出处: 作者:风尘浪子 原文链接:http://www.cnblogs.com/leslies2/archive/2012/03/22/2389318.html 同类链接:http://www.c ...

  6. [转载]C#委托和事件(Delegate、Event、EventHandler、EventArgs)

    原文链接:http://blog.csdn.net/zwj7612356/article/details/8272520 14.1.委托 当要把方法作为实参传送给其他方法的形参时,形参需要使用委托.委 ...

  7. C#委托与事件

    一.在控制台下使用委托和事件 我们都知道,C#中有"接口"这个概念,所谓的"接口"就是定义一套标准,然后由实现类来具体实现其中的方法,所以说"接口,是 ...

  8. C#委托与事件的简单使用

    前言:上一篇博文从原理和定义的角度介绍了C#的委托和事件.本文通过一个简单的小故事,来说明C#委托与事件的使用方法及其方便之处. 在阅读本文之前,需要你对委托和事件的基本概念有所了解.如果你是初次接触 ...

  9. C#之委托与事件

    委托与事件 废话一堆:网上关于委托.事件的文章有很多,一千个哈姆雷特就有一千个莎士比亚,以下内容均是本人个人见解. 1. 委托 1.1 委托的使用 这一小章来学习一下怎么简单的使用委托,了解一些基本的 ...

随机推荐

  1. bzoj4557【JLOI2016】侦查守卫

    这道题对于我来说并不是特别简单,还可以. 更新一下blog 树形DP f[i][j]表示i的子树中,最高覆盖到i向下第j层的最小花费. g[i][j]表示i的子树全部覆盖,还能向上覆盖j层的最小花费. ...

  2. 支持向量机(五)SMO算法

    11 SMO优化算法(Sequential minimal optimization) SMO算法由Microsoft Research的John C. Platt在1998年提出,并成为最快的二次规 ...

  3. Excel更改单元格格式后无效

    问题描述: 比如修改了数据的自定义显示格式(日期显示 yyyy"年"m"月",手机号分段000-0000-0000),应用后发现只有部分生效,或者都不生效,再检 ...

  4. Linux 独立安装subversion-1.8.18

    一.所需软件包 1.apr-1.4.6.tar.gz 下载地址:http://apr.apache.org/   2.apr-util-1.4.1.tar.gz 下载地址:http://apr.apa ...

  5. 比较两个文件不同以及生成SQL插入语句

    Tips 做一个终身学习的人! 日拱一卒,功不唐捐. 今天有个小小的需求,具体需求是这样的: 有两个文本文件,每个文件都有一些字符串文本: 比较第一个文件中,在第二个文件中,不存在的字符串文本: 把这 ...

  6. Echarts数据可视化dataZoom,开发全解+完美注释

    全栈工程师开发手册 (作者:栾鹏) Echarts数据可视化开发代码注释全解 Echarts数据可视化开发参数配置全解 6大公共组件详解(点击进入): title详解. tooltip详解.toolb ...

  7. ActiveMQ——activemq的详细说明,queue、topic的区别(精选)

    JMS中定义了两种消息模型:点对点(point to point, queue)和发布/订阅(publish/subscribe,topic).主要区别就是是否能重复消费. 点对点:Queue,不可重 ...

  8. mac 环境下 Quantlib 使用Swig 转换到java

    一.Mac安装boost方法:http://blog.csdn.net/xujiezhige/article/details/8230493 二.Swig,这里使用sudo install swig ...

  9. cocos2dx - 节点管理

    接上一节内容:cocos2dx - v2.3.3编辑器骨骼动画 本节主要Cocos2dx中节点的管理及应用 一般用法 用过Cocos2dx应该都有用过addChild,removeChild方法.或者 ...

  10. This application failed to start because it could not find or load the Qt platform plugin "windows" 的问题原因以及解决方案

    1. 问题原因非常简单,经过各种百度,都没有找到解决方案,在此做一个记录备用. 2.原因就在于,项目目录使用了中文路径,然后出现了这个问题. 3.我是在使用 syncfusion 下的HTML 转PD ...