委托是个说烂了的话题,但是依旧有好多人不知道为什么要在C#中使用委托,最近有朋友也问到我这个问题,所以举例些场景,以供那些知道怎么声明委托、怎么调用却不知道为什么要用的朋友一些参考,当然也是希望验证下自己的理解是否正确。

如何声明一个委托

委托使用关键字delegate,从外形上看和一个没有方法体的方法一样,只不过是多了个关键字。

    public delegate void MyDelegateHandler();//无返回值,无参数
public delegate int MyDelegateHandler1();//有返回值,无参数
public delegate object MyDelegateHandler2(string name);//有返回值,有参数
public delegate object MyDelegateHandler3(object first,ref int second,out float third, params object[] args);//有返回值,多个返回值

委托的声明可以放在类的外面,也可以在类的内部

    public delegate object MyDelegateHandler2(string name);//有返回值,有参数
static class Program
{
public delegate void MyDelegateHandler();//无返回值,无参数
}

C#内置的委托类型

在.NET Framework中定义了大量的委托类型,像什么WaitCallback、ParameterizedThreadStart、EventHandler、...,为了兼容旧版本所以一直没去掉,从.NET Framework 3.5后你可以使用Action、和Func(带返回值)来更简单的使用委托,他们都定义了大量的重载版本

委托的使用

下面是流年写的一个计算器类(比较简陋),里面有各种运算方法,每个方法只干一件事,恩,满足了单一原则,棒棒的!

    public class Calculator
{
public static int Add(int first, int second)
{
return first + second;
} public static int Sub(int first, int second)
{
return first - second;
}
}

流年很简单的就使用了这个类里的方法,哇,毫无压力

 var result = Calculator.Add(1, 6);

现在问题来了“在做运算前,需要去验证每个参数(假设这里希望使用的参数都是正整数)”,╮(╯▽╰)╭每个方法都需要去加一段代码

  public static int Add(int first, int second)
{
if (first <= 0 || second <= 0)
{
throw new ArgumentException("参数错误");
}
return first + second;
}

一个方法一个方法的去改,很是麻烦,干脆我把计算直接写一个方法里,三下五除二,流年开始啪,啪,啪...

  public static int Calc(int first, int second, string operater)
{
if (first <= 0 || second <= 0)
{
throw new ArgumentException("参数错误");
}
int result = 0;
if (operater.Equals("+"))
{
result = first + second;
}
if (operater.Equals("-"))
{
result = first - second;
}
return result;
}

OK,搞定,很简单嘛,但仔细一看,我靠Calc计算方法中干了那么多事情,又是加又是减的,如果计算器类还要添加对乘法的支持,还需要再修改这个方法,代码耦合度太高了,这不就违背了对扩展开放,对修改关闭的原则了嘛。就在这时,天空乌云密布,一道闪电击中了流年的脑袋...

首先我们来看,这段代码的变化点是什么?运算方法嘛

有什么办法可以隔离这种变化呢?委托嘛

 public static int Calc(int first, int second, Func<int, int, int> handler)
{
if (first <= 0 || second <= 0)
{
throw new ArgumentException("参数错误");
}
return handler.Invoke(first, second);//更简单的写法handler(first, second)
}
  Func<int, int, int> calcHandler = new Func<int, int, int>(Calculator.Add);
var result = Calculator.Calc(1, 6, calcHandler);

这样不用去改变原来的方法,Add还是Add,减法运算还是减法运算,就算再添加乘/除算法,直接添加加乘/除算法相关的方法就行,也不用去改动原来的代码了。而且算法选择逻辑也是交给的客户端,而不是方法内部。将变化隔离了出去。

更简单的使用委托

上面的委托实例声明好麻烦,那我们再改进改进,可以将方法直接赋值给委托实例

  Func<int, int, int> calcHandler = Calculator.Add;
var result = Calculator.Calc(1, 6, calcHandler);

再改进改进,直接将方法当做实参传递

 var result = Calculator.Calc(1, 6, Calculator.Add);

此刻,有没有一种想把委托按在床上的冲动。不要着急,这还只是前戏...

Lambda表达式

在上面我们提到可以将一个方法直接赋值给实例,那么是不是直接就可以将匿名方法直接赋值给委托实例呢?废话不说,直接试试就知道了

 Func<int, int, int> calcHandler = delegate (int first, int second)
{
return first - second;
};
var result = Calculator.Calc(1, 6, calcHandler);

这就完了?当然没有,让我们继续挑逗匿名方法

既然说,calcHandler 实例的引用是指向匿名方法的,那么是不是可以直接将匿名方法直接渗入Calculator.Calc方法的参数中

 var result = Calculator.Calc(1, 6,delegate (int x, int y){return x - y;});

基于匿名函数,从Visual Studio 2010开始,微软将匿名函数又升级成了Lambda表达式,代码是越来越简洁,连TMD方法都不用创建了,直接将算法写在调用上。

var result = Calculator.Calc(1, 6, (a, b) => a * b);

而且为了方便对集合类型的操作,微软还封装了大量的Linq扩展方法,这些都是基于委托实现的

 int[] numbers = { 11, 4, 3, 89, 5, 10 };
//获取集合中大于10的数字
var query = numbers.Where(w => w > 10);

提供异步调用

还是回到原来的代码,Calculator类中委托调用的地方handler.Invoke(first, second),如果说委托实例对应的方法是个耗时的操作,我想我们谁也不想直接同步调用,让程序傻傻的死在那里,至少给用户一些提示。为了处理这种问题,我们可以直接使用委托的异步调用

    public class Calculator
{
public static int Add(int first, int second)
{
Console.WriteLine($"Add Thread Id {Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(500);//模拟耗时的操作
return first + second;
} public static int Sub(int first, int second)
{
return first - second;
} public static int Calc(int first, int second, Func<int, int, int> handler)
{
Console.WriteLine($"Calc Thread Id {Thread.CurrentThread.ManagedThreadId}");
if (first <= 0 || second <= 0)
{
throw new ArgumentException("参数错误");
}
var ir = handler.BeginInvoke(first, second, null, null);
Console.WriteLine("还在计算当中...");
//等待计算结果
return handler.EndInvoke(ir);
}
}



最后一行代码handler.EndInvoke(ir),作用是等待异步调用返回结果。他会一直阻塞线程直到异步调用完成,然后返回计算的结果值。

委托异步调用方法的返回值为一个IAsyncResult接口,我们可以通过该接口的实例属性IsCompleted轮询判断异步是否调用完成。

在异步调用的方法参数中,有一个委托类型AsyncCallback,我们可以将一个函数传给他,异步方法执行完的时候会自动的去调用这个方法,这也就是所谓的回调函数。在回调函数中我们就可以干些别的事情,比如定义个事件将结果传递出去

  //定义一个计算完成事件
public static event Action<int> OnCalcCompelted;
   var ir = handler.BeginInvoke(first, second,
(o) =>
{
var result = handler.EndInvoke(o);
//通过事件通知注册用户计算已经完成,并将结果传递出去
if (OnCalcCompelted!=null)
{
OnCalcCompelted(result);
}
},
null);
Console.WriteLine("还在计算当中...");

结语:

方法与委托就好比普通类与接口(抽象类)的关系。

编码过程中委托并不一定是强制使用,他只不过是一种实现方式,在某些场景下比较合适,所以不要纠结于是要调用方法还是要通过委托调用,就像不懂设计模式也可以写代码完成功能,但是懂得这些套路之后你的代码会更加有条理,更具有扩展性,当然逼格也越高。但是,我觉得不用模式套路的代码逼格更高,谁都看不懂 O(∩_∩)O哈哈~

回到我们的Calculator类,如果需求是Calculator中只要实现加法运算,那TMD的谁还用委托,直接实现一个加法方法就行了,就这么简单。

涉及到委托的使用还不仅仅是这些,像什么事件、表达式树...每个都可以单独作为主题来讲,而且园子里也有很多讲解的文章。流年水平有限,就简单写到这里。

C#委托的一次"甜蜜"接触的更多相关文章

  1. C# 关于委托和事件的妙文:通过一个例子详细介绍委托和事件的作用;Observer模式简介

    委托和事件在 .Net Framework中的应用非常广泛,然而,较好地理解委托和事件对很多接触C#时间不长的人来说并不容易.它们就像是一道槛儿,过了这个槛的人,觉得真是太容易了,而没有过去的人每次见 ...

  2. c#事件与委托

    C#.net 目录(?)[-] 将方法作为方法的参数 将方法绑定到委托 事件的由来 事件和委托的编译代码 委托事件与Observer设计模式 范例说明 Observer设计模式简介 实现范例的Obse ...

  3. c#委托、事件、Observer

    委托和事件在.NET Framework[1] 中的应用非常广泛,然而,较好地理解委托和事件对很多接触C#时间不长的人来说并不容易. 中文名 委托 外文名 Delegate 编程语言 C# 作     ...

  4. c#关于委托和事件(二)(介绍的很详细)

    using System;using System.Collections.Generic;using System.Text; namespace Delegate {    // 热水器    p ...

  5. 《C#高级编程》学习笔记------C#中的事件和委托

    本文转载自张子阳 目录 委托的作用 将方法绑定到委托 事件的来由 Observer设计模式 .Net Framework中的委托与事件   引言 委托 和 事件在 .Net Framework中的应用 ...

  6. C#委托和事件讲解

    委托 和 事件在 .Net Framework中的应用非常广泛,然而,较好地理解委托和事件对很多接触C#时间不长的人来说并不容易.它们就像是一道槛儿,过了这个槛的人,觉得真是太容易了,而没有过去的人每 ...

  7. 通俗理解C#委托和事件

    引言 委托 和 事件在 .Net Framework中的应用非常广泛,然而,较好地理解委托和事件对很多接触C#时间不长的人来说并不容易.它们就像是一道槛儿,过了这个槛的人,觉得真是太容易了,而没有过去 ...

  8. C# 关于委托和事件的妙文

    C# 关于委托和事件的妙文: 通过一个例子详细介绍委托和事件的作用:Observer模式简介 转自:http://blog.csdn.net/susan19890313/article/details ...

  9. c#委托和事件(上)

    C# 中的委托和事件 引言 委托 和 事件在 .Net Framework中的应用非常广泛,然而,较好地理解委托和事件对很多接触C#时间不长的人来说并不容易.它们就像是一道槛儿,过了这个槛的人,觉得真 ...

随机推荐

  1. C#创建dll类库

    类库让我们的代码可复用,我们只需要在类库中声明变量一次,就能在接下来的过程中无数次地使用,而无需在每次使用前都要声明它.这样一来,就节省了我们的内存空间.而想要在类库添加什么类,还需取决于类库要实现哪 ...

  2. 【NLP】Python NLTK处理原始文本

    Python NLTK 处理原始文本 作者:白宁超 2016年11月8日22:45:44 摘要:NLTK是由宾夕法尼亚大学计算机和信息科学使用python语言实现的一种自然语言工具包,其收集的大量公开 ...

  3. 代码的坏味道(21)——中间人(Middle Man)

    坏味道--中间人(Middle Man) 特征 如果一个类的作用仅仅是指向另一个类的委托,为什么要存在呢? 问题原因 对象的基本特征之一就是封装:对外部世界隐藏其内部细节.封装往往伴随委托.但是人们可 ...

  4. 编译器开发系列--Ocelot语言7.中间代码

    Ocelot的中间代码是仿照国外编译器相关图书Modern Compiler Implementation 中所使用的名为Tree 的中间代码设计的.顾名思义,Tree 是一种树形结构,其特征是简单, ...

  5. Android之DOM解析XML

    一.DOM解析方法介绍 DOM是基于树形结构的节点或信息片段的集合,允许开发人员使用DOM API遍历XML树,检索所需数据.分析该结构通常需要加载整个文档和构造树形结构,然后才可以检索和更新节点信息 ...

  6. Android开发学习—— shape标签的使用

    参考这片文章http://www.cnblogs.com/armyfai/p/5912414.html

  7. Java实现FTP文件与文件夹的上传和下载

    Java实现FTP文件与文件夹的上传和下载 FTP 是File Transfer Protocol(文件传输协议)的英文简称,而中文简称为"文传协议".用于Internet上的控制 ...

  8. NOIP2016纪录[那些我所追求的]

    人生第一场正式OI [序] 2016-12-04 见底部 [Day -1] 2016-11-17 期中考试无心插柳柳成荫,考了全市第2班里第1(还不是因为只复习了不到两天考试),马上请了一个周的假准备 ...

  9. embedding mono实战笔录(一)

    最近在给自己的服务器节点添加脚本功能,考虑到 执行性能.开发效率.调试效率.可维护性.严谨性 五大要素,最终选用C#作为脚本语言,并使用mono作为中间层,使其具备跨平台特性,以备具有在Windows ...

  10. [源码]Literacy 快速反射读写对象属性,字段

    Literacy 说明 Literacy使用IL指令生成方法委托,性能方面,在调用次数达到一定量的时候比反射高很多 当然,用IL指令生成一个方法也是有时间消耗的,所以在只使用一次或少数几次的情况,不但 ...