C#基础篇——委托
前言
在本章中,主要是借机这个C#基础篇的系列整理过去的学习笔记、归纳总结并更加理解透彻。
在.Net开发中,我们经常会遇到并使用过委托,如果能灵活的掌握并加以使用会使你在编程中游刃有余,然后对于很多接触C#时间不长的开发者而言,较好的理解委托和事件并不容易。
本节主要是讲述对委托的定义、委托的使用、多播委托、泛型委托、匿名方法、Func和Action委托、Lambda委托,并对它们进行讨论。
说明
简单说它就是一个能把方法当参数传递的对象,而且还知道怎么调用这个方法,同时也是粒度最小的“接口”(约束了指向方法的签名)。
开始
1.定义委托
委托:是一种定义方法签名的类型。 当实例化委托时,可以将其实例与任何具有兼容签名的方法相关联。 可以通过委托实例调用方法。
这里引用一个网友的说法:
某人有三子,让他们各自带一样东西出门,并带回一头猎物。
上面一句话可以理解为父亲对儿子的委托:猎物 办法(工具 某工具)-->delegate 猎物(返回值) 带回猎物(委托名)(工具(参数类型) x)-->delegate int GetValue(int i)
三个人执行委托的方法各不相同
兔子 打猎(工具 弓)-public static int GetValue1(int i){ return i; }
野鸡 买(工具 钱)-public static int GetValue2(int i){ return i*2; }
狼 诱捕(工具 陷阱)-public static int GetValue3(int i){ return i*i; }
2.简单的使用
一个委托类型定义了该类型的实例化时能调用的一类方法,这些方法含有同样的返回类型和同样参数(类型和参数个数相同)
比如:定义一个委托
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);
3.多播委托
在开发中,我们有时候会遇到要通过调用一个委托,同时可以执行多个方法的时候,就可以考虑用多播委托。调用多个委托需要多次显示调用这个委托。所有的委托实例都可以包含多个方法,实现多播功能。
这个打个比方:多播,就像一群程序员在瞬聘网填好了求职意向后,某天有个公司发布了一个和这些程序员求职意向刚好相匹配的工作,然后这些求职者都被通知了 - “有一份好工作招人啦,你们可以直接申请去上班了!”。
也就是说,一个委托实例不仅可以指向一个方法,还可以指向多个方法。
多播委托,提供了一种类似于流水线的钩子机制,只要加载到这条流水线上的委托,都会被顺序执行。因为所有的都继承自MulticastDelegate,因此所有的委托都具有多播特性
//声明一个委托,委托返回值为void
public delegate void Greetings(String name);
public static void Hello(String name)
{
Console.WriteLine("您好, {0}!", name);
}
public static void GoodBye(String name)
{
Console.WriteLine("再见, {0}!", name);
}
public static void Main()
{
Greetings greetings = Hello;
//使用+=给委托添加方法
greetings += GoodBye;
String name = "艾三元";
Console.WriteLine("这是一种调用方法:");
//第一种执行方式
greetings(name);
//第二种执行方式
Console.WriteLine("这是另一种使用方法");
//返回委托的调用列表。
Delegate[] delegates = greetings.GetInvocationList();
//注意这里的delegates列表中存储的是Greetings类型的委托
foreach (Greetings greeting in delegates)
{
greeting(name);
}
Console.ReadKey();
}
说明:
如果是多播委托,委托的签名就必须返回 void ,否则,返回值应送到何处?当委托只包含一个方法的时候,则可以通过所封装的方法发现其返回类型的声明,不一定必须是void。实际上,如果编译器发现某个委托返回 void ,就会自动假定这是一个多播委托。
“+=” 用来添加,“-=”用来从委托中删除方法调用
4.泛型委托
在之前的篇章中,我们已经学会了什么是泛型,因此,也方便我们理解泛型委托,简单的说,就是一种含有泛型参数的委托。
public delegate T Calculator<T>(T arg);
static int Double(int x) { return x * 2; }
static class Utility
{
public static void Calculate<T>(T[] values, Calculator<T> c)
{
for (int i = 0; i < values.Length; i++)
values[i] = c(values[i]);
}
}
static void Main(string[] args)
{
int[] values = { 11, 22, 33, 44 };
Utility.Calculate(values, Double);
foreach (int i in values)
Console.Write(i + " "); // 22 44 66 88
Console.ReadKey();
}
5. 匿名方法
匿名方法,是在初始化委托时候内联声明的方法。
每次实例化一个委托时,都需要事先定义一个委托所要调用的方法。为了简化这个流程,C# 2.0开始提供匿名方法来实例化委托。这样,我们在实例化委托时就可以 “随用随写” 它的实例方法。
static string GetNumber(string str)
{
return str;
}
delegate string DelNumber(string str);
static void Main(string[] args)
{
//声明一个名称为GetNumber的具名方法
DelNumber delNumber1 = GetNumber;
Console.WriteLine(delNumber1("这是具名方法"));
//匿名方法 ,未在别的地方定义方法,而是直接把方法写在实例化代码中
DelNumber delNumber2 = delegate (string str)
{
return str;
};
Console.WriteLine(delNumber2("这是匿名方法调用"));
Console.ReadKey();
}
#endregion
通过以上简单的示例看出:
匿名方法的语法:关键字delegate {参数列表}{语句块}
delegte { Paramters} {ImplementationCode}
delegate (string str)
{
return str;
};
使用的格式是:
委托类名 委托实例名 = delegate (args) {方法体代码} ;
delegate string DelNumber(string str); //委托类型的返回类型
//匿名方法 ,未在别的地方定义方法,而是直接把方法写在实例化代码中
DelNumber delNumber2 = delegate (string str)
{
return str; //根据返回类型,返回一个string类型
};
这样就可以直接把方法写在实例化代码中,不必在另一个地方定义方法。当然,匿名委托不适合需要采用多个方法的委托的定义。需要说明的是,匿名方法并不是真的“没有名字”的,而是编译器为我们自动取一个名字。
可以在以下地方使用匿名方法:
- 声明委托变量时为初始化表达式。
- 组合委托时在赋值语句的右边。
- 为委托增加事件时在赋值语句的右边。
6.Func 和 Action 委托
在之前,我们在使用委托的时候,都是自定义一个委托类型,再使用这个自定定义的委托定义一个委托字段或变量。而在后续的编程语言中又新加入了一种特性,C#语言预先为我们定义了两个常用的委托,一个是Func,一个是Action,还带来了Lambda,这使得委托的定义和使用变得简单起来, 在以后进行C#程序编写中引入委托更加灵活。
Action委托
C#中与预定义了一个委托类型Action,基本特点就是可以执行一个没有返回值,没有参数的方法。是一类没有输出参数的委托,但是输入参数可以为C#中的任意类型,即可以进行委托执行形式的方法。
static void printString()
{
Console.WriteLine("Hello World");
}
static void printNumber(int x)
{
Console.WriteLine(x);
}
static void Main(String[] args)
{
//Action基本使用
Action a = printString;
a(); // 输出结果 Hello World
//Action指向有参数的方法
Action<int> b = printNumber; // 定义一个指向 形参为int的函C#数
b(5); // 输出结果 5
}
Action可以通过泛型来指定,指向的方法有 0 - 16个参数
Action<int, int, string, bool 等等>
Func委托
Func同样也是预定的委托,是一种由返回值的委托,传递0-16个参数,其中输入参数和返回值都用泛型表示。
static int GetNumber()
{
return 1;
}
static int GetNumber(string str)
{
return 1;
}
static void Main(string[] args)
{
Func<int> a = GetNumber; // 定义一个Func 委托, 指向一个返回int类型的 方法
Console.WriteLine(a());
Func<string, int> b = GetNumber; // 泛型中最后一个参数表示返回值类型。
Console.WriteLine(b("Hello"));
}
注意:Func<string, int> 最后一个参数表示返回值类型,前面的都是形参类型。
7. Lambda表达式
江山代有才人出,纵然匿名方法使用很方便,可惜她很快就成了过气网红,没能领多长时间的风骚。如今已经很少见到了,因为delegate关键字限制了她用途的扩展。自从C# 3.0开始,她就被Lambda表达式取代,而且Lambda表达式用起来更简单。Lambda表达式本质上是改进的匿名方法。
在匿名方法中,delegate关键字有点多余,因为编译器已知将我们的方法赋值给委托。因此,我们很容易的将匿名方法的步骤转换为Lambda表达式:1. 删除delegate关键字。2.在参数列表和匿名方法主体之间放lambda运算符=>。
DelNumber delNumber2 = delegate (string str){ return str;}; //匿名方法
DelNumber delNumber2 = (string str) =>{ return str;}; //Lambda方法
Lambda表达式的灵感来源于数学中的Lambda积分函数表达式,例如下图:
Lambda表达式把其中的箭头用 => 符号表示。
上面的对比例子中,Lambda还可以进一步简化
delegate string DelNumber(string str); //委托类型的返回类型
DelNumber delNumber2 = (string str) =>{ return str;}; //Lambda方法
DelNumber delNumber3 = (str) =>{ return str;}; //省略类型参数
DelNumber delNumber4 = str =>{ return str;}; //省略类型参数( 如果只有一个隐式类型参数,可以省略周围的圆括号)
DelNumber delNumber5 = str => str; //语句块替换为return关键字后的表达式 ( 如果只有一个返回语句,可以将语句块替换为return关键字后的表达式)
如今Lambda表达式已经应用在很多地方了,例如方法体表达式(Expression-Bodied Methods)、自动只读属性表达式等等。
Lambda表达式形式上分为两种:
1.表达式Lambda
当匿名函数只有一行代码时,可采用这种形式。例如:
DelNumber delNumber= (s4, s5) => s4.Age <= s5.Age;
其中=>符号代表Lambda表达式,它的左侧是参数,右侧是要返回或执行的语句。参数要放在圆括号中,若只有一个参数,为了方便起见可省略圆括号。有多个参数或者没有参数时,不可省略圆括号。
相比匿名函数,在表达式Lambda中,方法体的花括号{}和return关键字被省略掉了。
用的也是表达式Lambda,这是Lambda表达式的推广, 是C# 6 编译器提供的一个语法糖。
2.语句Lambda
当匿名函数有多行代码时,只能采用语句Lambda。例如,上面的表达式Lambda可改写为语句Lambda:
DelNumber delNumber= (s4, s5) =>
{
//此处省略其他代码
return s4.Age <= s5.Age;
};
语句Lambda不可以省略{}和return语句。
完整示例
delegate string DelNumber(string str); //委托类型的返回类型
static void Main(string[] args)
{
DelNumber delNumber2 = (string str) => { return str; }; //Lambda方法
DelNumber delNumber3 = (str) => { return str; }; //省略类型参数
DelNumber delNumber4 = str => { return str; }; //省略类型参数( 如果只有一个隐式类型参数,可以省略周围的圆括号)
DelNumber delNumber5 = str => str; //语句块替换为return关键字后的表达式 ( 如果只有一个返回语句,可以将语句块替换为return关键字后的表达式)
Console.WriteLine(delNumber2("lambda"));
Console.WriteLine(delNumber3("lambda"));
Console.WriteLine(delNumber4("lambda"));
Console.WriteLine(delNumber5("lambda"));
Console.ReadKey();
}
注意:一个参数可以省略圆括号,多个参数必须圆括号,但是没有参数,必须使用一组空的圆括号
如: (参数,参数)=>{语句} 或者 表达式
(参数) =>{语句} 或者 表达式
参数 =>{语句} 或者 表达式
() =>{语句} 或者 表达式
总结
- 委托相当于用方法作为另一方法参数,同时,也可以实现在两个不能直接调用的方法中做桥梁,如在多线程中的跨线程的方法调用就得用委托。
- 熟悉在什么情况使用委托,在使用事件设计模式时,当需要封装静态方法时,当需要方便的组合时等多种情况下,可以加以使用。
- 如果有不对的或不理解的地方,希望大家可以多多指正,提出问题,一起讨论,不断学习,共同进步。
- 在下一节中,将对事件进行简单介绍,并总结归纳。
参考 文档 《C#图解教程》
注:搜索关注公众号【DotNet技术谷】--回复【C#图解】,可获取 C#图解教程文件
C#基础篇——委托的更多相关文章
- C#多线程之基础篇1
在多线程这一系列文章中,我们将讲述C#语言中多线程的相关知识,在多线程(基础篇)中我们将学习以下知识点: 创建线程 中止线程 线程等待 终止线程 确定线程的状态 线程优先级 前台线程和后台线程 向线程 ...
- iOS系列 基础篇 08 文本与键盘
iOS系列 基础篇 08 文本与键盘 目录: 1. 扯扯犊子 2. TextField 3. TextView 4. 键盘的打开和关闭 5. 打开/关闭键盘的通知 6. 键盘的种类 7. 最后再扯两句 ...
- C#基础系列——委托和设计模式(二)
前言:前篇 C#基础系列——委托实现简单设计模式 简单介绍了下委托的定义及简单用法.这篇打算从设计模式的角度去解析下委托的使用.我们知道使用委托可以实现对象行为(方法)的动态绑定,从而提高设计的灵活性 ...
- c# 扩展方法奇思妙用基础篇八:Distinct 扩展(转载)
转载地址:http://www.cnblogs.com/ldp615/archive/2011/08/01/distinct-entension.html 刚看了篇文章 <Linq的Distin ...
- [C# 基础知识梳理系列]专题六:泛型基础篇——为什么引入泛型
引言: 前面专题主要介绍了C#1中的2个核心特性——委托和事件,然而在C# 2.0中又引入一个很重要的特性,它就是泛型,大家在平常的操作中肯定会经常碰到并使用它,如果你对于它的一些相关特性还不是很了解 ...
- 前端面试题目汇总摘录(JS 基础篇)
JS 基础 JavaScript 的 typeof 返回那些数据类型 object number function boolean undefined string typeof null; // o ...
- 转载 【.NET基础】--委托、事件、线程(2) https://www.cnblogs.com/chengzish/p/4569912.html
[.NET基础]--委托.事件.线程(2) 本文介绍event的使用以及原理,本文接上一篇文章的Demo继续[下载上一篇Demo] 上一篇我们在类(dg_SayHi.cs)里面定义代理了4个Del ...
- c# 扩展方法奇思妙用基础篇八:Distinct 扩展
刚看了篇文章 <Linq的Distinct太不给力了>,文中给出了一个解决办法,略显复杂. 试想如果能写成下面的样子,是不是更简单优雅 var p1 = products.Distinct ...
- iOS开发 - OC - block的详解 - 基础篇
深入理解oc中的block 苹果在Mac OS X10.6 和iOS 4之后引入了block语法.这一举动对于许多OC使用者的编码风格改变很大.就我本人而言,感觉block用起来还是很爽的,但一直以来 ...
随机推荐
- Wfuzz使用学习
工具用了不总结,使用命令很容易生疏,今天就把笔记梳理总结一下. 0x01 简介 WFuzz是用于Python的Web应用程序安全性模糊工具和库.它基于一个简单的概念:它将给定有效负载的值替换对FUZZ ...
- HTML学习——day1
HTML是一种用于创建网页的标准标记语 注意:对于中文网页需要使用<meta charset=''utf-8''>声明编码,否则会出现乱码. HTML标签 <标签>内容< ...
- 模拟SWPU邮件登录页面
模拟SWPU邮件登录页面设计流程 一.开发工具准备 本次开发该页面时使用的开发工具为vscode—— 在下载安装完成后,需要下载各类插件——如汉化.通过浏览器打开网页插件等. 二.开发过程 首先,打开 ...
- 理解javascript中的连续赋值
之前在扒源码时经常看到类似的连续赋值操作: var a = b = 1; 在某度搜了众多前辈的博客,总算对这骚操作有点眉目. Case analysis 首先,javascript中连续赋值最典型 ...
- XXX_ProductCRUD的项目结构与配置文件
MVC_ProductCRUD Hibernate_ProductCRUD 项目结构 ...
- jq代替jsdom操作部分
接触js后学习了一些js操作html的方法 js可以配合css完成许多动画和操作.初次接触jquery感觉不是很习惯,毕竟js有了习惯,但是jq还是省去了很多繁琐的操作步骤. 首先使用之前 ...
- FPGA内部硬件结构简介
我们知道FPGA内部有很多可供用户任意配置的资源,其中包括:可编程逻辑.可编程I/O.互连线.IP核等资源,很多学过数字电路的人都知道与或非门可以构成几乎所有的数字电路,但是FPGA内部最基本的主要单 ...
- JavaScript实现树深度优先和广度优先遍历搜索
1.前置条件 我们提前构建一棵树,类型为 Tree ,其节点类型为 Note.这里我们不进行过多的实现,简单描述下 Note 的结构: class Node{ constructor(data){ t ...
- Docker 入门:Dockerfile
主要内容: 什么是 Dockerfile 查看 DockerHub 中镜像的 Dockerfile Dockerfile 编写 Dockerfile 常用命令 什么是 Dockerfile 使用 Do ...
- JavaScript——闭包(转自别人)
有这样一个段子:说闭包的主要作用是什么?,答:面试.确实在许多面试中,闭包是必问项目,所以不为别的,只为面试,理解闭包就很重要. 说到 闭包 ,这是js不得不提的一个特性,很多传统语言都不具备这样的特 ...