- 本博客所总结书籍为《CLR via C#(第4版)》清华大学出版社,2021年11月第11次印刷(如果是旧版书籍或者pdf可能会出现书页对不上的情况)
- 你可以理解为本博客为该书的精简子集,给正在学习中的人提供一个“glance”,以及对于部分专业术语或知识点给出解释/博客链接。
- 【本博客有如下定义“Px x”,第一个代表书中的页数,第二个代表大致内容从本页第几段开始。(如果有last x代表倒数第几段,last代表最后一段)】
- 电子书可以在博客首页的文档-资源归档中找到,或者点击:传送门自行查找。如有能力请支持正版。(很推荐放在竖屏上阅读本电子书,这多是一件美事)
- 欢迎加群学习交流:637959304 进群密码:(CSGO的拆包密码)
目录
- 第十七章 委托
- 初识委托
- 用委托回调静态方法
- 用委托回调实例方法
- 委托揭秘
- 用委托回调多个方法(委托链)
- 委托定义不要太多(泛型委托)
- C#为委托提供的方法
- 委托和反射
- 第十八章 定制特性
- 使用定制特性
- 定义自己的特性类
- 特性构造器和字段/属性数据类型
- 检测定制特性
- 两个特性实例的相互匹配
- 检测定制特性时不创建从Attribute派生的对象
- 条件特性类
- 第十九章 可空值类型
- C#对可空值类型的支持
- C#的空接合操作符
- CLR对可空值类型的特殊支持
第十七章 委托
初识委托
- 回调方法的应用范围:登记回调方法来获得各种各样的通知,例如未处理的异常、窗口状态变化、菜单项选择、文件系统变化、窗体控件事件和异步操作已完成等。(P345 2)委托可以调用静态方法和实例方法。(静态方法和实例方法的区别可以参考之前的章节)
- (书中举了一个很长的示例,简单版的可以看这个)传送门
- 书中示例:
用委托回调静态方法
- 在StaticDelegateDemo方法中第一次调用Counter方法时,为第三个参数(对应于Counter的fb参数)传递的是null。由于Counter的 fb参数收到的是null,所以处理每个数据项时都不调用回调方法。(P357 last 2)
- StaticDelegateDemo方法再次调用Counter,为第三个参数传递新构造的Feedback委托对象。委托对象是方法的包装器(wrapper),使方法能通过包装器来间接回调。在本例中,静态方法的完整名称 Program.FeedbackToConsole被传给Feedback委托类型的构造器,这就是要包装的方法。new操作符返回的引用作为Counter 的第三个参数来传递。现在,当Counter执行时,会为序列中的每个数据项调用Program类型的静态方法FeedbackToConsole。FeedbackToConsole方法本身的作用很简单,就是向控制台写一个字符串,显示正在进行处理的数据项。(P348 1)
- 将方法绑定到委托时,C#和CLR都允许引用类型的协变性(covariance)和逆变性(contravariance)。协变性是指方法能返回从委托的返回类型派生的一个类型。逆变性是指方法获取的参数可以是委托的参数类型的基类。(P348 5)只有引用类型支持协变性与逆变性。
用委托回调实例方法
- 实例,上述代码中的InstanceDelegateDemo方法。代码中FeedbackToFile方法的工作方式类似于FeedbackToConsole和FeedbackToMsgBox,不同的是它会打开一个文件,并将字符串附加到文件末尾。(P349 3)
委托揭秘
- 编译器会根据委托实现一个完整的类,类中有4个方法:一个构造器,Invoke、BeginInvoke和EndInvoke。
- 所有委托类型都派生自MulticastDelegate
MulticastDelegate重要的非公共字段
代码语言:javascript复制Feedback fbstatic = new Feedback(Program.FeedbackToConsole) ;
Feedback fbInstance = new Feedback(new Program ( ) .FeedbackToFile);
用委托回调多个方法(委托链)
- 委托链是委托对于对象的集合。可利用委托链调用集合中的委托所代表的全部方法。
- 使用Delegate.Combine组合添加委托:(以及使用Remove删除委托)(P353 3)
//如章节开篇代码的demo1中所示,用如下方法添加了三个委托,第一次将null和fb1合并,第二次是fb2和fb1合并,最后是fb3。下图插入三个委托后的状态
fbChain = (Feedback) Delegate.Combine (fbChain,fb1);
//Remove
fbChain = (Feedback) Delegate.Remove(fbChain,new Feedback (FeedbackToMsgBox)) ;
- C#对委托链的支持:使用 =和-=操作直接进行添加,简化了代码的书写。(P356 last2)(如开头demo2函数所示)
- MulticastDelegate类提供了一个实例方法GetInvocationList,用于显式调用链中的每一个委托,并允许你使用需要的任何算法。以解决委托链除了最后一个返回值其余回调方法的返回值都会被抛弃,以及委托链阻塞的问题。(P357 1)示例:(建议认真阅读,原文太长这里就不放了)(P357 last)
public abstract class MulticastDelegate : Delegate {
//创建一个委托数组,其中每个元素都引用链中的一个委托
public sealed override Delegate [ ] GetInvocationList () ;
}
委托定义不要太多(泛型委托)
- 对于许多委托变量多引用的方法相同的情况,建议使用泛型委托捏。(P359 3)
C#为委托提供的方法
- C#提供的语法简化: 1、不需要构造委托对象(P360 last) 2、不需要定义回调方法(lambda表达式)(P361 2) 3、局部变量不需要手动包装到类中即可传给回调方法(P364 1)
//如果委托不获取任何参数,就使用()
Func<string> f=()=>"Jeff ";
//如果委托获取1个或更多参数,可显式指定类型
Func<int,string> f2 =(int n) => n.Tostring ( ) ;
Func<int,int,string> f3 = (int nl,int n2) => (nl n2). ToString();
//如果委托获取1个或更多参数,编译器可推断类型
Func<int,String> f4 = (n)=> n.Tostring ( ) ;
Func<int, int,string> f5 = (n1,n2) => (n1 n2).Tostring ();
//如果委托获取1个参数,可省略(和)
Func<int, String> f6 = n => n.Tostring ( ) ;
//如果委托有ref/ out参数,必须显式指定ref/out和类型
Bar b=(out int n)=> n=5;
//如果主体由两个或多个语句构成,必须用大括号将语句封闭,此时如果委托期待返回值,则必须在主体添加return语句
Func<Int32,Int32,String> f7 = (nl,n2) => { Int32 sum = nl n2; return sum.ToString(); };
委托和反射
- 使用CreatDelegate和DynamicInvoke来在编译时不知道委托的所有必要信息的前提下创建委托。(反射概念会在后续章节讲述)(P367 4)
public abstract class MethodInfo : MethodBase {
//构造包装了一个静态方法的委托
public virtual Delegate CreateDelegate(Type delegateType);
//构造包装了一个实例方法的委托: target 引用“this”实参
public virtual Delegate CreateDelegate(Type delegateT'ype,0bject target) ;
}
//调用
public abstract class Delegate {
//调用委托并传递参数
public object DynamicInvoke (params Object [ ] args) ;
}
第十八章 定制特性
使用定制特性
- 自定义特性:将一些附加信息与某个目标元素关联起来的方式。
- FCL定义了几百个定制特性,以下是举例: 1、将 DlImport特性应用于方法,告诉CLR该方法的实现位于指定DLL的非托管代码中。 2、将Serializable特性应用于类型,告诉序列化格式化器一个实例的字段可以序列化和反序列化。 3、将AssemblyVersion特性应用于程序集,设置程序集的版本号。将Flags特性应用于枚举类型,枚举类型就成了位标志(bit flag)集合。(P372 2) 使用特性时必须用[]括起来,例如[Serializable]
- 定制特性是一个类型的实例。为了符合“公共语言规范”(CLS)的要求,定制特性类必须直接或间接从公共抽象类System.Attribute派生。C#只允许符合CLS规范的特性。(P373 last)
定义自己的特性类
- 示例:(P374 last)
namespace system {
[ AttributeUsage(AttributeTargets.Enum,Inherited = false) ]public class FlagsAttribute : system.Attribute {
public FlagsAttribute ();
}
特性构造器和字段/属性数据类型
- 应用特性时必须传递一个编译时常量表达式,它与特性类定义的类型匹配。在特性类定义了一个Type参数、Type字段或者Type属性的任何地方,都必须使用C# typeof操作符(如下例所示)。在特性类定义了一个Object参数、Object字段或者Object属性的任何地方,都可传递一个Int32、String或其他任何常量表达式(包括null)。如果常量表达式代表值类型,那么在运行时构造特性的实例时会对值类型进行装箱。(P378 2)
检测定制特性
- 仅仅定义特性类没有用。确实可以定义自己想要的所有特性类,并应用自己想要的所有实例。但这样除了在程序集中生成额外的元数据,没有其他任何意义。应用程序代码的行为不会有任何改变。(P378 last)
- 使用反射技术可以来进行检测的实现。如System.Reflection.CustomAttributeExtensions类定义的扩展方法。该类定义了三个静态方法来获取与目标关联的特性:IsDefined,GetCustomAttributes 和 GetCustomAttribute。如果只想判断目标是否应用了一个特性,那么应该调用IsDefined,因为它比另两个方法更高效。但我们知道,将特性应用于目标时,可以为特性的构造器指定参数,并可选择设置字段和属性。使用IsDefined不会构造特性对象,不会调用构造器,也不会设置字段和属性。 要构造特性对象,必须调用GetCustomAttributes或GetCustomAttribute方法。每次调用这两个方法,都会构造指定特性类型的新实例,并根据源代码中指定的值来设置每个实例的字段和属性。两个方法返回的都是对完全构造好的特性类实例的引用。(P379 last)
两个特性实例的相互匹配
- 除了判断是否向目标应用了一个特性的实例,可能还需要检查特性的字段来确定它们的值。一个办法是老老实实写代码检查特性类的字段值。 另一个方法是,System.Attribute公开了虚方法 Match,可重写它来提供更丰富的语义。Match的默认实现只是调用Equal方法并返回它的结果。(P382 2)
- 代码示例(P382 last)
检测定制特性时不创建从Attribute派生的对象
- 使用System.Reflection.CustomAttributeData类在查找特性的同时进制执行特性类中的代码。(P384 last)
- 代码示例(P385)
条件特性类
- 应用了System.Diagnostics.ConditionalAttribute 的特性类称为条件特性类。代码示例:(P387)
第十九章 可空值类型
- CLR中的值类型不能为null,但在有些语言中时允许的。因此CLR引入了可空值类型的概念。(P389 last)
现在,要在代码中使用一个可空的Int32,就可以像下面这样写:
Nullable<Int32> x = 5;
Nullable<Int32> y = null;
Console.WriteLine("x: Hasvalue={0},Value={1} "",x.Hasvalue, x.Value) ;
Console.WriteLine("y: Hasvalue={0},value=(1)",y.HasValue,y.GetValueOrDefault());
//结果
x: Hasvalue=True,value=5
y : Hasvalue=False, value=0
C#对可空值类型的支持
- C#允许用问号表示法来声明并初始化x和y变量,等价于Nullable<int>。操作可空实例可能会生成大量代码。在重载过程中也可以编写有关可空操作符的相关判断。
int? x = 5;
int? y =null;
- 操作符对于可空值类型的操作结果: 1、一元操作符( , ,-,–,! ,~)操作数是null,结果就是null。 2、二元操作符( ,-,*,l,%,&,,^,<<,>>)) 两个操作数任何一个是null,结果就是null。但&和|操作符应用于boolean?操作数时有如下图规则 3、相等性操作符(==,!=) 两个操作数都是 null,两者相等。一个操作数是null,两者不相等。两个操作数都不是null,就比较值来判断是否相等。 4、关系操作符(<,>,<=,>=) 两个操作数任何一个是null,结果就是false。两个操作数都不是null,就比较值。
C#的空接合操作符
- C#提供了一个“空接合操作符”(null-coalescing operator),即??操作符,它要获取两个操作数。假如左边的操作数不为 null,就返回这个操作数的值。如果左边的操作数为 null,就返回右边的操作数的值。利用空接合操作符,可以方便地设置变量的默认值。 ??既可以用于引用类型也可以用于空值类型。(P393 last2)
CLR对可空值类型的特殊支持
- 可空值类型的装箱和拆箱:null不进行装箱,拆箱的null转换为Nullable<T>(P394 last2)
- 通过可空值类型调用GetType(P395 2)
- 通过可空值类型调用接口方法(P395 last2)