您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > C/C++/C#

什么是C# 委托?这篇文章让你困惑全摆脱

时间:2019-08-16 09:45:29  来源:  作者:
C# 委托?这篇文章让你困惑全摆脱

作者 | 羽生结弦

责编 | 胡雪蕊

出品 | CSDN(CSDNnews)

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

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

 

1.零、委托类型和委托实例

 

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。这种用途叫做编写插件式方法,插件式方法就是只有在运行时才将方法赋值给委托。

 

2.多播委托

 

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


 

csharp

Delegate d = method1;

d += method2;

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


 

csharp

d -= method1;

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

方法一:


 

csharp

Delegate d = ;

d += method1;

方法二:


 

csharp

d = method1;

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

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


 

csharp

public delegate int DemoDelegate(int num);

static void Main(string[] args)

{

DemoDelegate dd = ;

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两个方法。

 

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

 

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

2. 一个静态方法赋值给委托对象时,Target 属性值为。例子如下:- 首先定义一个类 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;

}

}

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

C# 委托?这篇文章让你困惑全摆脱

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

 

4.泛型委托类型

 

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


 

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);

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

5.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 的优点了,我们不需要在外部显式的定义委托,比较方便。
 

6.冷知识

 

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】



Tags:   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
前言什么是数据脱敏数据脱敏是指对某些敏感信息通过脱敏规则进行数据的变形,实现敏感隐私数据的可靠保护常用脱敏规则替换、重排、加密、截断、掩码良好的数据脱敏实施1、尽...【详细内容】
2021-12-28  Tags:   点击:(3)  评论:(0)  加入收藏
河南最有名的“13碗面”,吃过10种以上的一定是地道河南人,你吃过几碗?河南位于黄河中下游,优越的地理位置和条件,让河南的种植业在全国脱颖而出,被称为全国的“粮仓”。小麦是河南...【详细内容】
2021-12-28  Tags:   点击:(3)  评论:(0)  加入收藏
在狗界中,有些狗狗比较凶残、霸道,今天我们就来说说被称为“犬中四煞”的4种狗,请认住它们的长相,看见了要绕路走! NO1:黑狼犬产地:中国寿命:11-12年黑狼犬是狼狗的一种,长大高大威猛...【详细内容】
2021-12-28  Tags:   点击:(3)  评论:(0)  加入收藏
协议下的体面离婚 2015年1月 方晴供职于一家外企,袁亮硕士毕业后开了家公司。两人相识、恋爱后走进婚姻殿堂。 方晴和袁亮的儿子小浩出生了。本该是其乐融融的三口之家,却在一...【详细内容】
2021-12-28  Tags:   点击:(2)  评论:(0)  加入收藏
中国人神话世界五千年到一万年之前到底是一个什么样的世界?相信这个问题应该是困扰了大家许久吧!其实这些问题可以从远古时代的三皇五帝开始说起,三皇五帝对于中国人的影响就如...【详细内容】
2021-12-28  Tags:   点击:(2)  评论:(0)  加入收藏
去年有个新闻,说的是一名印度女孩自小被欧洲有钱人家收养,长大后要回来给自己出生的村子捐钱做慈善。等她回村的时候,村里人专门为女孩修了一条路。表面上看,这貌似是个暖心的故...【详细内容】
2021-12-28  Tags:   点击:(3)  评论:(0)  加入收藏
日本在今年又给大家带来了一个巨大消息,日本著名的球星本田圭佑出资设立的一家公司,正式发售了飞行摩托车。 在之前可是在电视或者是电影中才能看到的,是具备了未来科幻的一个...【详细内容】
2021-12-28  Tags:   点击:(4)  评论:(0)  加入收藏
V社今日公布了2021年Steam最畅销游戏榜单,其中涵盖了本年度Steam上收入最高的100款游戏。为了得出每款游戏的总收入,Steam计算了2021年1月1日至2021年12月15日的游戏销售额、...【详细内容】
2021-12-28  Tags:   点击:(3)  评论:(0)  加入收藏
“都怪我一时糊涂铸下大错,这几年为了蒙混过关,拆东墙补西墙就怕被发现,我对不起信任我的领导同事,更对不起我的家人。”内蒙古某国有合资公司原出纳员包某在庭审现场听取公诉人...【详细内容】
2021-12-28  Tags:   点击:(2)  评论:(0)  加入收藏
2021年黄金价格下跌11.3%,黄金现在已经下跌了6.5%。白银价格一度下跌19.3%,白银现在已经下跌了15%。美元通胀。白银自2020年2月份以来,五家中央银行(Fed、欧洲中央银行、日本中...【详细内容】
2021-12-28  Tags:   点击:(3)  评论:(0)  加入收藏
▌简易百科推荐
一、简介很多时候我们都需要用到一些验证的方法,有时候需要用正则表达式校验数据时,往往需要到网上找很久,结果找到的还不是很符合自己想要的。所以我把自己整理的校验帮助类分...【详细内容】
2021-12-27  中年农码工    Tags:C#   点击:(2)  评论:(0)  加入收藏
引言在学习C语言或者其他编程语言的时候,我们编写的一个程序代码,基本都是在屏幕上打印出 hello world ,开始步入编程世(深)界(坑)的。C 语言版本的 hello world 代码:#include <std...【详细内容】
2021-12-21  一起学嵌入式    Tags:C 语言   点击:(11)  评论:(0)  加入收藏
读取SQLite数据库,就是读取一个路径\\192.168.100.**\position\db.sqlite下的文件<startup useLegacyV2RuntimeActivationPolicy="true"> <supportedRuntime version="v4.0"/...【详细内容】
2021-12-16  今朝我的奋斗    Tags:c#   点击:(21)  评论:(0)  加入收藏
什么是shellshell是c语言编写的程序,它在用户和操作系统之间架起了一座桥梁,用户可以通过这个桥梁访问操作系统内核服务。 它既是一种命令语言,同时也是一种程序设计语言,你可以...【详细内容】
2021-12-16  梦回故里归来    Tags:shell脚本   点击:(18)  评论:(0)  加入收藏
一、编程语言1.根据熟悉的语言,谈谈两种语言的区别?主要浅谈下C/C++和PHP语言的区别:1)PHP弱类型语言,一种脚本语言,对数据的类型不要求过多,较多的应用于Web应用开发,现在好多互...【详细内容】
2021-12-15  linux上的码农    Tags:c/c++   点击:(17)  评论:(0)  加入收藏
1.字符串数组+初始化char s1[]="array"; //字符数组char s2[6]="array"; //数组长度=字符串长度+1,因为字符串末尾会自动添&lsquo;\0&lsquo;printf("%s,%c\n",s1,s2[2]);...【详细内容】
2021-12-08  灯-灯灯    Tags:C语言   点击:(47)  评论:(0)  加入收藏
函数调用约定(Calling Convention),是一个重要的基础概念,用来规定调用者和被调用者是如何传递参数的,既调用者如何将参数按照什么样的规范传递给被调用者。在参数传递中,有两个很...【详细内容】
2021-11-30  小智雅汇    Tags:函数   点击:(19)  评论:(0)  加入收藏
一、问题提出问题:把m个苹果放入n个盘子中,允许有的盘子为空,共有多少种方法?注:5,1,1和1 5 1属同一种方法m,n均小于10二、算法分析设f(m,n) 为m个苹果,n个盘子的放法数目,则先对...【详细内容】
2021-11-17  C语言编程    Tags:C语言   点击:(49)  评论:(0)  加入收藏
一、为什么需要使用内存池在C/C++中我们通常使用malloc,free或new,delete来动态分配内存。一方面,因为这些函数涉及到了系统调用,所以频繁的调用必然会导致程序性能的损耗;另一...【详细内容】
2021-11-17  深度Linux    Tags:C++   点击:(38)  评论:(0)  加入收藏
OpenCV(Open Source Computer Vision Library)是一个(开源免费)发行的跨平台计算机视觉库,可以运行在Linux、Windows、Android、ios等操作系统上,它轻量级而且高效---由一系列...【详细内容】
2021-11-11  zls315    Tags:C#   点击:(50)  评论:(0)  加入收藏
相关文章
    无相关信息
最新更新
栏目热门
栏目头条