一、什么是委托

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

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

  用过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. AJAX实现弹窗显示详情,全选和批量删除

    以Nation表为例,将Nation表显示在页面上,每一行数据前面加上复选框,后面加上查看详情,点击以弹窗形式显示每一行的数据,并且在表格最后一行加上全选复选框,点击选中全部数据,后面跟一个批量删除按 ...

  2. PAT 天梯赛 L2-021. 点赞狂魔 【水】

    题目链接 https://www.patest.cn/contests/gplt/L2-021 题意 给出一个若干个人名,后面给出点赞的总数,以及每个赞的标签类型,输出前三个点赞狂魔,按标签类型不同数 ...

  3. PHP无限分类封装

    <?php /** +------------------------------------------------ * 通用的树型类 +--------------------------- ...

  4. Linux文件系统管理 文件系统

    概述 文件系统:文件系统这个名词大家都很陌生,不过如果说成分区,大家就比较容易理解了.原先每个分区只能格式化为一个文件系统,所以我们可以认为文件系统就是指分区.不过随着技术的进步,现在一个文件系统可以 ...

  5. 用C#编写猜数、九九乘法表‘、迷宫

    using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threa ...

  6. 面向过程编程实例------grep-rl 'root 路径

    #应用:grep -rl 'root' /etc import os def deco(func): def wrapper(*args): g=func(*args) next(g) return ...

  7. UVA11383 Golden Tiger Claw

    题目 UVA11383 Golden Tiger Claw 做法 \(KM\)好题啊,满足所有边\(l(x)+l(y)≥w(x,y)\)(个人理解,如不对请及时留言),这样能满足\(\sum\limi ...

  8. linux输入子系统简述【转】

    本文转载自:http://blog.csdn.net/xubin341719/article/details/7678035 1,linux输入子系统简述 其实驱动这部分大多还是转载别人的,linux ...

  9. Bootstrap3全局CSS样式

    目录 1. 概览 2. 栅栏系统 3. 文本 4. 列表 5. 代码 6. 表格 7. 表单 7.1 基本实例 7.2 内联表单 7.3 水平排列的表单 8. 按钮 9. 图片 10. 辅助类 10. ...

  10. 【转载】丑数humble numbers

    转载地址:http://blog.csdn.net/qwerty_xk/article/details/12749961 题:只有2 3 5 这三个因子的数,求第1500个   设1为第一个丑数,求第 ...