详解C#中的委托
作者:邹琼俊 发布时间:2022-05-10 06:01:43
委托这个东西不是很好理解,可是工作中又经常用到,你随处可以看到它的身影,真让人有一种又爱又恨的感觉,我相信许多人被它所困扰过。
一提到委托,如果你学过C语言,你一定会马上联想到函数指针。
什么是委托?委托是C#中类型安全的,可以订阅一个或多个具有相同签名方法的函数指针。委托可以把函数做为参数传递,其实际意义便是让别人代理你的事情。委托可以看做是函数的指针,整数可以用整数变量指向它,对象可以用对象变量指向它,
函数也可以用委托变量指向它。我们可以选择将委托类型看做只定义了一个方法的接口,而委托的实例可以看做是实现了那个接口的一个对象。
使用委托,必须满足4个条件:
声明委托类型;
必须有一个方法包含了要执行的代码;
必须创建一个委托实例;
必须调用(invoke)委托实例。
委托的申明
声明委托的方式:delegate 返回值类型 委托类型名(参数)
委托的申明和接口方法的申明基本上一致,只是在返回类型关键字的前面多了一个delegate关键字。还有就是委托一般声明为public类型,因为它随时要供别人调用的。
委托的本质也是一个类型。我们声明一个类可以进行实例化,同样委托也可以进行实例化。
有如下四种委托:
//1.无参数无返回值
public delegate void NoParaNoReturnEventHandler();
//2.有参数无返回值
public delegate void WithParaNoReturnEventHandler(string name);
//3.无参数有返回值
public delegate string NoParaWithReturnEventHandler();
//4.有参数有返回值
public delegate string WithParaWithReturnEventHandler(string name);
如果代码想要执行操作,但不知道操作细节,一般可以使用委托。例如, Thread类之所以知道要在一个新线程里运行什么,唯一的原因就是在启动新线程时,向它提供了一个ThreadStart或ParameterizedThreadStart委托实例。
Thread th = new Thread(Test);
th.Start();
public Thread(ThreadStart start);
public delegate void ThreadStart();
ThreadStart是一个无参无返回值的委托。
static void Test()
{
Console.WriteLine("线程方法");
}
这个Test方法的函数签名必须和委托ThreadStart的函数签名一致。
委托的调用
必须先实例化委托,然后再调用。
函数的签名和委托的签名必须一致。NoParaNoReturnEventHandler _NoParaNoReturnEventHandler = ConsoleInfo;,编译器帮我们进行了new,但是不能写成NoParaNoReturnEventHandler _NoParaNoReturnEventHandler = ConsoleInfo();
因为这样就成为了函数调用。
#region 无返回值委托调用
public static void Show()
{
//实例化委托
NoParaNoReturnEventHandler _NoParaNoReturnEventHandler = new NoParaNoReturnEventHandler(ConsoleInfo);
//NoParaNoReturnEventHandler _NoParaNoReturnEventHandler = ConsoleInfo; //简写
//委托调用 通过Invoke()调用,或者可以直接省略
_NoParaNoReturnEventHandler.Invoke();
//_NoParaNoReturnEventHandler();
}
private static void ConsoleInfo()
{
Console.WriteLine("无参数无返回值的函数调用");
}
#endregion
没有委托就没有异步,异步正是因为委托的存在。
_NoParaNoReturnEventHandler.BeginInvoke(null,null); //异步调用
为什么要使用委托
我们完全可以直接调用方法,为什么还需要通过一个委托来调用呢?委托有什么意义?
解耦,对修改关闭,对扩展开放。逻辑分离。
你可以把委托理解为函数的父类,或者是一个方法的占位符。
我们来看下代码,假设有2个方法,一个说英语,一个说汉语,而这2个方法的函数签名是一样的。
public static void SayChinese(string name)
{
Console.WriteLine("你好," + name);
}
public static void SayEnglish(string name)
{
Console.WriteLine("hello," + name);
}
那么我们在外部调用的时候,
MyDelegate.SayChinese("张三");
MyDelegate.SayEnglish("zhangsan");
如果要调用这两个不同的方法,是不是要写不同的调用代码
我们能不能只一个方法调用呢?修改代码如下:
public static void Say(string name,WithParaNoReturnEventHandler handler)
{
handler(name);
}
public static void SayChinese(string name)
{
Console.WriteLine("你好," + name);
}
public static void SayEnglish(string name)
{
Console.WriteLine("hello," + name);
}
这样,只通过一个方法Say来进行调用。
如何调用呢?如下三种调用方式:
WithParaNoReturnEventHandler _WithParaNoReturnEventHandler = new WithParaNoReturnEventHandler(MyDelegate.SayChinese);
MyDelegate.Say("张三",_WithParaNoReturnEventHandler);
MyDelegate.Say("张三", delegate(string name) { Console.WriteLine("你好," + name); }); //匿名方法
MyDelegate.Say("张三", (name) => { Console.WriteLine("你好," + name); }); //lambda表达式
以上代码使用了几种调用方式,这些调用方式都是随着C#的升级而不断优化的。第一种是C#1.0中就存在的传统调用方式,第二种是C#2.0中的匿名方法调用方式,所谓匿名方法,就是没有名字的方法,当方法只调用一次时使用匿名方法最合适不过了。C#3中的lambda表达式。其实泛型委托同样是被支持的,而.NET 3.5则更进一步,引入了一组名为Func的泛型委托类型,它能获取多个指定类型的参数,并返回另一个指定类型的值。
lambda表达式
lambda表达式的本质就是一个方法,一个匿名方法。
如果方法体只有一行,无返回值,还可以去掉大括号和分号。
MyDelegate.Say("张三", (name) => Console.WriteLine("你好," + name));
如果方法体只有一行,有返回值,可以去掉大括号和return。
WithParaWithReturnEventHandler _WithParaWithReturnEventHandler = (name)=>name+",你好";
从.NET3.5开始,基本上不需要我们自己来申明委托了,因为系统有许多内置的委托。
Action和Func委托,分别有16个和17个重载。int表示输入参数,out代表返回值,out参数放置在最后。
Action表示无返回值的委托,Func表示有返回值的委托。因为方法从大的角度来分类,也分为有返回值的方法和无返回值的方法。
也就是说具体调用什么样的方法,完全由调用方决定了,就有了更大的灵活性和扩展性。为什么这么说,如果我有些时候要先说英语再说汉语,有些事时候要先说汉语再说英语,如果没有委托,我们会怎么样实现?请看如下代码:
public static void SayEnglishAndChinese(string name)
{
SayEnglish(name);
SayChinese(name);
}
public static void SayChineseAndEnglish(string name)
{
SayChinese(name);
SayEnglish(name);
}
如果又突然要添加一种俄语呢?被调用方的代码又要修改,如此循环下去,是不是要抓狂了?随着不断添加新语种,代码会变得越来越复杂,越来越难以维护。这样的代码耦合性非常高,是不合理的,也就是出现了所谓的代码的坏味道,你可以通过设计模式(如观察者模式等),在不使用委托的情况下来重构代码,但是实现起来是非常麻烦的,要写很多更多的代码...
委托可以传递方法,而这些方法可以代表一系列的操作,这些操作都由调用方来决定,就很好扩展了,而且十分灵活。我们不会对已有的方法进行修改,而是只以添加方法的形式去进行扩展。
可能有人又会说,我直接在调用方那里来一个一个调用我要执行哪些方法一样可以实现这样的效果啊?
可你有没有想过,你要调用的是一系列方法,你根本无法复用这一系列的方法。使用委托就不一样了,它好比一个方法集合的容器,你可以往里面增减方法,可以复用的。而且使用委托,你可以延时方法列表的调用,还可以随时对方法列表进行增减。委托对方法进行了再一次的封装。
总结:也就是当你只能确定方法的函数签名,无法确定方法的具体执行时,为了能够更好的扩展,以类似于注入方法的形式来实现新增的功能,就能体现出委托的价值。
委托和直接调用函数的区别:用委托就可以指向任意的函数,哪怕是之前没定义的都可以,而不用受限于哪几种。
多播委托
组合的委托必须是同一个类型,其相当于创建了一个按照组合的顺序依次调用的新委托对象。委托的组合一般是给事件用的,用普通委托的时候很少用。
通过+来实现将方法添加到委托实例中,-来从委托实例中进行方法的移除。
+和-纯粹是为了简化代码而生的,实际上其调用的分别是Delegate.Combine方法和Delegate.Remove。
如果委托中存在多个带返回值的方法,那么调用委托的返回值是最后一个方法的返回值。
public static void MultipleShow()
{
//多播委托
NoParaWithReturnEventHandler _NoParaWithReturnEventHandler = new NoParaWithReturnEventHandler(GetDateTime);
_NoParaWithReturnEventHandler += GetDateTime;
Console.WriteLine(_NoParaWithReturnEventHandler());
}
public static string GetDateTime()
{
return string.Format("今天是{0}号。", DateTime.Now.Day.ToString());
}
委托总结:
委托封装了包含特殊返回类型和一组参数的行为,类似包含单一方法的接口;
委托类型声明中所描述的类型签名决定了哪个方法可用于创建委托实例,同时决定了调用的签名;
为了创建委托实例,需要一个方法以及(对于实例方法来说)调用方法的目标;
委托实例是不易变的,就像String一样;
每个委托实例都包含一个调用列表——一个操作列表;
事件不是委托实例——只是成对的add/remove方法(类似于属性的取值方法/赋值方法)。
常见使用场景:窗体传值、线程启动时绑定方法、lambda表达式、异步等等。
生活中的例子:现在不是大家都在抢火车票吗,使用云抢票就相当于使用委托,你可以直接自己买票,也可以托管于云抢票,自己抢票的话,在快要开枪的时候,你必须时刻刷新,下单输验证码等等,使用云抢票的话,你只要放票前,提前输入抢票信息,就再也不需要你管了,自动出票,你根本不需要知道云抢票那边是怎么帮你实现抢票的。相同时间和车次可以做成一个委托实例,有很多人都通过这个委托实例来进行抢票操作。
源码下载:http://pan.baidu.com/s/1dEPlxJj
来源:http://www.cnblogs.com/jiekzou/p/6262597.html
猜你喜欢
- 就是仿照现在扫一扫的形式,周围是半透明的遮挡,然后中间是全透明的,拍摄后只截取框内的内容查了很多博客,实现起来真的太复杂了,本人比较怕麻烦所
- 加密代码using System;using System.IO;using System.Security.Cryptography;pu
- 本文实例讲述了Android ListView的简单应用。分享给大家供大家参考,具体如下:我们今天要讲的内容是Android中ListVie
- 先看一下效果图:<?xml version="1.0" encoding="utf-8"?&g
- 麦洛开通博客以来,有一段时间没有更新博文了.主要是麦洛这段时间因项目开发实在太忙了.今天周六还在公司加班,苦逼程序猿都是这样生活的.今天在做
- for循环和foreach循环其实可以算得上是从属关系的,即foreach循环是可以转化成for循环,但是for循环不一定能转换成forea
- 本文实例讲述了java数据结构与算法之中缀表达式转为后缀表达式的方法。分享给大家供大家参考,具体如下://stackpublic class
- 前几天在看一个cameraCTSbug时,结果在一个java for循环上有点蒙。正好赶上这个点总结一下。java中的控制结构:条件结构这里
- 这篇文章主要介绍了java加载property文件配置过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,
- 通过本文你可以用非常简短的代码替代业务逻辑中的判null校验,并且很容易的在出现空指针的时候进行打日志或其他操作。注:如果对Java8新特性
- 我们编程的过程中大部分使用了很出色的ORM框架,例如:MyBatis,Hibernate,SpringJDBC,但是这些都离不开数据驱动JD
- 先看看下面的代码能不能编译通过:public static void main(String[] args) {List l1 = new
- 1. 数据结构分类按照线性和非线性可以将Java数据结构分为两大类:①线性数据结构:数组、链表、栈、队列②非线性数据结构:树、堆、散列表、图
- 一、流程和任务的关系以下是一个简单的请假流程图,其中有一个开始事件,两个用户任务,一个结束事件。启动流程后,activiti会自动创建第一个
- C# AttributeUsage的使用是如何的呢?首先让我们来了解一下什么是AttributeUsage类它是另外一个预定义特性类,Att
- JDBC Statement对象实例以下是利用以下三种查询以及打开和关闭说明的例子:boolean execute(String SQL)
- 一. 封装封装是面向对象的三大特性之一;面向对象程序三大特性:封装、继承、多态 。封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和
- C# 操作符之三元操作符“?:”是什么呢?C# 操作符之三元操作符“?:”有时也称为条件操作符。对条件表达式b?x:y,先计算条件b,然后进
- 本文实例讲述了C#中TreeView实现适合两级节点的选中节点方法。分享给大家供大家参考。具体如下:class TreeViewChecke
- 一、Spring Bean 集合注入在【Spring学习笔记(三)】已经讲了怎么注入基本数据类型和引用数据类型,接下来介绍如何注入比较特殊的