【C# in depth 第三版】温故而知新(1)
声明
本文欢迎转载,原文地址:
前言关于这本书(《深入理解C# 第三版》)的详细情况以及好坏,自行搜索即可,我就不啰里啰嗦的,此文责在备份,意在记录一下第二次阅读当中发现原先囫囵吞枣之处,也为了记忆深刻吧。对这里还有一本《Clr via C# 第四版》也准备二次阅读,关于精度细读章节,知乎传送门( 赵姐夫的回答 ): https://www.zhihu.com/question/27283360。就在昨天(9月9号)北京和上海相继展开了技术分享会(有同步直播),这里要给北京的sqlserver演讲的老哥点赞,讲得不错挺落地和实在的,还有上海的分享的【.Net 微服务实战】,看了心里挺有感触的,并不是说从知识或者实践的层面领悟到有多少有多少,而是理解和明白这玩意儿为何而生,培养一点大局观和思维套路吧,毕竟在DDD、微服务大行其道的今天只有不断学习,然后温故而知新,可能才能知道它们想表达的含义吧!
这些知识你还记得么吗,\(^^)メ上面扯了一些口水话,其实在学习偏架构的知识时候,那么必经之路便是对语言基础的足够掌握以及对上层框架的理解掌握,那么今天再去扯架构和框架之前就来回顾一下关于语言的一些小知识,以下是在下记录一些书中查漏补缺自个人备份记忆使用,其中可能也包含以下不常用或者小知识,大神大佬请自动忽略 ,哈哈。
误区:对象在C#中默认是通过引用传递的我这里摘录引用书中部分描叙:首先“引用传递”的正式定义相当复杂,要涉及左值和类似的计算机科学术语,但是最重要的一点是,假如以引用传递的方式来传递一个变量,那么调用的方法可以通过更改其参数值,来改变调用者的变量值(说到这里有木有想到 ref out 关键字,是的它们就可以达到引用传递的效果)。那么默认情况,就是没有显示使用关键词的情况,引用类型变量的值才是引用(类似: 0x12345678 这种),而不是对象本身,且不需要按引用来传递参数本身,就可以更改该参数引用的那个对象的内容。例如:下面的方法更改了相关对象StringBuilder的内容,但是调用者的表达式引用的仍然是之前的那个对象:
void AppendHello(StringBuilder builder) { builder.Append("Hello"); }调用这个方法时,参数值(对StringBuilder的一个引用)是以值传递的方式传递的。如果想在方法内部更改builder的变量值,如:builder=null;对调用者来说是看不见的。
理解:JIT编译器如何处理泛型对于所有的封闭类型,JIT的职责就是将泛型类型的IL转换为本地代码,我们以List作为例子,首先JIT为每个以值类型作为类型实参的封闭类型都创建不同的代码,理论上对于一些值类型来说,代码是可以共享的,但是JIT必须十分谨慎,不仅需要考虑空间大小的问题,还要考虑垃圾回收的问题,所以第一次创建独享的代码。那么,所有使用引用类型(string、stream、stringbuilder等)作为类型实参的封闭类型都共享相同的本地代码,之所以可以这样做,是由于所有引用都具有相同的大小(32位CLR上是4个字节,64位CLR是8个字节,在任何一个特定的CLR中所有引用具有相同的大小)。关于泛型的新增API,GetGenericTypeDefinition作用于已构造的类型,获取它的泛型类型定义和MarkGenericType作用于泛型类型的定义,返回一个已构造类型,诸如此类还有:GetGenericArguments、IsGenericTypeDefinition、IsGenericType、MarkGenericMethod、IsGenericMethod等等。
Nullable类型理解与使用null进行赋值和比较原理注意Nullable是一个结构也就是值类型,对于例如Nullable的变量来说,直接包含了一个bool和一个int成员,而不是其他对象的引用。关于null赋值比较,原理:C#编译器允许使用null在比较和赋值时表示一个可空类型的空值。其中这样来处理的原因,相信也是从语言层面让语义更加符合自然逻辑,获得和引用类型null的同样体验。那么到底编译器关于可空值类型帮我们做了什么呐,int? a=null; if(a==null){ .... },与null比较其实在编译器生成IL代码中,被转换为调用a.HasValue,对a赋值为null,其实也是调用了Nullable的构造函数创建一个空值实例而已,注意直接调用a.Value在没有真正的值提供时将会抛出异常。
注意匿名函数变量捕获划重点:
1、捕获的是变量本身,而不是创建委托实例时它的值
2、捕获的变量的生存周期被延长了,至少和捕获它的委托一样长
3、多个委托可以捕获同一个变量
4、在循环内部,同一个变量声明实际上会引用不同的变量“实例”(这点在R#也会提示开发者)
5、如果捕获的变量不会发生改变,就不需要担心
6、在C#5或者以上修正foreach的表达含义,但是for依然需要注意。
这里直接引用书中的代码加强记忆和再次熟悉下流程,如下图所示代码:
关于代码的执行流程那得自个儿看看,慢慢跟着执行流程,相信就能明白Yield在代码执行中起到的作用,就貌似能在不同的方法之间跳跃,总的来说可以归为几点:
1、在第一次调用MoveNext之前,CreateEnumerable中的代码不会被调用
2、所有工作在调用MoveNext时就完成了,获取Current的值不会执行任何代码
3、在yield return的位置,代码会停止执行,方法暂时返回调用者方法,在下一次执行MoveNext时有继续在下一行代码继续执行
4、在一个方法中的不同位置可以编写多个yield return语句
5、代码不会在最后的yield return处结束而是通过返回false的MoveNext调用来结束方法的执行
上面说了这么多,在看到第3和4点的时候有没有感觉到await的部分作用似曾相识,await也可以让方法返回而且可以等在那里等到结果拿到之后接着那个“断点”继续执行,而且在一个Async标记的异步方法中可以,有多个await的拆包操作,所以在一定程度上面yield在思想和设计层面上,给后面的C#5的异步埋下了铺垫。从状态机的角度来看(这块并不是很熟悉,所以说错了,请斧正)在C#5中为迭代器块而生成的状态机和那些异步函数而生成的状态机之间的相似性是惊人的。异步开发中两个复杂的问题就是:1、处理状态 2、在感兴趣的事情发生之前进行有效的暂停,迭代器使得这两个问题得以完美的解决。 这里提供一下yield实现异步的伪代码,也是书中的代码片段:(伪代码是基于CCR实现的,主要是明白它想传递表达的意思,即可代码部分看看就好) static IEnumerator<ITask> ComputeTotalStockVal.(str.user,str.pass) { string token=null; yield return Arbiter.Receive(false,AuthService.CcrCheck(user,pass), delegate(string t){ token=t; }); IEnumerable<Holding> stocks=null; IDictionary<string,decimal> rates=null; yield return Arbiter.JoindReceive(false, DbService.CcrGetStockHoldings(token), StockService.CcrGetRate(token), delegate(IEnumerable<Holding> s,IDictionary<string,decimal> r) { stocks=s; rates=r; }); OnRequestComplete(ComputeTotal(stocks,rates)); }