一、什么是委托

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

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

  用过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. 动手动脑:String.equals()的使用方法

    public class StringEquals { /** * @param args the command line arguments */ public static void main( ...

  2. Linux centos开机执行JAR Shell脚本

    Linux centos开机执行shell脚本 Linux centos开机执行 java  jar 1.编写jar执行脚本 vim start.sh 加入如下内容(根据自己真实路径与数据进行编写) ...

  3. 【HackerRank】Encryption

    One classic method for composing secret messages is called a square code.  The spaces are removed fr ...

  4. android studio Error:Unable to start the daemon process【转】

    本文转载自:https://blog.csdn.net/dhx20022889/article/details/44919905 我在用android studio 做一个小项目,在家里的mac电脑中 ...

  5. Spring中为什么实体类不用注入

    要理解为什么不用注入,首先就清楚注入的目的是什么?如果不注入,在程序中要使用某个类对象的方法,则需要去new一个对象.然后我们调用其中的方法,众所周知"程序=算法+数据".不失一般 ...

  6. MyEclipse Could not create the view: An unexpected exception was thrown解决方案

    问题:电脑突然断电,myeclipse非正常关闭,“Package Explorer”非正常显示,出现错误“Could not create the view: An unexpected excep ...

  7. win10打不开菜单且点击通知栏无反应的解决方法

    1.在键盘上按下win+R键,或在开始菜单图标上点击右键选择"运行" 2.输入powershell,按下“确定”运行 3.在窗口里输入或复制粘贴以下命令,注意只有一行: Get-A ...

  8. Oracle数据库类型总结

    RACLE基本数据类型(亦叫内置数据类型 built-in datatypes)可以按类型分为:字符串类型.数字类型.日期类型.LOB类型.LONG RAW& RAW类型.ROWID & ...

  9. 实时监测contenteditable(可编辑文档)的内容发生改变

    如果是文本框用onchange,oninput,onpropertychange都可以实时监控值发生变化,但是div设置了属性contenteditable(可编辑文档)就不管用了. 最简单的方法用o ...

  10. open inventor 学习笔记

    初次接触open inventor,跟以前学习一种新的东西,都想要先做出个小的helloworld程序,这个当然也不例外. 首先会创建一个窗口句柄,初始化一下场景数据库. HWND window = ...