C#中的委托数据类型简介

 更新时间:2020年6月25日 11:25  点击:1444

什么是委托?

委托是一个类型安全的对象,它指向程序中另一个以后会被调用的方法(或多个方法)。通俗的说,委托是一个可以引用方法的对象,当创建一个委托,也就创建一个引用方法的对象,进而就可以调用那个方法,即委托可以调用它所指的方法。

来看下面的例子,类deleMthod定义了3个方法,add、minus和multi,他们都具有相同的输入参数列表(int x,int y)和输出参数类型int,那么我们就说这三个方法具有相同的方法签名。开发者可以抽象地用 int 某名称(int x,int y) 的一种类型对方法进行封装,在c#中这种抽象的数据类型叫委托,针对上述的几个方法我们可以定义委托 : public delegate int Handler(int x ,int y),public 是一个访问修饰符,delegate关键字表示这是一个委托,int Hander(int x,int y)表示这个委托的名称。

class deleMethod
{
public int add(int x, int y)
{
return x + y;
}
public int minus(int x, int y)
{
return x - y;
}
public int multi(int x, int y)
{
return x * y;
}
}

怎么使用委托

使用委托大体有四个步骤:

•定义一个委托,上节已经提及。

•定义委托方法,上节deleMethod类中add、minus、multi都是委托方法,定义的目的就是为了使用它,讲专业点就是为了方法的调用

•委托变量及赋值,和类型一样,在使用前需要对变量赋值。

•委托变量的使用。

怎样定义委托变量,还是接着上面的例子。我们已经定义了一个委托类型 public delegate int Handler(int x,int y),和c#语法规范一样定义一个变量并赋值语法是:“类型名 变量名 = new 类型名(方法);”,如上例

“Handler deleCall = new Handler(方法名);“,在.net2.0后对于委托的实例化可以简化为” Handler deleCall = 方法名;“。

委托变量的使用,也就是对委托方法的调用。其语法是”int result1 = deleCall(10,20);“或者使用委托调用方法 Invoke,“int result2 = deleCall.Invoke(10,20);”。

具体如何使用可以看看下面的示例:

class Program
{
public delegate int Handler(int x, int y); //---定义委托的类型,可以将委托看成一种特殊的数据类型
static void Main(string[] args)
{ 
deleMethod dm = new deleMethod(); //---实例化包含委托方法的类型
Handler deleCall = new Handler(dm.add); //---定义委托变量delCall,并出示化赋值
int result1 = deleCall(10, 20); //---实例方法的调用invoke
Console.WriteLine("the add resutl is:{0}", result1);
deleCall = dm.minus;
int result2 = deleCall.Invoke(12, 6);
Console.WriteLine("the minus result is:{0}", result2);
Console.ReadLine();
}
} 

如上例所示,定义一个简单的加、减功能如此的复杂,搅来搅去让人头,真是无语,难怪很多朋友谈委托色变晕。在实际使用的过程中,c#还是有很多方式帮我们简化代码。

简化委托

预定义的泛型委托

c#系统最常见的预定义的委托类型有三种,Func<>委托、Action<>委托、Predicate<>委托,Func<>委托是一个有返回值的委托,输入参数可以多达16个;而Action<>委托是一个没有返回值的委托,它的输入参数也可以多达16个;而Predicate<>是一个具有bool返回类型的委托,它只运行一个输入参数。对于有上例的委托类型,我们可以使用预定义的委托类型Fun<int,int,int>来代替,省去我们自己定义一个什么鬼东西 public delegate int Handler(int x,int y)类型,其代码其实可以简化为如下例所示:

namespace DelegateDemo1
{
class Program
{
static void Main(string[] args)
{
deleMethod dm = new deleMethod();
Func<int, int, int> fun = dm.add; //---使用预定义的委托类型Func<>
int result4 = fun(8, 10);
Func<int, int, int> fun1 = dm.minus;
int result5 = fun1(12, 8);
Console.WriteLine("预定义的委托输出{0},{1}", result4, result5);
Console.ReadLine();
}
}
class deleMethod
{
public int add(int x, int y)
{
return x + y;
}
public int minus(int x, int y)
{
return x - y;
}
public int multi(int x, int y)
{
return x * y;
}
}
}

我把委托的方法定义和委托的调用放在一起看,是不是比原先自己定义的一个委托类型简单方便一些?但是这样使用委托还是不怎么清爽,估计在实际应用中很少人会怎么写代码,太不方便了。

匿名委托

当委托实例的调用和委托方法的定义分开处理,码农们在读程序代码的时候需要来回的去找委托方法去匹配委托变量,看看参数列表和返回值是否正确,这样的程序代码的可读性很差。其实c#还是有方法让我们简化代码:那就是匿名委托,将方法体直接在委托的实例化时给出,之所以叫匿名委托就是再定义委托的时候省略掉委托的名称,它的定义语法是delegate(参数1,参数2) 后面直接就给出方法体,用大括号将方法体括起。刚看起来比较怪异,接触多了也就习惯了,莫有办法只能去适应c#的语法规范。话说多了是水,还不如看代码来得直接。

namespace DelegateDemo1
{
class Program
{
static void Main(string[] args)
{
Func<int, int, int> fun = delegate(int x, int y) { return x + y; };
Func<int, int, int> fun1 = delegate(int x, int y) { return x - y; };
int result4 = fun(8, 10);
int result5 = fun1(12, 8);
Console.WriteLine("预定义的委托输出{0},{1}", result4, result5);
Console.ReadLine();
}
}
} 

看看是不是在原来的基础上大幅度减少了代码量,肿么办,是否代码量已经减少到极致了?

lambda表达式

其实对于委托的定义还可以进一步简化,那就是使用lambda表达式,lambda表达式的定义是(参数列表)=>{方法体},=>读作goes to。lambda表达式对参数列表和方法表达式的精简达到极致,对于上面的例子,用λ表达式可以省略掉匿名委托的关键字和参数类型,系统可以进行类型推断,不影响运行,其简化的代码如下。对.net 1.0,2.0最传统的委托定义和使用,是一个巨大的简化,它剔除了所有多余的语句达到极致。

namespace DelegateDemo1
{
class Program
{
static void Main(string[] args)
{
Func<int, int, int> fun = (x, y) => x + y;
Func<int, int, int> fun1 = (x, y) => x - y; 
int result4 = fun(8, 10);
int result5 = fun1(12, 8);
Console.WriteLine("预定义的委托输出{0},{1}", result4, result5);
Console.ReadLine();
}
}
} 

委托链

前面讲过,委托在本质上仍然是一个类,我们用delegate关键字声明的所有委托都继承自System.MulticastDelegate。后者又是继承自System.Delegate类,System.Delegate类则继承自System.Object。一个委托可以绑定若干相同签名的方法形成一个委托链,委托链是一个委托实例的集合,它允许我们调用这个集合中的委托实例所代表的所有方法(对于有返回值的方法,委托链的返回值为链表中最后一个方法的返回值),在下面的例子我们定义的委托方法都没有返回值。我们可以用 GetInvocationList()方法获取委托链。

class Program
{
static void Main(string[] args)
{
//Action 表示没有返回值的一类方法
Action<int, int> actionA = (x, y) =>
{
Console.WriteLine("x是{0},y是{1},他们的平方和是{2}", x, y, x * x + y * y);
};
actionA += (x, y) =>
{
Console.WriteLine("x是{0},y是{1},他们的平方差是{2}", x, y, x * x - y * y);
};
actionA(10, 5); 
foreach (var item in actionA.GetInvocationList())
Console.WriteLine(item.Method);
Console.ReadLine();
}
} 

什么是事件

经常看到一种定义是:事件是一种特殊的委托,是对委托的封装。其实这种定义是很不严谨的。委托是一种数据类型,但是事件只是委托的实例,不能算是一种数据类型,所以说事件是一种特殊的委托是不准确的。如果这样定义:事件是一种特殊的委托实例,是对委托的封装。那么在C#中事件是如何定义并被使用的呢?其实事件从定义到使用要经过四个阶段。

•定义事件依赖的委托,并定义事件以及引发事件的方法,该步骤可以定义在事件发布器类中

•定义事件所依赖的事件方法,该步骤可以定义在事件订阅者类中

•如果有事件参数,该步骤可以定义在事件参数类中

•注册事件并引发事件,该步骤一般写在主程序中

一般来讲在事件定义过程中,各个方法的命名没有统一的标准,但我们可以参考微软在c#中的命名规范进行命名

建议事件委托命名为:事件+EventHandler。如果有额外的参数传递需定义自己的参数类型,参数类型的命名规范 :事件+EventHandler。比如,可以定义一个事件委托 public delegate void calcEventHandler(object sender,calcEventArgs e); 。

定义一个事件变量如:public event calcEventHandler calc;

定义一个引发事件的方法如:public void onCalc(object sender,calcEventArgs e){}

建议方法名为 on+事件,就如同我们在开发web程序时,绑定的事件名有onClick,onLoad等一样。

参考看下面的例子,了解定义一个事件的一般过程,如果不需要传递事件的参数可以省去事件参数类的定义,使用系统预定义的EventArgs就可以了。

/// <summary>
/// 主程序类
/// </summary>
class Program
{
static void Main(string[] args)
{
eventPublish myEvent = new eventPublish();
eventSubscription myMethod = new eventSubscription();
//绑定方法 add 和 subtract,又称为对事件方法的注册 -=称为对事件方法的注销
myEvent.calc += myMethod.add;
myEvent.calc += myMethod.substract;
while (true)
{
try
{
Console.WriteLine("请输入第一个整数数");
int numA = Convert.ToInt16(Console.ReadLine());
Console.WriteLine("请输入第二个整数");
int numB = Convert.ToInt16(Console.ReadLine());
calcEventArgs e = new calcEventArgs(numA, numB);
//在本例不需要sender参数,随意传递了字符串"sender"
myEvent.onCalc("sender", e); 
}
catch (Exception ex)
{
Console.WriteLine("出现错误," + ex.Message);
}
}
}
}
/// <summary>
/// 定义一个事件发布器类
/// </summary>
class eventPublish
{
//定义一个委托类型,委托名一般是 事件变量名+EventHandler
public delegate void calcEventHander(object sender,calcEventArgs e);
//定义一个事件变量,其变量名为calc
public event calcEventHander calc;
//封装事件,对外定义了引发事件的方法,定义的引发方法名一般是 on+事件变量名
public void onCalc(object sender, calcEventArgs e)
{
if (calc != null)
calc(sender,e);
}
}
/// <summary>
/// 定义一个事件订阅者类(事件方法类)
/// </summary>
class eventSubscription
{
public void add(object sender, calcEventArgs e)
{
Console.WriteLine("两数相加等于{0}", e.X + e.Y );
}
public void substract(object sender, calcEventArgs e)
{
Console.WriteLine("两数相减等于{0}", e.X - e.Y);
}
}
/// <summary>
/// 定义一个事件参数类
/// </summary>
class calcEventArgs : EventArgs
{
private int _x;
private int _y;
public calcEventArgs(int x, int y)
{
this._x = x;
this._y = y;
}
public int X
{
get { return _x; }
}
public int Y
{
get { return _y; }
}
}

我们将事件是对委托的封装,如果用ILDAS反编译可执行文件查看中间代码就可以非常明了的看出事件运行机制


如上图所示,事件calc,经.net运行时编译为中间语言后产生了一个private 的calc属性,同时也自动生成了add_calc和remove_calc方法,用于注册和注销订阅者方法。因为事件是被封装的,尽管calc属性是private,但在所以在发布器类内部可以用 calc(sender,e)这样的方法直接调用;但在主程序中如果这样使用就会出错,只能通过onCalc方法进行间接调用。

后记

  本篇文章,一开始提出了什么是委托的疑问,通过引入几个方法来讲述委托是什么以加强对委托概念的理解。第二部分讲述了使用委托的四个步骤,并通过示例阐述了这几个步骤。第三部分讲述了委托使用的简化问题,通过使用泛型委托简化自定义委托,通过使用匿名委托可以简化定义委托方法。匿名委托是在定义委托的时候直接给出方法体,通过使用lambda表达式的类型推断可进一步简化委托的使用。第四部分讲述了委托链,通过绑定方法初始化委托,并通过+=绑定更多的委托方法。第五部分讲述了事件的定义和使用的四个步骤。当然委托的使用场景还有很多,比如通过BeginInvoke和EndInvoke进行异步调用,因不是本篇文章的重点,所以文章中没有提及。

以上内容是小编给大家介绍的C#中的委托数据类型简介,希望对大家有所帮助!

[!--infotagslink--]

相关文章

  • C#连接SQL数据库和查询数据功能的操作技巧

    本文给大家分享C#连接SQL数据库和查询数据功能的操作技巧,本文通过图文并茂的形式给大家介绍的非常详细,需要的朋友参考下吧...2021-05-17
  • php简单数据操作的实例

    最基础的对数据的增加删除修改操作实例,菜鸟们收了吧...2013-09-26
  • 解决Mybatis 大数据量的批量insert问题

    这篇文章主要介绍了解决Mybatis 大数据量的批量insert问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-01-09
  • php中浮点型(float)和整型(integer)数据类型详解

    文章分析了关于php中浮点型(float)和整型(integer)数据类型的用法区别以及在那种情况下会出现数据长度不够。 取值只能为True或者False,当其他类型转化为boolean类...2016-11-25
  • Antd-vue Table组件添加Click事件,实现点击某行数据教程

    这篇文章主要介绍了Antd-vue Table组件添加Click事件,实现点击某行数据教程,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-11-17
  • 详解如何清理redis集群的所有数据

    这篇文章主要介绍了详解如何清理redis集群的所有数据,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-02-18
  • c++中的system("pause")的作用和含义解析

    这篇文章主要介绍了c++中system("pause")的作用和含义,非常不错,具有参考借鉴价值,需要的朋友参考下吧...2020-04-25
  • vue 获取到数据但却渲染不到页面上的解决方法

    这篇文章主要介绍了vue 获取到数据但却渲染不到页面上的解决方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-11-19
  • c# 数据类型占用的字节数介绍

    本篇文章主要是对c#中数据类型占用的字节数进行了详细的介绍。需要的朋友可以过来参考下,希望对大家有所帮助...2020-06-25
  • Javascript类型转换的规则实例解析

    这篇文章主要介绍了Javascript类型转换的规则实例解析,涉及到javascript类型转换相关知识,对本文感兴趣的朋友一起学习吧...2016-02-27
  • 基于BootStrap Metronic开发框架经验小结【八】框架功能总体界面介绍

    这篇文章主要介绍了基于BootStrap Metronic开发框架经验小结【八】框架功能总体界面介绍 的相关资料,需要的朋友可以参考下...2016-05-14
  • php把读取xml 文档并转换成json数据代码

    在php中解析xml文档用专门的函数domdocument来处理,把json在php中也有相关的处理函数,我们要把数据xml 数据存到一个数据再用json_encode直接换成json数据就OK了。...2016-11-25
  • mybatis-plus 处理大数据插入太慢的解决

    这篇文章主要介绍了mybatis-plus 处理大数据插入太慢的解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-12-18
  • postgresql数据添加两个字段联合唯一的操作

    这篇文章主要介绍了postgresql数据添加两个字段联合唯一的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-02-04
  • C# 16 进制字符串转 int的方法

    这篇文章主要介绍了C# 16 进制字符串转 int的方法,非常不错,具有参考借鉴价值,需要的朋友可以参考下...2020-06-25
  • InterlliJ IDEA2020新建java web项目找不到Static Web的解决

    这篇文章主要介绍了InterlliJ IDEA2020新建java web项目找不到Static Web的解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-09-02
  • 深入理解PHP变量的值类型和引用类型

    在PHP中,大部分变量类型,如字符串,整型,浮点,数组等都是值类型的,而类和对象是引用类型,在使用的时候,需要注意这一点。看到网友在讨论PHP的&符号,要彻底理解它的用法,就有必要讨论一下变量的两种形式。PHP的变量在内存中是这样...2015-10-23
  • Vue生命周期activated之返回上一页不重新请求数据操作

    这篇文章主要介绍了Vue生命周期activated之返回上一页不重新请求数据操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-07-26
  • C#基于委托实现多线程之间操作的方法

    这篇文章主要介绍了C#基于委托实现多线程之间操作的方法,实例分析了C#的委托机制与多线程交互操作的相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下...2020-06-25
  • JQuery.Ajax()的data参数类型实例详解

    假如现在有这样一个表单,是添加元素用的。<form id='addForm' action='UserAdd.action' type='post'> <label for='uname'>用户名</label>:<input type='text' name='uname' id='uname'><br>...2015-11-24