作者 | 羽生结弦

责编 | 胡雪蕊

出品 | CSDN(CSDNnews)

在C#中的委托关键字是 Delegate,委托类似于C/C++中函数的指针。是存有对某个方法引用的引用类型变量,可在运行时被改变。一般用于实现事件和回调方法。

注意:所有的委托都派生自 System.Delegate 类委托分为 委托类型和委托实例,下面分别进行讲解。

零、委托类型和委托实例

1. 委托类型 

委托类型定义了委托实例可以调用的方法、方法的返回类型和参数。我们可以通过委托类型的返回类型和参数来得知具体可以调用哪种方法。下面我们通过一个例子来看一下:

(1)首先我们定义一个委托类型:

csharp
delegate string DemoDelegate(int num);

(2)其次我们定义两个方法:

csharp
string IntToString(int num)
{
    return num.ToString();
}

int StringToInt(string num)
{
    return int.Parse(num);
}

我们来分析一下这两个代码段。首先我们定义了一个委托 DemoDelegate ,委托所定义的返回值类型是 string 类型,参数只包含一个,参数类型是 int。因此根据委托定义得知只有方法的返回值类型是 string 且参数只有一个,并且 参数类型是 int 时,委托才能调用。所以符合条件的方法就只有IntToString。

2. 委托实例

当把方法赋值给委托变量的时候就创建了委托实例。同样我们用一个例子来看一下:

csharp
static void Main(string[] args)
{
    DemoDelegate dd = IntToString;
    string num = dd(123);
    // 将输出 string 类型 "123"
    Console.WriteLine(num);
}

委托实例本质上就是调用者委托委托方法调用被调用者,在这里就是 Main 方法委托DemoDelegate 去调用 IntToString 方法。这样做的好处是调用者和被调用者的耦合度降低了。

小知识:上面的代码我们还可以这样写,这两种写法是等价的:

csharp
static void Main(string[] args)
{
    DemoDelegate dd = new DemoDelegate(IntToString);
    string num = dd.Invoke(123);
    // 将输出 string 类型 "123"
    Console.WriteLine(num);
}

委托的用途很多,我们这里来看一个例子,这个例子展示了委托其中一种的用途

csharp
public delegate int DemoDelegate(int num);

class Tool
{
    public static void IntSquare(int[] intArray, DemoDelegate dd)
    {
        for (int i = 0; i < intArray.Length; i++)
        {
            intArray[i] = dd(intArray[i]);
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        DemoDelegate dd = Square;
        int[] intArray = {2,4,6 };
        Tool.IntSquare(intArray, dd);
        for (int i = 0; i < intArray.Length; i++)
        {
            Console.WriteLine(intArray[i]);
        }

Console.Read();
    }

static int Square(int num)
    {
        return num * num;

}
}

我们将委托提取出来,作为一个公共的,然后定义一个 Tool 类,其中定义了一个计算数组中每个值的方法,这个方法接受两个参数,一个是int类型的数组,另一个是 DemoDelegate 委托类型的参数。通过委托调用 Program 类中的 Square 方法来计算数组中每个数字的平方值。我们在 Main方法中将 Square 方法赋值给委托变量,然后见数组和委托变量一同传入刚才我们定义的 Tool 类中的 IntSquare 方法,最后输出值为:4、16、36。这种用途叫做编写插件式方法,插件式方法就是只有在运行时才将方法赋值给委托。

多播委托

前面的例子我们都是讲一个方法赋值给委托变量,这种叫单播委托。但是在大部分情况下我们需要将多个方法赋值给委托,这是我们就用到了多播委托。要把多个方法赋值给委托变量,我们需要用到 +和 += ,方法如下:

csharp
Delegate d = method1;
d += method2;

当我们调用委托 d 的时候,就会按照赋值顺序来调用方法,即先调用 method1 再调用 method2 。我们有时候也需要移除委托中的某个方法,这时我们可以用 - 和 -= 进行操作,比如我们移除前面例子中的 method1 方法:

csharp
d -= method1;

当我们进行 + 或者 += 操作时,操作数可以是null,相当于把一个新值赋值给了委托变量,也就是说如下两种方法是等价的:

方法一:

csharp
Delegate d = null;
d += method1;

方法二:

csharp
d = method1;

同理,当进行 - 或者 -= 操作时,相当于把null值赋给了委托变量。

下面我们来看一下多播委托的例子:

csharp
public delegate int DemoDelegate(int num);
static void Main(string[] args)
{
    DemoDelegate dd = null;
    dd += Square;
    dd += Remainder;
    dd(5);
    Console.Read();
}

static int Square(int num)
{
    Console.WriteLine(num * num);
    return num * num;
}

static int Remainder(int num)
{
    Console.WriteLine(num % 2);
    return num % 2;
}

在代码中我们定义了两个方法,分别是计算数值平方的 Square 和计算数值除以2的余数Remainder 。在 Main 方法中我们利用 += 将两个方法赋值给委托变量 dd 。执行这段代码,最终输出结果为:25、1。当我们利用 - 或者 -= 来移除掉一个方法时,例如移除掉 Square ,这时就只会输出1,当我们把所有的方法都移除掉时,程序运行起来将会报空指针异常的错误。

注意:
1. 委托不可变,使用 **+=** 或者 **-=** 实际上是创建了新的委托实例,并把它付给当前的委托变量。
2. 如果多播委托的返回类型不是void,那么调用者只能获取到最后一个被调用方法的返回值,前面方法的返回值将会被抛弃。
3. c#会将 +、-、+=、-=编译为 Combine 和 Remove两个方法。

实例方法委托和静态方法委托

实例方法和静态方法都是c#中经常用到的方法,我们可以将这两种方法都赋值给委托,因此就出现了实例方法稳妥和静态方法委托。它们之间的区别如下:1. 一个实例方法被赋值给委托对象时,委托对象不仅要保留对方法的引用,还要保留方法所属实例的引用,这时 System.Delegate 中的Target 属性就表示的是方法所属的实例;

2. 一个静态方法赋值给委托对象时,Target 属性值为null。例子如下:- 首先定义一个类 Demo 里边包含 NumAdd 实例方法和 Num 静态方法

csharp
class Demo
{
    public int NumAdd(int num)
    {
        return ++num;
    }

public static int Num(int num)
    {
        return num;
    }
}

接着在控制台中调用这两个方法

csharp
public delegate int DemoDelegate(int num);
class Program
{
    static void Main(string[] args)
    {
        Demo demo = new Demo();
        DemoDelegate dd = demo.NumAdd;
        dd(2);
        Console.WriteLine("方法所属实例:"+dd.Target);
        Console.WriteLine("调用方法:"+dd.Method);

DemoDelegate staticDd = Demo.Num;
        staticDd(2);
        Console.WriteLine("方法所属实例:" + staticDd.Target);
        Console.WriteLine("调用方法:" + staticDd.Method);
        Console.ReadLine();
    }
}

运行以上代码,输出结果如下:

我们可以看到,将静态方法赋值给委托对象后打印方法所属实例为空。

泛型委托类型

在一些情况下我们不确定参数类型和返回值类型,这时我们就需要用到泛型委托类型,语法如下:

csharp
public delegate T DemoDelegate<T>(T arg);

我们具体看一下例子:

csharp
public delegate T DemoDelegate<T>(T num);

class Demo
{
    public int NumAdd(int num)
    {
        return ++num;
    }

}
class Program
{
    static void Main(string[] args)
    {
        Demo demo = new Demo();
        DemoDelegate<int> dd = demo.NumAdd;
        Console.WriteLine(dd(2));
        Console.ReadLine();
    }
}

运行上面的代码,控制台将会输出结果 3

注意:我们可以将返回值类型或者参数类型固定,例如:

csharp
public delegate string DemoDelegate<T>(T arg);
public delegate T DemoDelegate<T>(int arg);

使用泛型委托的好处是可以写出一组委托类型,这组方法可以拥有热议类型的返回值和任意数量的参数。下一小节我们就来看一下具体怎么用。

Action 和 Func

1. Func 

Func是一个具有返回类型的方法,它的类型参数数量可以多达16个,其中包括0到多个输入类型参数和一个输出类型参数。下面的代码段展示了Func部分类型参数:

csharp
delegate void Action();
delegate void Action<in T> (T t);
delegate void Action(in T1,in T2)(T t1,T t2);

2. Action 

Action 是一个不具有返回类型的方法,他的类型参数数量同样多达16个。下面展示了部分Action类型参数:

csharp
delegate void Action();
delegate void Action<in T> (T t);
delegate void Action(in T1,in T2)(T t1,T t2);

我们来看一下例子,以Func为例,Action同理

csharp
class Demo
{
    public void Num<T>(T[] array, Func<T, T> func)
    {
        for (int i = 0; i < array.Length; i++)
        {
            Console.WriteLine(func(array[i]));
        }
    }

}
class Program
{
    static void Main(string[] args)
    {
        Demo demo = new Demo();
        int[] array = new int[] {2,4,6 };
        demo.Num<int>(array, NumAdd);
        Console.ReadLine();
    }

static int NumAdd(int num)
    {
        return ++num;
    }
}

从代码中可以看出,我们将 Demo 类中的 Num 方法的第二个参数类型写成了 Func<T,T>,这里的意思是委托实例的返回类型和类型参数都是T类型。我们在Main函数中通过委托,控制台输出结果是3、5、7 。这时我们就看出了使用 Func 和 Action 的优点了,我们不需要在外部显式的定义委托,比较方便。

冷知识

1. 委托与接口

一般来说接口可以解决的问题,委托同样也可以解决,那么什么时候使用委托呢?我们来看一下:

(1)当需要多播的时候;

(2)订阅者需要多次实现接口的时候。

2. 委托兼容性

(1)委托类型

委托类型之间互不兼容,即使它们的签名一样也不行,也就是说如下的写法是错误的。

csharp
delegate void DD1();
delegate void DD2();
DD1 dd1=Method;
DD2 dd2=dd1;

(2)委托实例

如果委托实例具有相同的方法目标,那么委托实例就是相等的。

(3)参数

当调用一个方法时,提供的参数可以比方法参数更具体。例如被调用的方法参数是 Object类型,但是提供的参数是 String 类型,这时程序不会报错,因为string 来自 object,string 比 object 更具体。(委托只支持引用转换)

(4)返回值

同参数一样,当调用方法时,可以获得一个比被调用方法返回值更具体的返回值。

作者简介:朱钢,笔名羽生结弦,CSDN博客专家,.NET高级开发工程师,7年一线开发经验,参与过电子政务系统和AI客服系统的开发,以及互联网招聘网站的架构设计,目前就职于北京恒创融慧科技发展有限公司,从事企业级安全监控系统的开发。

【End】

2019年Python全栈工程师薪资一路攀高,都是开发人员改怎么转向高收入?

https://edu.csdn.net/topic/python115?utm_source=csdn_bw

5G进入元年,物联网发展愈加火爆!

你是否身怀绝技、却无人知晓;别让你的IoT项目再默默无闻了!

继第一届AI优秀案例评选活动之后,2019年案例评选活动再度升级,CSDN将评选出TOP 30优秀IoT案例,赶快扫码参与评选吧!重磅福利,等你来领!

热 文 推 荐 

☞雷军:小米推动山寨机灭亡;苹果回应禁止第三方换电池;Javascript 引擎 V8 7.7 发布 | 极客头条

从ACM班、百度到亚马逊,深度学习大牛李沐的开挂人生

Chrome 为何会成功?

《乐队的夏天》很酷?程序员式的摇滚才燃爆了!

Kubernetes要成为一个企业友好平台,到底还缺啥?

糟了,上网记录被 Python 扒下来了!

AI芯片技术“分叉”百害无利,国产芯片大有可为

☞比特币勒索出新招,佳能单反被黑客攻破,白帽却利用一个漏洞赎回了照片,这波操作简直了……

行!这下 CSDN 玩大了!粉丝:太良心

点击阅读原文,输入关键词,即可搜索您想要的 CSDN 文章。

你点的每个“在看”,我都认真当成了喜欢

C# 委托?这篇文章让你困惑全摆脱!相关推荐

  1. 流行于机器学习竞赛的Boosting,这篇文章讲的非常全了

    作者 | AISHWARYA SINGH 译者 | 武明利,责编 | Carol 出品 | AI科技大本营(ID:rgznai100) 你能说出至少两种机器学习中的 Boosting 吗? Boost ...

  2. 对张子阳先生对委托和事件的两篇文章的读后思考(说得很透,内附故事一篇)...

    第一篇 C#中的委托和事件 第二篇 C#中的委托和事件(续) 首先,张子阳先生的这是两篇关于委托和事件间关系的文章,是目前为止我读过的介绍委托和事件以及异步调用最简明清晰文章,作者通过非常有节奏的&q ...

  3. 设计模式的1000+篇文章总结

    设计模式的1000+篇文章总结 本文收集和总结了有关设计模式的1000+篇文章,由于篇幅有限只能总结近期的内容,想了解更多内容可以访问:https://www.ai2news.com/, 其分享了有关 ...

  4. 面试官问你MyBatis SQL是如何执行的?把这篇文章甩给他

    来自:Java建设者 初识 MyBatis MyBatis 是第一个支持自定义 SQL.存储过程和高级映射的类持久框架.MyBatis 消除了大部分 JDBC 的样板代码.手动设置参数以及检索结果.M ...

  5. 还搞不懂 Java NIO?快来读读这篇文章!

    来自:会点代码的大叔 首先,我们需要弄清楚几个概念:同步和异步,阻塞和非阻塞. 01 同步和异步 1. 同步 进程触发 IO 操作的时候,必须亲自处理: 比如你必须亲自去银行取钱. 2. 异步 进程触 ...

  6. 全文搜索引擎Elasticsearch,这篇文章给讲透了

    之前已经分享过Elasticsearch的使用和原理的知识,由于近期在公司内部做了一次分享,所以本篇主要是基于之前的博文的一个总结,希望通过这篇文章能让读者大致了解Elasticsearch是做什么的 ...

  7. java执行sql文件_面试官问你MyBatis SQL是如何执行的?把这篇文章甩给他

    初识 MyBatis MyBatis 是第一个支持自定义 SQL.存储过程和高级映射的类持久框架.MyBatis 消除了大部分 JDBC 的样板代码.手动设置参数以及检索结果.MyBatis 能够支持 ...

  8. 每个程序员都必读的10篇文章

    作为一名Java程序员和软件开发人员,那些每个程序员都应该知道的XXX的文章教会了我不少东西,它们提供了某个特定领域的一些实用的并且有深度的信息,这些东西通常很难找到.在我学习的过程中我读到过许多非常 ...

  9. java执行sql文件_面试官:MyBatis SQL是如何执行的?把这篇文章甩给他

    初识 MyBatis MyBatis 是第一个支持自定义 SQL.存储过程和高级映射的类持久框架.MyBatis 消除了大部分 JDBC 的样板代码.手动设置参数以及检索结果.MyBatis 能够支持 ...

最新文章

  1. 一九四六年首台电子计算机,2012年计算机一级MsOffice第四十九套练习题及答案解析...
  2. 比较好的电脑系统_win10电脑系统选择什么版本比较好
  3. Feign-实现抽取
  4. php debug用什么意思,phpdebug_backtrace()函数是干什么的?
  5. c语言字符数组不写,C语言数组
  6. c语言常用字符串处理函数6,【总结】C语言中常见的字符串处理函数
  7. 三因素方差分析_详解方差分析表(ANOVA)(一) —— 线性回归与矩阵代数.回顾
  8. 【转】J2ME开发环境的建立
  9. 区块链 hyperledger fabric的系统链码有哪些类型 LSCC ESCC VSCC CSCC是什么
  10. Postfix 故障记录
  11. 电脑中病毒了一直下载安装软件怎么办?
  12. [NOIP2016 提高组] 玩具谜题
  13. iOS可持续化集成: Jenkins + bundler + cocoapods + shenzhen + fastlane + pgyer
  14. 100行代码,10分钟,详解Vue2.x响应式原理——理解Observer,Dep,Watcher
  15. word中脚注和尾注的处理
  16. oracle查询语句 switch,ORACLE SQL语句中的“SWITCH语句”函数DECODE
  17. MAC系统重置root密码
  18. 手机屏幕按键测试软件,涨见识了——手机居然能作为维修检测工具来用!
  19. Fluent Mybatis 牛逼!
  20. QCC512x QCC302x 使用I2C驱动外设(Bitserial)

热门文章

  1. Sqoop导出模式——全量、增量insert、更新update的介绍以及脚本示例
  2. Scollector+Bosun+OpenTSDB的监控方案逻辑组网
  3. 《Webservice的应用与开发》学习笔记 ·002【XML进阶、XML Schema】
  4. 两台电脑网线传输文件教程
  5. 苹果手机软件升级密码_拥有苹果全家桶以后那些事
  6. Android使用Intent完成活动之间的通信
  7. The _imagingft C module is not installed
  8. 中国塑料汽车外饰件市场趋势报告、技术动态创新及市场预测
  9. 如何通过索引说数据库优化能力
  10. 搞机器学习还敲什么代码