一、什么是委托

  简单说它就是一个能把方法当参数传递的对象,而且还知道怎么调用这个方法,同时也是粒度更小的“接口”(约束了指向方法的签名)

  委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递,是种将方法动态地赋给参数的做法。

  用过C/C++的,对委托不会陌生,委托可以看成函数指针的升级版本!

  函数指针简介:

  下面是一段C程序,Calc就是定义的函数指针。

typedef int (* Calc)(int a, int b);

int Add(int a, int b)
{
int result = a + b;
return result;
}
main()
{
int x = ; int y = ; int z = ; Calc funcPoint1 = &Add; z = funcPoint1(x, y); printf("%d \n", z);
}

这段程序很好的体现了一切皆地址的思想,变量和函数都是地址。

直接调用和间接调用的效果是一致的,都是访问那个内存地址,委托相当于函数指针的升级版。

  委托的简单案例

  一个委托类型定义了该类型的实例能调用的一类方法,这些方法含有同样的返回类型和同样参数(类型和个数相同)。

委托是一个类,所以要在类声明的位置进行声明,而不是写在类里面,那样就写成嵌套类了。如下定义了一个委托类型 - Calculator:

delegate int Calculator (int x);

此委托适用于任何有着int返回类型和一个int类型参数的方法,如:

static int Double (int x) { return x * 2; }

创建一个委托实例,将该此方法赋值给该委托实例:

Calculator c = new Calculator(Double);

也可以简写成:

Calculator c = Double;

这个方法可以通过委托调用:

int result = c(2);

下面是完整代码:

delegate int Calculator(int x);

class Program {
static int Double(int x) { return x * ; }
static void Main(string[] args) {
Calculator c = Double;
    //c 就是委托实例,
int result = c();
Console.Write(result);
Console.ReadKey();
}
}

二、委托的一般使用

2.1用委托实现插件式编程

  我们可以利用“委托是一个能把方法作为参数传递的对象”这一特点,来实现一种插件式编程。

  例如,我们有一个Utility类,这个类实现一个通用方法(Calculate),用来执行任何有一个整型参数和整型返回值的方法。这样说有点抽象,下面来看一个例子:

delegate int Calculator(int x);

//这里定义了一个委托
class Program {
static int Double(int x) { return x * ; }
static void Main(string[] args) {
int[] values = { ,,,};
Utility.Calculate(values, Double);
foreach (int i in values)
Console.Write(i + " "); // 2 4 6 8
Console.ReadKey();
}
} class Utility {
public static void Calculate(int[] values, Calculator c) {
// Calculator c 是简单委托的变种写法,就是把实例化放在了形参定义的语句里
//但是这个实例化具体对应的是什么方法,只有真的传入参数的时候才知道!
for (int i = ; i < values.Length; i++)
values[i] = c(values[i]);
}
}

  这个例子中的Utility是固定不变的,程序实现了整数的Double功能。我们可以把这个Double方法看作是一个插件,如果将来还要实现诸如求平方、求立方的计算,我们只需向程序中不断添加插件就可以了。

  如果Double方法是临时的,只调用一次,若在整个程序中不会有第二次调用,那么我们可以在Main方法中更简洁更灵活的使用这种插件式编程,无需先定义方法,使用λ表达式即可,如:

...

Utility.Calculate(values, x => x * 2);

...

2.2多播委托

一个委托实例不仅可以指向一个方法,还可以指向多个方法。例如:

MyDelegate d = MyMethod1;
// “+=” 用来添加,同理“-=”用来移除。
d += MyMethod2;
// d -= MyMethod2

调用时,按照方法被添加的顺序依次执行。注意,对于委托,+= 和 -= 对null是不会报错的,如:

MyDelegate d;
d += MyMethod1;// 相当于MyDelegate d = MyMethod1;

  为了更好的理解多播在实际开发中的应用,我用模拟瞬聘网的职位匹配小工具来做示例。在职位匹配过程中会有一段处理时间,所以在执行匹配的时候要能看到执行的进度,而且还要把执行的进度和执行情况写到日志文件中。在处理完一个步骤时,将分别执行两个方法来显示和记录执行进度。

  我们先定义一个委托(ProgressReporter),然后定义一个匹配方法(Match)来执行该委托中的所有方法。如下:

public delegate void ProgressReporter(int percentComplete);
public class Utility {
public static void Match(ProgressReporter p) {
if (p != null) {
for (int i = ; i <= ; i++) {
p(i * );
System.Threading.Thread.Sleep();
//线程暂停0.1s之后再继续运行程序!
}
}
}
}

然后我们需要两个监视进度的方法,一个把进度写到Console,另一个把进度写到文件。如下:

class Program {
static void Main(string[] args) {
ProgressReporter p = WriteProgressToConsole;
p += WriteProgressToFile;
Utility.Match(p);
Console.WriteLine("Done.");
Console.ReadKey();
}
static void WriteProgressToConsole(int percentComplete) {
Console.WriteLine(percentComplete+"%");
}
static void WriteProgressToFile(int percentComplete) {
System.IO.File.AppendAllText("progress.txt", percentComplete + "%");
} }

运行结果:

看到这里,是不是发现你已然更加爱上C#了。

2.3静态方法和实例方法对于委托的区别

  当一个类的实例的方法被赋给一个委托对象时,在上下文中不仅要维护这个方法,还要维护这个方法所在的实例。System.Delegate 类的Target属性指向的就是这个实例。(也就是要在内存中维护这个实例,也就是可能的内存泄漏)

  但对于静态方法,System.Delegate 类的Target属性是Null,所以将静态方法赋值给委托时性能更优。

2.4泛型委托

如果你知道泛型,那么就很容易理解泛型委托,说白了就是含有泛型参数的委托,例如:

public delegate T Calculator<T> (T arg);

我们可以把前面的例子改成泛型的例子,如下:

public delegate T Calculator<T>(T arg);
class Program {
static int Double(int x) { return x * ; }
static void Main(string[] args) {
int[] values = { , , , };
Utility.Calculate(values, Double);
foreach (int i in values)
Console.Write(i + " "); // 2 4 6 8
Console.ReadKey();
}
}
class Utility {
public static void Calculate<T>(T[] values, Calculator<T> c) {
for (int i = ; i < values.Length; i++)
values[i] = c(values[i]);
}
}

2.5Func 和 Action 委托

有了泛型委托,就有了能适用于任何返回类型和任意参数(类型和合理的个数)的通用委托,Func 和 Action。如下所示(下面的in表示参数,out表示返回结果):

delegate TResult Func <out TResult> ();

delegate TResult Func <in T, out TResult> (T arg);

delegate TResult Func <in T1, in T2, out TResult> (T1 arg1, T2 arg2);

... 一直到 T16

delegate void Action ();

delegate void Action <in T> (T arg);

delegate void Action <in T1, in T2> (T1 arg1, T2 arg2);

... 一直到 T16

有了这样的通用委托,我们上面的Calculator泛型委托就可以删掉了,示例就可以更简洁了:

public static void Calculate<T>(T[] values, Func<T,T> c) {
for (int i = ; i < values.Length; i++)
values[i] = c(values[i]);
}
//Func 是对delegate的一种简写,更简洁

Func 和 Action 委托,除了ref参数和out参数,基本上能适用于任何泛型委托的场景,非常好用。ACTION 和FUNC 最常用的两种委托,类库为我们准备好的!

  action就是一种委托的简便写法,默认的是无返回值类型的方法,注意不要加括号,只是绑定地址,而不是执行!Func这种用来调用有返回值的委托!

直接调用方法,使用calculator.report();

间接调用,使用action.Invoke();

action();这种写法是为了模仿函数指针的写法。实际上还是默认调用invoke()

上面的案例写的有些乱,而且是一种把func写入函数参数的类型,不容易理解,下面再用另一个案例

static void Main(string[] args)
{
Func<string> RetBook = new Func<string>(FuncBook);
Console.WriteLine(RetBook());
}
public static string FuncBook()
{
return "送书来了";
}

无返回值

static void Main(string[] args)
{
Action<string> BookAction = new Action<string>(Book);
BookAction("百年孤独");
}
public static void Book(string BookName)
{
Console.WriteLine("我是买书的是:{0}",BookName);
}

2.6委托的异步调用

1、显式异步调用

显式异步调用 thread  或者 task

2、隐式异步调用

使用委托进行隐式异步调用,begininvoke就是隐式异步调用,它会开发分支线程,他有两个参数。

aciont1.BeginInvoke(null, null);

EndInvoke

隐式调用也有两种,一种是不使用回调函数的,另一种是使用的。

不使用回调函数

namespace delegate3
{
class Program
{
//public delegate int AddHandler(int a, int b);
public class Cal
{
public static int Add(int a, int b)
{
Console.WriteLine("开始计算:" + a + "+" + b);
Thread.Sleep(); //模拟该方法运行三秒
Console.WriteLine("计算完成!");
return a + b;
}
}
static void Main(string[] args)
{
Console.WriteLine("===== 异步调用 AsyncInvokeTest =====");
//AddHandler handler = new AddHandler(Cal.Add);
//IAsyncResult: 异步操作接口(interface)
//BeginInvoke: 委托(delegate)的一个异步方法的开始
//IAsyncResult result = handler.BeginInvoke(1, 2, null, null);
Console.WriteLine("继续做别的事情。。。"); Func<int,int,int> RetBook = new Func<int,int,int>(Cal.Add);
//RetBook.BeginInvoke(1, 2);
IAsyncResult result = RetBook.BeginInvoke(, , null, null);
//异步操作返回
Console.WriteLine(RetBook.EndInvoke(result));
Console.ReadKey();
}
}
}

使用回调函数

namespace CallBack
{
public delegate int AddHandler(int a, int b);
public class Cal
{
public static int Add(int a, int b)
{
Console.WriteLine("开始计算:" + a + "+" + b);
Thread.Sleep(); //模拟该方法运行三秒
Console.WriteLine("计算完成!");
return a + b;
}
}
class Program
{
static void Main()
{
Console.WriteLine("===== 异步回调 AsyncInvokeTest =====");
AddHandler handler = new AddHandler(Cal.Add);
//异步操作接口(注意BeginInvoke方法的不同!)
IAsyncResult result = handler.BeginInvoke(, , new AsyncCallback(回调函数), "AsycState:OK");
Console.WriteLine("继续做别的事情。。。");
Console.ReadKey();
}
static void 回调函数(IAsyncResult result)
{
//result 是“加法类.Add()方法”的返回值
//AsyncResult 是IAsyncResult接口的一个实现类,空间:System.Runtime.Remoting.Messaging
//AsyncDelegate 属性可以强制转换为用户定义的委托的实际类。
AddHandler handler = (AddHandler)((AsyncResult)result).AsyncDelegate;
Console.WriteLine(handler.EndInvoke(result));
Console.WriteLine(result.AsyncState);
}
}
}

三、委托的缺点

引用了某个方法,那么这个方法在内存中就不能释放了,一旦释放,委托就不能调用这个方法,所以委托有可能造成内存泄漏。(静态方法不存在这个问题)

C#进阶之路(一):委托的更多相关文章

  1. JavaScript进阶之路(一)初学者的开始

    一:写在前面的问题和话 一个javascript初学者的进阶之路! 背景:3年后端(ASP.NET)工作经验,javascript水平一般般,前端水平一般般.学习资料:犀牛书. 如有误导,或者错误的地 ...

  2. OpenCV进阶之路:神经网络识别车牌字符

    1. 关于OpenCV进阶之路 前段时间写过一些关于OpenCV基础知识方面的系列文章,主要内容是面向OpenCV初学者,介绍OpenCV中一些常用的函数的接口和调用方法,相关的内容在OpenCV的手 ...

  3. MVC进阶之路:依赖注入(Di)和Ninject

    MVC进阶之路:依赖注入(Di)和Ninject 0X1 什么是依赖注入 依赖注入(Dependency Injection),是这样一个过程:某客户类只依赖于服务类的一个接口,而不依赖于具体服务类, ...

  4. 【SSH进阶之路】Hibernate映射——多对一单向关联映射(四)

    [SSH进阶之路]Hibernate基本原理(一) ,小编介绍了Hibernate的基本原理以及它的核心,採用对象化的思维操作关系型数据库. [SSH进阶之路]Hibernate搭建开发环境+简单实例 ...

  5. 【SSH进阶之路】一步步重构容器实现Spring框架——彻底封装,实现简单灵活的Spring框架(十一)

    文件夹      [SSH进阶之路]一步步重构容器实现Spring框架--从一个简单的容器開始(八)      [SSH进阶之路]一步步重构容器实现Spring框架--解决容器对组件的"侵入 ...

  6. 2017PHP程序员的进阶之路

    2017PHP程序员的进阶之路 又是一年毕业季,可能会有好多毕业生即将进入开发这个圈子,踏上码农这个不归路.根据这些年在开发圈子总结的LNMP程序猿发展轨迹,结合个人经验体会,总结出很多程序员对未来的 ...

  7. 浅谈Android进阶之路

    过去十年是移动互联网蓬勃发展的黄金期,相信每个人也都享受到了移动互联网红利,在此期间,移动互联网经历了曙光期.成长期.成熟期.现在来说已经进入饱和期.依然记得在 2010-2013 年期间,从事移动开 ...

  8. 处女作《Web全栈开发进阶之路》出版了!

    书中源码下载地址:https://github.com/qinggee/WebAdvanced 01. 当初决定写博客的原因非常的纯洁:只要每个月写上 4 篇以上博客,月底的绩效奖金就多 500 块. ...

  9. Android研发进阶之路

    前言 移动研发火热不停,越来越多人开始学习android开发.但很多人感觉入门容易成长很难,对未来比较迷茫,不知道自己技能该怎么提升,到达下一阶段需要补充哪些内容.市面上也多是谈论知识图谱,缺少体系和 ...

  10. GO语言的进阶之路-初探GO语言

    GO语言的进阶之路-初探GO语言 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.为什么我们需要一门新语言 Go语言官方自称,之所以开发Go 语言,是因为“近10年来开发程序之难 ...

随机推荐

  1. Linux centos7 防火墙设置

    1.查看防火墙状态 systemctl list-unit-files|grep firewalld.service 或 systemctl status firewalld.service 2.开启 ...

  2. Linux软件包管理 RMP包管理

    概述 RPM 包的命名一般都会遵守统一的命名规则,例如: httpd-2.2.15-15.el6.centos.1.i686.rpm 其中的各项代表的含义如下: httpd:软件包名. 2.2.15: ...

  3. 【CodeChef】Turbo Sort

    题目链接:Turbo Sort 用java自带O(NlogN)的排序就可以,java要特别注意输入输出.输入用BufferedReader,输出用printWriter.printWriter的速度比 ...

  4. DNS 介绍

    DNS 介绍 DNS 为 Domain Name System (域名系统的缩写),它是一种将ip地址转换为对应的主机名或将主机名转换成与之对应的ip地址的一种服务机制.DNS使用TCP和UDP,端口 ...

  5. LVS/NAT 配置

    LVS/NAT 配置 实验环境 三台主机:Linux Centos 6.4 32位 调度器Director:192.168.1.160(内网IP).192.168.2.20(公网IP) HTTP真实服 ...

  6. jQuery带闹钟的数字时钟

    在线演示 本地下载

  7. 20145230《java程序设计》第三次试验报告

    20145208 实验三 Java面向对象程序设计 实验内容 实验内容 初步掌握单元测试和TDD 理解并掌握面向对象三要素:封装.继承.多态 初步掌握UML建模 熟悉S.O.L.I.D原则 了解设计模 ...

  8. Ubuntu 15.10环境下安装Hive

    1. 安装MySQL sudo tar -xzvf mysql-5.7.13-linux-glibc2.5-x86_64.tar.gz sudo mv mysql-5.7.13-linux-glibc ...

  9. QGIS3.0.3+Qt5.9+VS2015_x64编译

    QGIS3.0.3+Qt5.9+VS2015_x64编译 参考:https://blog.csdn.net/u010670734/article/details/80241615 https://ww ...

  10. [转载]spring security 的 logout 功能

    原文地址:security 的 logout 功能">spring security 的 logout 功能作者:sumnny 转载自:http://lengyun3566.iteye ...