C#高级编程第11版 - 第八章 索引
【1】8.1 引用方法
1.委托是指向方法的.NET地址变量。
2.委托是类型安全的类,定义了返回类型和参数类型。委托类不单单只包含一个方法引用,它也可以保存多个方法的引用。
3.Lambda表达式直接跟委托相关。当参数是类型是一种委托类型,你可以使用Lambda表达式来实现一个被委托引用的方法。
【2】8.2 委托
方法在调用前,它需要被关联成某个类的实例。因此不允许直接访问方法地址。而当你需要这么处理的时候,你可以将方法细节封装成一种新的对象:委托。
委托则是存储了一个方法或者多个方法的访问地址。
【3】8.2.1 声明委托
1.声明委托的几种定义:
这里我们定义了一个叫IntMethodInvoker的委托,并且指定了这个委托的每个实例,可以保存(hold)指向返回值为void并接收一个int类型参数的方法引用。委托最重要的一点是,它是类型安全的。当你定义委托的时候,你提供了完整的方法细节,包括方法的签名以及它的返回值。
delegate void IntMethodInvoker(int x);
假定你想定义一个委托,叫做TwoLongOp,用来指代(represents)一个带有两个long类型参数并返回double类型的方法。你可以像这么做:
delegate double TwoLongsOp(long first, long second);
定义一个不接受任何参数仅仅返回string类型的方法委托,你可以像这么写:
delegate string GetAString();
2.可以像修饰普通class那样给委托加上访问修饰符——public,private,protected等等,如下所示:
public delegate string GetAString();
3.定义一个委托真的意味着定义了一个新的类。委托类实际上派生自System.MulticastDelegate,而它又派生自基类System.Delegate。C#编译器清楚委托类的存在并使用委托语法隐藏了委托类的操作细节。这也是另外一个可以用来说明C#是如何通过与基类联动并尽可能地让编程变得简单的例子。
4.当你定义完一个委托之后,你可以创建它的实例对象,并且用创建的实例来保存特定方法的细节。
5.当我们讨论委托的时候,它只有一个术语。委托既可以用来代表委托类,也可以用来指代具体的委托实例。当你创建一个委托实例的时候,它本身也可能作为一个委托被引用(is also referred to as a delegate)。你需要根据上下文来理清我们说到的委托究竟代表何种含义。
【4】8.2.2 使用委托
1.支持委托实例直接通过小括号进行调用,跟你调用实例里的Invoke方法是一样的。
2.委托推断:在每个应用到委托实例的地方,你都可以只敲方法名。也可省略这个new部分的代码,仅仅将方法名传递给委托变量。委托推断可以在任何用到委托变量的地方生效。
3.x.ToString作为参数传递给构造函数。在这里你不能在ToString后面敲上小括号变成"x.ToString()"这样子。因为这意味着一次方法调用,这个方法调用返回的是一个string类型的对象,而这种类型无法赋值给一个委托类型的变量。你只能将方法的地址赋值给委托变量。
4.给定委托的实例可以引用任意类型任意对象上的实例或者静态方法,只要方法签名跟委托需要的方法签名一致即可。
5.GetAString()仅仅是个委托名,用于下面来调用,()里代表要调用哪个方法的参数。
【5】8.2.3 简单的委托示例
1.一种使用委托的方式——将相关方法组织到一个数组中,然后你就可以通过循环来重复调用它们。
2.上述代码中关键的一行是你将哪个委托传递给ProcessAndDisplayNumber方法,譬如这样:
ProcessAndDisplayNumber(operations[i], 2.0);
之前的例子仅仅只是将委托的名称进行传递,并不包含任何参数,而在这里,通过将operations[i]作为参数进行传递,语法上其实做了两步处理:
- operations[i]意味着一个委托,它引用了某个方法;
- operations[i](2.0)意味着将2.0作为参数传递给委托引用的方法,进行一次方法调用。
【6】8.2.4 Action<T>和Func<T>委托
1.除了为每个参数和返回类型定义一个新的委托,你也可以直接使用Action<T>和Func<T>委托。
2.参考:https://www.cnblogs.com/asdyzh/p/9794451.html
3.当委托返回类型为void的时候,你可以使用预定义的Action<T>委托。
【7】8.2.5 BubbleSorter 示例
1.冒泡排序算法是一个常见算法并且可以非常简单地对数字进行排序。它对于小集合的数字处理非常好用,因为对于大集合的数字(譬如10个以上),有其他的更有效率的算法。
2.通过使用泛型版本的Sort<T>方法,来实现不是int类型的比较。
3.comparison委托引用的方法,必须带有两个同类型的参数,并且当第一个参数值小于第二个的时候,将返回true,否则返回false。
static public void Sort<T>(IList<T> sortArray, Func<T, T, bool> comparison)
4.注意为了和Func<T, T, bool>委托的签名匹配,你在这个类里定义的CompareSalary必须包含两个Employee参数,并且返回一个布尔值。
public static bool CompareSalary(Employee e1, Employee e2) => e1.Salary < e2.Salary;
从小到大排列。
【8】8.2.6 多播委托
1.封装多个方法的这种委托我们称之为多播委托(multicast delegate)。当你调用多播委托的时候,它会按顺序调用它引用的所有的方法。为了使多播委托有效,委托的返回值必须为void,否则,你只能得到委托引用的最后一个方法的返回值。
2.多播委托:+=,-=。+=替代了数组。
3.在引擎下,一个多播委托实际上是一个派生自System.MulticastDelegate的类,而MulicastDelegate又派生自System.Delegate。System.MulticastDelegate拥有额外的成员,允许将方法链的调用存储到一个列表中
4.当你使用多播委托时,请记住同一个委托引用的方法链中的顺序形式上是未定义的(formally undefined)。因此,请尽量不要编写一些需要依赖于特定执行顺序的方法。
5.通过一个委托来调用多个方法可能会导致一个更大的问题。多播委托包含了一个委托集合并挨个进行调用。假如其中某个方法抛出了一个异常,那么后续的调用就会中止。
在这种场景下,你也可以通过自己枚举委托的方法列表,来避免这种问题的发生。委托类里定义了一个方法,GetInvocationList,用来返回一个Delegate的数组对象。现在你可以通过它,直接调用它们引用的方法,捕获异常,并执行下一个方法:
Delegate[] delegates = d1.GetInvocationList();
foreach (Action d in delegates)
{
try
{
d();
}
catch (Exception)
{
Console.WriteLine("Exception caught");
}
}
先枚举委托的方法列表,然后遍历。
【9】8.2.7 匿名方法
1.委托要起效的话,它必须指向某个已经存在的方法,这意味着,委托必须跟它想要引用的方法拥有相同的签名
2.匿名方法是一段代码用来作为委托的参数。
3.没有将某个具体的方法名称赋值给变量,取而代之的是,我们使用了一个简单的代码块,通过delegate关键字以及紧随其后的string参数声明来赋值。
4.当你为某个事件定义委托的时候,这点将会变得更加明显(evident),它使得你不用书写更多复杂代码,尤其当你需要定义大量事件处理函数时。使用匿名方法并不会让你的代码跑的快些,编译器依然将其当成一个正常的方法,只不过在后台为其自动生成一个你不知道的方法名而已。
5.
在使用匿名方法时你必须遵守一系列的规则:
- 你不能在匿名方法中使用任何跳转语句,例如break,goto或者continue。反过来也一样,外部的跳转语句不能跳转到匿名方法中去。
- 匿名方法中不能使用不安全的代码(Unsafe code),并且匿名方法外定义的ref或者out参数无法访问,但是其他定义在匿名方法外的变量可以引用。
- 假如同样的功能需要被多次使用,就别用匿名方法了。在这种情况下,与其反复书写匿名方法,还不如定义一个正常的命名方法进行复用呢。
6.在新版本的程序中你不再需要使用这个语法,因为lambda表达式提供了同样并且更丰富的功能。
【10】8.3 lambda 表达式
在lambda表达式运算符=>
的左边,列出了所需的参数,而运算符右边则定义了方法实现,并最终将其赋值给委托变量lambda。
【11】8.3.1 参数
1.假如只有一个参数:
Func<string, string> oneParam = s => $"change uppercase {s.ToUpper()}";
Console.WriteLine(oneParam("test"));
2.假如委托需要多个参数:
Func<double, double, double> twoParams = (x, y) => x * y;
Console.WriteLine(twoParams(3, 2));
或者:
Func<double, double, double> twoParamsWithTypes = (double x, double y) => x * y;
Console.WriteLine(twoParamsWithTypes(4, 2));
【12】8.3.2 多行代码
1.假如lambda表达式仅仅只包含一行语句,那么方法块的左右大括号和return语句都可以省略,编译器会为你隐式地添加return:
Func<double, double> square = x => x * x;
写全:
Func<double, double> square = x =>
{
return x * x;
};
2.当你的方法实现需要用到多行代码的时候,方法块的{}
和显式的return关键字都是必须的。尽量写出来。
【13】8.3.3 闭包
1.在lambda表达式里你也可以访问代码块外的变量,这种方式被称之为闭包(closure)。小心使用。
2.为了实现lambda表达式x => x + someVal,编译器创建了一个匿名类,这个匿名类里包含了私有变量someVal,并且拥有一个构造函数来传递这个外部变量。这个构造函数会根据你将会访问多少外部变量而生成。
使用lambda表达式并且调用该方法的时候,创建了一个匿名类的实例并且当方法被调用的时候,将局部变量的值传递给匿名类的实例。
3.当你在lambda表达式里修改了一个闭包变量的值时,你在外部访问这个变量的时候,得到的也是修改后的值。
4.当你在多线程使用闭包的时候,你可能会陷入并发冲突。因此在使用闭包时,最好只使用不可变类型。这样做能够保证值是永久不变的,并且不需要同步处理。
你可以在任何委托类型的地方使用lambda表达式,而另外一个可以使用lambda表达式的类型是Expression或者Expression<T>,编译器用它来创建表达式树。
【14】8.4 事件
1.事件是基于委托的,并且对委托提供了发布/订阅的机制。
【15】8.4.1 事件发布程序
1.空条件运算符?.
在C# 6.0版本开始支持。事件的处理,对处理器进行空值处理:
if (NewCarInfo != null)
{
NewCarInfo(this, new CarInfoEventArgs(car));
}//C#6.0以前 NewCarInfo?.Invoke(this, new CarInfoEventArgs(car));
记住在触发事件之前,实现检查委托是否为null是很有必要的,假如没有任何人订阅的话,委托的值就是null,此时调用其方法将会引发异常。
2.EventHandler<TEventArgs>定义了一个处理器,接收两个参数并返回void,第一个参数是object类型,而第二个参数是泛型TEventArgs类型。
3.EventHandler<TEventArgs>的定义如下,其中限定了泛型类型必须是EventArgs类或者其派生类:
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e) where TEventArgs: EventArgs
4.在C#里,通过event关键字你可以在一行里声明EventHandler,编译器在后台为它创建了对应的委托类型变量,并且添加了add和remove关键字来订阅/退订委托。它与自动完成属性以及完整属性非常类似,完整的声明语法如下所示:
private EventHandler<CarInfoEventArgs> _newCarInfo;
public event EventHandler<CarInfoEventArgs> NewCarInfo
{
add => _newCarInfo += value;
remove => _newCarInfo -= value;
}
5.完整版的事件定义也是很有用的,假如你需要在其中添加其他的逻辑的话,譬如在多线程访问中添加同步代码。UWP和WPF控件有时候也通过完整定义来添加事件的冒泡和隧道处理(bubbling and tunneling functionality with the events)。
6.CarDealer类通过调用委托的Invoke方法来触发事件。这将会调用所有订阅了该事件的处理器。记住,就像前面多播委托演示的那样,方法调用的顺序是没有保障的。为了能更好地控制多有处理器的执行,你可以使用Delegate类里的GetInvocationList方法来访问委托里的所有项并且对每项单独调用,就像之前演示的那样。
【16】8.4.2 事件侦听器
通过定义一个类来充当事件监听器。当这个类订阅了某事件,则可以接收到设定好的信息,如果退订则就接收不到了。通过+=事件名订阅,-=事件名退订。
【17】8.5 小结
本章介绍了委托,lambda表达式和事件的基础知识。你已经学到了如何声明一个委托,并如何将方法添加到委托队列里。你也了解到如何使用lambda表达式来实现委托的方法调用,并且你还了解了为一个事件声明处理器的过程,包括如何创建一个自定义的事件已经如何触发这个事件。
在设计大型应用程序时使用委托和事件能大大降低各层之间的依赖关系。这使得你能开发高复用的程序组件。
lambda表达式是C#的语言特性,它是基于委托的,通过它,你可以省略不少代码。lambda表达式不单只用在委托上,它还能用在LINQ中。
参考网址:https://www.cnblogs.com/zenronphy/p/ProfessionalCSharp7Chapter8.html#81-%E5%BC%95%E7%94%A8%E6%96%B9%E6%B3%95
C#高级编程第11版 - 第八章 索引的更多相关文章
- C#高级编程第11版 - 第九章 索引
[1]9.1 System.String 类 String类中关键的方法.如替换,比较等. [2]9.1.1 构建字符串 1.String类依然有一个缺点:因为它是不可变的数据类型,这意味当你初始化一 ...
- C#高级编程第11版 - 第二章 索引
[1]2.1.1 Hello,World! 1. using static System.Console; // ... WriteLine("Hello World!"); 提前 ...
- 2019-1-17 前言 C#高级编程(第11版)
C#已更新为更快的速度.主要版本7.0是2017年3月发布,次要版本7.1和7.2很快发布在2017年8月和2017年12月.通过项目设置,您可以与每个应用程序一起分发,是开源的,不可用仅适用于Win ...
- C#高级编程第11版 - 第六章 索引
[1]6.2 运算符 1.&符在C#里是逻辑与运算.管道符号|在C#里则是逻辑或运算.%运算符用来返回除法运算的余数,因此当x=7时,x%5的值将是2. [2]6.2.1 运算符的简写 1.下 ...
- C#高级编程第11版 - 第五章 索引
[1]5.1 泛型概述 1.通过泛型,你可以创建独立于特定类型(contained types)以外的方法和类,而不用为不同类型编写多份同样功能的代码,你只需要创建一个方法或者类. 2.泛型类使用泛型 ...
- C#高级编程第11版 - 第三章 索引
[1]3.1 创建及使用类 1.构造函数:构造函数的名字与类名相同: 使用 new 表达式创建类的对象或者结构(例如int)时,会调用其构造函数.并且通常初始化新对象的数据成员. 除非类是静态的,否则 ...
- C#高级编程第11版 - 第七章 索引
[1]7.1 相同类型的多个对象 1.假如你需要处理同一类型的多个对象,你可以使用集合或者数组. 2.如果你想使用不同类型的不同对象,你最好将它们组合成class.struct或者元组. [2]7.2 ...
- C#高级编程第11版 - 第四章 索引
[1]4.2 继承的类型 1.C#不支持类的多继承,但它支持一个接口继承自多个接口. 2.单继承:单继承允许一个类继承自另外一个基类,C#支持. 3.多级继承:多级继承允许创建一个类继承自它的父类,而 ...
- ASP.NET MVC 4高级编程(第4版)
<ASP.NET MVC 4高级编程(第4版)> 基本信息 作者: (美)Jon Galloway Phil Haack Brad Wilson K. Scott All ...
随机推荐
- 搜索引擎优化(SEO)解决方案
搜索引擎优化(SEO)解决方案 在此之前,希望大家能重新审视搜索引擎,通俗来讲就是我们日常所用的百度.谷歌.搜狗.雅虎等.磨刀不误砍柴工,知己知彼,百战不殆! 一.搜索引擎是什么? 搜索引擎(Se ...
- 为什么游戏公司的server不愿意微服务化?
背景介绍 笔者最近去面试了家游戏公司(有上市).我问他,公司有没有做微服务架构的打算及考量?他很惊讶的,我没听说过微服务耶,你可以解释一下吗? 我大概说了,方便测试,方便维护,方便升级,服务之间松耦合 ...
- 【代码周边】Idea设置类注解和方法注解(带图)
Idea版本: 类注解 打开setting→Editor→Code Style→File and Code Templates /** * Created with IntelliJ IDEA. * ...
- Modbus java slave app
文章实现 Modbus slave app , 用 serial rtu 传输, 代码只实现监听功能(本人测试可行), 要实现写功能,可研究一下代码中 updateProcessImage 方法.完整 ...
- log4j2文件结构
标签结构 Configuration properties Appenders Console PatternLayout File RollingRandomAccessFile Filters T ...
- Debian9 升级至 Debian10
前言 目前国内云服务商提供的镜像最新只有 9 , 本文讲解升级至 10 的方法 正文 查看当前版本 lsb_release -a No LSB modules are available. Distr ...
- 【Redis3.0.x】配置文件
Redis3.0.x 配置文件 概述 Redis 的配置文件位于Redis安装目录下,文件名为 redis.conf. 可以通过 CONFIG 命令查看或设置配置项. Redis 命令不区分大小写. ...
- 【剑指 Offer】03.1.不修改数组找出重复的数字
找出数组中重复的数字. 在一个长度为 n + 1 的数组 nums 里的所有数字都在 1-n 的范围内.所以数组中至少有一个是重复的.请找出数组中任意一个重复的数字. 示例 1: 输入: [2, 3, ...
- python模块详解 | psutil
目录 psutil 简介 psutil的功能函数 cpu memory_内存 disk_磁盘 net_网络 pid_进程管理 sensors_传感器 其他(用户,启动时间) psutil简介 psut ...
- 【Vue】Vue开发环境搭建
Vue前置学习环境 文章目录 Vue前置学习环境 IDE Node.js 调试环境 工程环境 小结 IDE WebStorm 官网下载:https://www.jetbrains.com/websto ...