前言

在本章中,主要是借机这个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();
}

注意:一个参数可以省略圆括号,多个参数必须圆括号,但是没有参数,必须使用一组空的圆括号

如: (参数,参数)=>{语句} 或者 表达式
(参数) =>{语句} 或者 表达式
参数 =>{语句} 或者 表达式
() =>{语句} 或者 表达式

总结

  1. 委托相当于用方法作为另一方法参数,同时,也可以实现在两个不能直接调用的方法中做桥梁,如在多线程中的跨线程的方法调用就得用委托。
  2. 熟悉在什么情况使用委托,在使用事件设计模式时,当需要封装静态方法时,当需要方便的组合时等多种情况下,可以加以使用。
  3. 如果有不对的或不理解的地方,希望大家可以多多指正,提出问题,一起讨论,不断学习,共同进步。
  4. 在下一节中,将对事件进行简单介绍,并总结归纳。

参考 文档 《C#图解教程》

注:搜索关注公众号【DotNet技术谷】--回复【C#图解】,可获取 C#图解教程文件

C#基础篇——委托的更多相关文章

  1. C#多线程之基础篇1

    在多线程这一系列文章中,我们将讲述C#语言中多线程的相关知识,在多线程(基础篇)中我们将学习以下知识点: 创建线程 中止线程 线程等待 终止线程 确定线程的状态 线程优先级 前台线程和后台线程 向线程 ...

  2. iOS系列 基础篇 08 文本与键盘

    iOS系列 基础篇 08 文本与键盘 目录: 1. 扯扯犊子 2. TextField 3. TextView 4. 键盘的打开和关闭 5. 打开/关闭键盘的通知 6. 键盘的种类 7. 最后再扯两句 ...

  3. C#基础系列——委托和设计模式(二)

    前言:前篇 C#基础系列——委托实现简单设计模式 简单介绍了下委托的定义及简单用法.这篇打算从设计模式的角度去解析下委托的使用.我们知道使用委托可以实现对象行为(方法)的动态绑定,从而提高设计的灵活性 ...

  4. c# 扩展方法奇思妙用基础篇八:Distinct 扩展(转载)

    转载地址:http://www.cnblogs.com/ldp615/archive/2011/08/01/distinct-entension.html 刚看了篇文章 <Linq的Distin ...

  5. [C# 基础知识梳理系列]专题六:泛型基础篇——为什么引入泛型

    引言: 前面专题主要介绍了C#1中的2个核心特性——委托和事件,然而在C# 2.0中又引入一个很重要的特性,它就是泛型,大家在平常的操作中肯定会经常碰到并使用它,如果你对于它的一些相关特性还不是很了解 ...

  6. 前端面试题目汇总摘录(JS 基础篇)

    JS 基础 JavaScript 的 typeof 返回那些数据类型 object number function boolean undefined string typeof null; // o ...

  7. 转载 【.NET基础】--委托、事件、线程(2) https://www.cnblogs.com/chengzish/p/4569912.html

    [.NET基础]--委托.事件.线程(2)   本文介绍event的使用以及原理,本文接上一篇文章的Demo继续[下载上一篇Demo] 上一篇我们在类(dg_SayHi.cs)里面定义代理了4个Del ...

  8. c# 扩展方法奇思妙用基础篇八:Distinct 扩展

    刚看了篇文章 <Linq的Distinct太不给力了>,文中给出了一个解决办法,略显复杂. 试想如果能写成下面的样子,是不是更简单优雅 var p1 = products.Distinct ...

  9. iOS开发 - OC - block的详解 - 基础篇

    深入理解oc中的block 苹果在Mac OS X10.6 和iOS 4之后引入了block语法.这一举动对于许多OC使用者的编码风格改变很大.就我本人而言,感觉block用起来还是很爽的,但一直以来 ...

随机推荐

  1. Redux:异步操作

    最近状态不太好,学习redux的异步操作花的时间比想象的多,这里尽量清晰简要的表述一下在redux中怎么实现异步操作. 先回顾一下同步操作: 我们用redux执行同步的时候,都是先发起一个dispat ...

  2. 9.5 Go 依赖管理

    9.5 Go 依赖管理 godep是解决包依赖的管理工具,目前最主流的一种,原理是扫描记录版本控制的信息. A. 所有的第三方包都放在$GOPATH的src目录下. B. 如果不同程序依赖的版本不一样 ...

  3. PAT-1134 Vertex Cover (图的建立 + set容器)

    A vertex cover of a graph is a set of vertices such that each edge of the graph is incident to at le ...

  4. 21-8 数据检索2 top和distinct

    --distinct关键字,根据已经查询出的结果然后去除重复 select distinct * from TblStudent --Top(一般会配合order by一起使用) ---------- ...

  5. RAC配置2个私网网卡使用HAIP服务

    如果是在oracle 10gRAC, 私网网卡冗余保护只能使用操作系统网卡绑定方式,但是到了oracle 11g,如果担心一个私网网卡故障导致rac节点驱逐,可以使用2个私网网卡,Oracle将自动使 ...

  6. Java——DOS命令窗口用命令编译文件夹下所有.java文件

    1.进入指定目录    cd 进入用户主目录    cd ~ 进入用户主目录     cd - 返回进入此目录之前所在的目录     cd .. 返回上级目录    cd\ 直接退回到当前盘根目录2. ...

  7. 织梦系统dedecms实现列表页双样式,列表样式循环交替变化

    有时候做列表页需要交替变换样式,那如何实现列表页双样式呢? 在DeDeCMS里面有这样一个函数,可以循环赋予html代码不同的样式,如下: [field:global function=MagicVa ...

  8. vue npm run dev报错webpack-dev-server

    在运行vue项目时报如下问题: E:\mobile_real\mobile-vue>npm run dev > mobile_real@1.0.0 dev E:\mobile_real\m ...

  9. 【Java8新特性】Stream API有哪些中间操作?看完你也可以吊打面试官!!

    写在前面 在上一篇<[Java8新特性]面试官问我:Java8中创建Stream流有哪几种方式?>中,一名读者去面试被面试官暴虐!归根结底,那哥儿们还是对Java8的新特性不是很了解呀!那 ...

  10. spark学习笔记总结

    Spark简介 spark 可以很容易和yarn结合,直接调用HDFS.Hbase上面的数据,和hadoop结合.配置很容易. spark发展迅猛,框架比hadoop更加灵活实用.减少了延时处理,提高 ...