- 本博客所总结书籍为《CLR via C#(第4版)》清华大学出版社,2021年11月第11次印刷(如果是旧版书籍或者pdf可能会出现书页对不上的情况)
- 你可以理解为本博客为该书的精简子集,给正在学习中的人提供一个“glance”,以及对于部分专业术语或知识点给出解释/博客链接。
- 【本博客有如下定义“Px x”,第一个代表书中的页数,第二个代表大致内容从本页第几段开始。(如果有last+x代表倒数第几段,last代表最后一段)】
- 电子书可以在博客首页的文档-资源归档中找到,或者点击:传送门自行查找。如有能力请支持正版。(很推荐放在竖屏上阅读本电子书,这多是一件美事)
-
个人博客网址:《CLR via C#》笔记:第2部分 设计类型(4)-Sugar的博客,如文章转载中出现例如传送门失效等纰漏,建议直接访问原博客网址。
- 欢迎加群学习交流:637959304 进群密码:(CSGO的拆包密码)
目录
第十章 属性
无参属性
- 封装对类型中的数据字段的访问,好处(P202 1)
1、可以让访问字段来执行一些额外作用(side effect)、缓存某些值或者推迟创建一些内部对象。
2、可以以线程安全的方式访问字段。
3、字段可能是一个逻辑字段,它的值不由内存中的字节表示,而是通过某个算法来计算获得 - 使用get,set封装数据(P203 1),下述的get访问器方法不接受参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
- 自动实现的属性(Automatically Implemented Property,AIP):public string Name{get;set;}
使用AIP即创建了一个属性,访问该属性的任何代码实际都会调用get和set方法。 - AIP有如下问题:(P205 2)
1、字段声明语法可能包含初始化部分,所以要在一行代码中声明并初始化字段。但没有简单的语法初始化AIP。所以,必须在每个构造器方法中显式初始化每个AIP。
2、运行时序列化引擎将字段名持久存储到序列化的流中。AIP的支持字段名称由编译器决定,每次重新编译代码都可能更改这个名称。因此,任何类型只要含有一个AIP,就没办法对该类型的实例进行反序列化。在任何想要序列化或反序列化的类型中,都不要使用AIP功能。
3、调试时不能在AIP的get或set方法上添加断点,所以不好检测应用程序在什么时候获取或设置这个属性。相反,手动实现的属性可设置断点,查错更方便。 - 对象和结合初始化器:即可以在定义时候就使用{}对其公共成员复制,并支持完成一些额外操作。(P208 2)
1 2 |
|
- 匿名类型:用简洁的语法来自动声明不可变的元组类型。
1 2 3 4 5 6 |
|
- System.Tuple类型:从Object派生,也是匿名类型。示例:(P212 last)
有参属性
- C#称有参数的get访问器为索引器(P214 1)
C#使用数组风格的语法来公开有参属性(索引器)。换句话说,可将索引器看成是C#开发人员对[]操作符的重载。(P214 2)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
|
调用属性刚问器方法时的性能
- 简单的get,set,JIT编译器会将代码内联(inline)。这样就没有性能上的损失。(P218 last)
属性访问器的可访问性
- 有时希望为get访问器方法指定一种可访问性,为set访问器方法指定另一种可访问性。最常见的情形是提供公共get 访问器和受保护set访问器。(P219 1)
1 2 3 4 5 6 7 8 9 10 |
|
第十一章 事件
- 定义了事件成员的类型允许类型(或类型的实例)通知其他对象发生了特定的事情。定义了事件成员的类型能提供以下功能:(P221 1)类型之所以能提供事件通知功能,是因为类型维护了一个已登记方法的列表。事件发生后,类型将通知列表中所有已登记的方法。
1、方法能等级它对事件的关注
2、方法能注销它对事件的关注
3、事件发生时,登记了的方法将接收到通知 - CLR事件模型以委托为基础。委托是调用”回调方法的一种类型安全的方式。对象凭借回调方法接收它们订阅的通知。
设计要公开事件的类型
- 根据上图示例来进行设计:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
|
编译器如何实现事件
- C#编译器会把event事件转化为3个构造:1、一个被初始化为null的私有委托字段 2、一个公共add_xxx方法 3、一个公共remove_xxx方法(P226 1)
设计侦听事件的类型
- 定义一个类型来使用另一个类型提供的事件(P228 last)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
显式实现事件
- 通过显示实现事件来高效率地实现提供大量事件的类
代码:(P231)(注意一下应该是排版问题,代码中的EventSet类是包含之后所有函数到结尾的,但其他函数并没有缩进导致看起来和EventSet类同级了) - 委托(delegate)的使用:传送门
- 博客参考:传送门(原文代码中用了线程等内容,会在后续章节中讲述,目前看不太明白的可以看这篇博客)
第十二章 泛型
- 泛型(generic)是CLR和编程语言提供的一种特殊机制,它支持另一种形式的代码重用,即“算法重用”。(P233 1)即该算法没有设定要操作什么数据类型,因此可以广泛的应用于不同类型的对象,在使用时指定算法要操作的具体数据类型。(P233 2)
- CLR允许创建泛型引用类型和泛型值类型,不允许创建泛型枚举类型。
CLR允许创建泛型接口和泛型委托。(P233 3) - 泛型的优势:1、源代码保护 2、类型安全 3、更清晰的代码 4、更佳的性能(P235 1)
FCL中的泛型
- 示例(P237 last)
泛型基础结构
- 开放类型和封闭类型:具有泛型类型参数的类型仍然是类型,CLR同样会为它创建内部的类型对象。这一点适合引用类型(类)、值类型(结构)、接口类型和委托类型。然而,具有泛型类型参数的类型称为开放类型,CLR禁止构造开放类型的任何实例。这类似于CLR禁止构造接口类型的实例。代码引用泛型类型时可指定一组泛型类型实参。为所有类型参数都传递了实际的数据类型,类型就成为封闭类型。CLR允许构造封闭类型的实例。(P239 1)
- 泛型类型和继承:泛型也是类型,可以从任何类型派生。指定类型的实参不影响继承层次结构(用于判断强制类型是否被允许)。(P240 last)
- 泛型类型同一性:如果有一类继承自泛型,并分别实例化两个对象,那么泛型和类的对象在使用==符号判等时返回的结果为FALSE。可以使用using指令来化简泛型封闭类型,例
1 |
|
- 代码爆炸:使用泛型类型参数的方法在进行JIT编译时,CLR获取方法的IL,用指定的类型实参替换,然后创建恰当的本机代码(这些代码为操作指定数据类型“量身定制”)。这正是你希望的,也是泛型的重要特点。但这样做有一个缺点:CLR要为每种不同的方法/类型组合生成本机代码。我们将这个现象称为代码爆炸。它可能造成应用程序的工作集显著增大,从而损害性能。(P243 1)
优化:相同类型实参调用只需组合编译一次。(P243 2)CLR判定所有引用类型实参都完全相同则可以代码共享。(P243 3)
泛型接口
- 没有泛型接口,每次用非泛型接口(如IComparable)来操纵值类型都会发生装箱,而且会失去编译时的类型安全性。这将严重制约泛型类型的应用范围。因此,CLR提供了对泛型接口的支持。引用类型或值类型可指定类型实参实现泛型接口。也可保持类型实参的未指定状态来实现泛型接口。(P243 last)
泛型委托
- CLR支持泛型委托,目的是保证任何类型的对象都能以类型安全的方式传给回调方法。此外,泛型委托允许值类型实例在传给回调方法时不进行任何装箱。(17章会详细讲述)
委托和接口的逆变和协变泛型类型实参
- 委托的每个泛型类型参数都可标记为协变量(convariant)或逆变量(contravariant)。利用这个功能,可将泛型委托类型的变量转换为相同的委托类型(但泛型参数类型不同)。泛型类型参数可以是:不变量,逆变量(泛型类型参数可以从一个类更改为它的某个派生类,用in标记),协变量(泛型类型参数可以从一个类更改为它的某个基类,用out标记)。
泛型方法
- 定义泛型类、结构或接口时,类型中定义的任何方法都可引用类型指定的类型参数。类型参数可作为方法参数、方法返回值或方法内部定义的局部变量的类型使用。然而,CLR还允许方法指定它自己的类型参数。这些类型参数也可作为参数、返回值或局部变量的类型使用。(P247 1)
- 泛型方法和类型推断:C#编译器支持调用泛型方法时进行类型推断。(P248 last3)类型可以定义多个方法,让其中一个方法接受具体数据类型,让另一个接受泛型类型参数。(P249 2)
泛型和其他成员
- 在C#中,属性、索引器、事件、操作符方法、构造器和终结器本身不能有类型参数。但它们能在泛型类型中定义,而且这些成员中的代码能使用类型的类型参数。(P249 last2)
可验证性和约束
- 约束的作用:限制能指定成泛型实参的类型数量。通过限制类型的数量,可以对那些类型执行更多操作。(P250 last3)
约束可应用于泛型类型的类型参数,也可应用于泛型方法的类型参数(如下所示)。CLR不允许基于类型参数名称或约束来进行重载,只能基于元数(类型参数个数)对类型或方法进行重载。(P251 3)
1 2 3 4 5 6 7 |
|
- 主要约束:类型参数可以指定零个或者一个主要约束。主要约束可以是代表非密封类的一个引用类型。不能指定以下特殊引用类型:System.Object ,System.Array,System.Delegate ,System.MulticastDelegate,System.ValueType,System.Enum或者System.Void。(P252 4)
可以这是class和struct为主要约束(P252 last3) - 次要约束:类型参数可以指定零个或者多个次要约束,次要约束代表接口类型。这种约束向编译器承诺类型实参实现了接口。由于能指定多个接口约束,所以类型实参必须实现了所有接口约束(以及主要约束,如果有的话)。第十三章将详细讨论接口约束。(P25 3)
- 构造器约束:类型参数可指定零个或一个构造器约束,它向编译器承诺类型实参是实现了公共无参构造器的非抽象类型。注意,如果同时使用构造器约束和 struct约束,C#编译器会认为这是一个错误,因为这是多余的;所有值类型都隐式提供了公共无参构造器。(where T :new())(P254 2)
- 其他可验证性问题:(P254 last)
1、泛型类型变量的转型:将泛型类型的变量转型为其他类型是非法的,除非转型为与约束兼容的类型。
2、将泛型类型变量设置为默认值:将泛型类型变量设为null是非法的,除非将泛型类型约束成引用类型。
3、将泛型类型变量与null进行比较:无论泛型类型是否被约束,使用–或!=操作符将泛型类型变量与null进行比较都是合法的:
4、两个泛型类型变量相互比较:如果泛型类型参数不能肯定是引用类型,对同一个泛型类型的两个变量进行比较是非法的。
5、泛型类型变量作为操作数使用:会出大量问题,建议不用。