今天天气不错,连夜下了场大雨后一切湿漉漉的。是的,我又回来接着写了,我又要看新的书了,怕自己忘记了之前看过的,大概搬运下旧书笔记。一边给新书记笔记,一遍搬运旧的,说不定可以打通任督二脉。
CLR是每种.NET编程语言都使用的运行库。c#是.net的核心开发语言,.net是一个运行时的平台,CLR是.net框架的底层,CLR全称为:通用语言运行库。.NET编程语言的编译器生成中间语言(Intermediate Language,IL)代码。
CLR(运行库)包括:
①:JIT(即时编译器),他会把IL码生成本地代码。
②:GC(垃圾回收器),负责清理不再引用的托管内存。
③:调试器,允许不同的编程语言之间启动调试会话。
④:线程实用工具,负责在底层平台上创建线程。
WPF,用于创建Windows桌面应用程序。System.Windows名称空间下的所有内容都属于WPF,但System.Windows.Forms除外。在WCF之前的程序间通信,一般有两种。第一种:是ASP.Web.Services和.NET Remoting;第二种:消息队列。
他们的实现一般都用到不同的API。WCF并不能替代前面说到的两种通信技术,它是将前面用到的多种API结合起来到一个API中,使得你可以在一个API中使用多种类似技术。即WCF可以实现remoting和消息队列。当然在wcf实现类似的技术也不会叫原名了。
LINQ(发音"link")的写法就类似SQL。
var students = from student in db.Students where student.Name =="张三" select new{student.Id}
lambda是一种语法糖,优化LINQ语法,LINQ才是实现的技术。有了LINQ和Lambda表达式,就可以使用相同的查询语法来访问对象集合、数据库和XML文件。
LINQ TO SQL和Entity Framework都可以对数据库进行操作,但是Linq书写就比较简单。但是对于更复杂的还是的依赖Framework,因为它更强大。现在你可以在Framework里去使用Linq。
C#是动态集成(动态语言在编译时不会对类型进行检查,而是在运行时识别对象的类型。)脚本语言,使其更容易使用COM集成(COM就是微软的组件对象模型。是由微软推出的一套接口规范,通过设定不同组件之间需要遵守的标准与协议,主要用来跨语言、跨进程之间的模块通信。详情看《COM技术内幕》一书)。C#语法拓展为使用dynamic关键字(dynamic关键字可充当C#类型系统中的动态类型声明。这样,C#就获得了动态功能,同时仍然作为静态类型化语言而存在。用dynamic增强C#泛型表达力)、命名参数和可选参数,以及用泛型增强的协变和逆变。
多核CPU产生了并行编程,任务并行库(Tack Parallel Linrary)使用Tack类(Task类这是一种任务,任务的实现依赖于线程。技术可以粗略理解为任务就是一个线程池,业务上就是做某件事)和Parallel类(parallel([ˈpærəlel])是尽可能的去压榨CPU给自己用)抽象出线程,更容易创建并行运行的代码。
C#使用async和await(async和await要始终保持一致。他们有很多详细的使用)大大简化了异步方法的编程,尤其是在触屏操作界面上使用。C#有一个新的编译器引擎Roslyn(.Net有了Roslyn后C#、VB.net也具备了脚本语言的优点,不用预先编译就能够运行,同时又具备了预编译语言的特性,执行效率更高,著名的跨平台游戏开发引擎unity/unity3D就已经提供了C#作为脚本开发语言的支持,一年揽金百亿的王者荣耀就是基于unity3D引擎开发的)。
Microsoft Silverlight是一个跨浏览器的、跨平台的插件,为网络带来下一代基于.NETFramework的媒体体验和丰富的交互式应用程序。Silverlight是一个Web浏览器插件,支持动态内容。
.NET程序的库和可执行文件成为“程序集(assembly)”。程序集是完全自描述性的,它是一个逻辑单元,这意味着它可以存储在多个文件中(动态程序集存储在内存中,我不是存储在文件中)。程序集的一个重要特征是它们包含的元数据描述了对应代码中定义的类型和方法。
程序集有两种类型:私有程序集和共享程序集。
私有程序集:一般附带在某个软件上,且只能用于该软件。
共享程序集:是其它应用程序可以使用的公共库。
UWP(通用Windows平台)利用Native .NET把IL编译成本地代码。UWP不同于传统pc上的exe应用,也跟只适用于手机端的app有本质区别。它并不是为某一个终端而设计,而是可以在所有windows10设备上运行。简而言之,UWP应用就是win10平板化的软件,win10电脑、平板、手机都可以使用。
在CLR执行应用程序之前,编写好的源代码(使用C#或者其它语言编写的代码)都需要编译。在.NET,编译分为两个阶段:
①将源代码编译为Microsoft中间语言(IL码)。
②:CLR把IL码编译为平台专用的本地代码。IL码仔.NET程序集中可用。运行时,JIT编译器编译IL码,创建特定于平台的本地代码。
即提供了一个IDE给你写源代码,可以是C#或者其它语言。
然后VS执行前CLR会把你写的源代码转成中间语言(IL)码。
最后由CLR中的JIT编译器编译IL码,创建特定平台的代码,如UWP平台。
1、进程和CLR的关系 | ||||||||||||||
一个进程可以只包含一个CLR,也可以包含多个CLR | ||||||||||||||
2、CLR和AppDomain的关系 | ||||||||||||||
一个CLR可以包含多个AppDomain | ||||||||||||||
3、CLR和线程池的关系 | ||||||||||||||
一个CLR只包含一个线程池 | ||||||||||||||
所以得出一个CLR下的多个AppDomain共享一个线程池和一个进程下的多个CLR拥有多个线程池的结论.注:多个线程池间的线程池相互不产生影响. | ||||||||||||||
4、CLR和线程池和操作请求队列的关系 | ||||||||||||||
①、CLR第一次初始化时,线程池并没有线程,当应用程序调用异步代码执行一个方法时,会将该请求记录项加入到操作请求队列中, | ||||||||||||||
线程池的代码从这个队列中获取记录项,并派发给线程池线程,接着线程池会创建线程,当然这里会有性能开销,但是当该线程执行完毕之后, | ||||||||||||||
线程池会回收这个线程,这里注意:线程池不会直接销毁这个线程,而是让它处于闲置状态.这样就不会产生额外的性能开销. | ||||||||||||||
但是如果该线程如果长时间处于闲置状态,那么线程池会销毁它,关于这个时间的计算很复杂,各个CLR对它的定义各不相同. | ||||||||||||||
②、当应用程序向线程池发起了多个请求,线程池会尝试用一个线程来处理你所有的请求,但是如果这个线程处理压力过大, | ||||||||||||||
那么它会开启一个新的线程来给它分担压力.以此类推. | ||||||||||||||
③、线程池之包含了少量线程,因为如果线程太多,会增加性能开销,当然如果你升级了你电脑的cpu,线程池则会创建更多的线程. | ||||||||||||||
这个过程线程池会自动的去读取你得cpu核数信息,自动的去分配合适的线程数 | ||||||||||||||
合理地分配CPU资源.当应用程序的压力减轻,那么它会销毁不用的线程. |
顺带提一下目前的JIT编译器,新版比老版不仅快,而且还在用Visual Studio调试时更好地支持Edit&Continue特性,允许在调试时编写代码,可以继续调试会话,而不需要停止并重新启动。(你在打断点的时候,还是可以写代码的。当然保存会报错)。
打包和发布应用程序,我一般用vs2010自带的打包工具,但是听说它不太靠谱,虽然我没碰到过。如果我们是要打包一个NuGet包,可以用命令 > dotnet pack ,发布就 > dotnet publish。
数据访问使用流API会让我们有很大的灵活性(普通的访问就普通的API好了),流提供了更多的特性,例如加密(我以前用的是模5加密算法)或压缩。阅读器和写入器简化了流的使用。为了读取数据库方便,可以直接使用ADO.NET,也可以使用抽象层:ADO.NET Entity Framework(ADO.NET与ADO.NET Entity Framework是不同的技术)。Entity Framework提供了从对象层次结构到数据库关系的映射。
XAML是用来创建表单XML声明,表单代表WPF应用程序的所有可视化方面和行为。WPF是卖相声明性编程方向的一步,听说趋势以后就是这样。声明性编程就是:不使用C#\VB\JAVA等编译语言通过编程方式创建对象,而是通过XML类型的编程方式声明一切对象。
WCF(WCF是微软的一组工业标准的实现,该标准定义了服务交互、类型转化、编排和多种协议的管理。WCF提供了服务间的互操作性并且提高了开发效率(包括几乎任何应用程序所要求实现的基本的常规的繁重任务))允许一次性构建服务,然后在一个配置文件中进行更改,让这个服务用于许多方面(甚至在不同协议下)。
ASP .NET Web API是非常容易通信的一个选项,能满足分布式应用程序90%以上需求。这项技术基于REST(Representational State Transfer),它定义了无状态、可伸缩的Web服务的指导方针和最佳实践。
在我们只需要简单的通信时,应该用年轻的ASP.Net Web API。因为WCF太过于庞大了。
当你想创建一个支持消息、消息队列、双工通信的服务时,你应该选择WCF
当你想创建一个服务,可以用更快速的传输通道时,像TCP、Named Pipes或者甚至是UDP(在WCF4.5中),在其他传输通道不可用的时候也可以支持HTTP,你应该选择WCF。
当你想创建一个基于HTTP的面向资源的服务并且可以使用HTTP的全部特征时(比如URIs、request/response头,缓存,版本控制,多种内容格式),你应该选择Web API
当你想让你的服务用于浏览器、手机、iPhone和平板电脑时,你应该选择Web API
ASP .NET Web API是创建微服务的一个好方法,创建微服务的方法定义了更小的服务,可以独立运行和部署,可以自己控制数据存储。
对于实时Web功能以及客户端和服务器端之间的双向通信,可以使用的ASP .NET技术是WebHooks和SignalR。SignalR(SignalR是一个.Net开源库,用于构建需要实时进行用户交互和数据更新的Web应用,如在线聊天,游戏,天气或者股票信息更新等实时应用程序)使用WebSocket技术,在WebSocket不可用时,它可以回退到基于拉的通信机制。
WebHooks可以集成公共服务,这些服务可以调用公共ASP .NET Web API服务。WebHooks技术从GitHub或Dropbox和其他服务中接收推送通知。(webhooks与异步编程中"订阅-发布模型"非常类似,一端触发事件,一端监听执行。
webhooks就是用来做代码的自动拉取或者是项目的自动部署之类的)。
Web服务无论是通过WCF完成还是通过ASP .NET Web服务完成么,都需要一个主机才能运行。IIS通常是一个很好的选择,因为它提供了几乎所有服务,但它也可以是自定义程序。使用自定义选项创建一个后台进程,在运行Windows时启动的是Windows服务。
ASP .NET MVC基于著名的MVC模型(模型-视图-控制器)模式,更容易进行单元测试。它还允许把编写用户界面代码与HTML\CSS\JavaScript清晰地分离,它只在后台使用C#。
Microsoft Azure是微软基于云计算的操作系统。Microsoft Azure的主要目标是为开发者提供一个平台,帮助开发可运行在云服务器、数据中心、Web和PC上的应用程序。Microsoft Azure提供了软件即服务(Software as Service,SaaS)、基础即服务(Infrastructure as a Service,IaaS)和平台即服务(Platform as a Service,PaaS)。
C#编译器需要用某个初始值对变量进行初始化,之后才能在操作中引用改变量,否则会报错。
C#实例化一个引用对象,需要使用new关键字。即创建一个引用,使用new关键字把该引用指向存储在堆上的一个对象。
不确定类型时,使用var关键字替代实际的类型,当var根据值推断出类型后,比如var此刻是int型,那一切规则就得照着int型来了。但是深有体会好多人滥用这个关键字,动不动就万能var甩出来。我们其实要注意一些编程规则的:
①、变量必须初始化,否则编译器就没有推断变量类型的依据;
②、初始化器不能为空;
③、初始化器必须放在表达式中;
④、不能把初始化器设置为一个对象,除非在初始化器中创建一个新对象。
作用域有时候不注意就误以为会报错,比如:
class Program
{
static int j = 20;
static void Main()
{
int j =30;
WriteLine(j);
return;
}
}
虽然声明了一样名称的变量,这段代码也会编译,首先确定它没有超出作用域,Main()方法中的j隐藏了同名的类级别变量,所以在运行时结果会是30。如果想要20,那么久Program.j。在访问静态方法中的静态字段,不能使用类的实例,只能使用类本身的名称。如果要访问实力字段(该字段属于类的一个特定实例),就需要使用this关键字。
c#中静态字段和非静态字段以static标识区分,主要区别是静态字段可以直接以类名.字段名方式使用,自而非静态字段必须创建类实例才可以使用。
Class1.name;
new Class1().name;
另外,静态字段在装载程序集的时候就分配内存空间,而非静态字段要在实例初化时候才分配内存空间。
在变量的前面加上关键字const就变成常量了,常量总是隐式静态的。
C#有两种数据类型:①:值类型;②引用类型。
其区别是值类型直接存储其值,而引用类型存储对值的引用。这两种存储在内存的不同地方:值类型存储在堆栈(stack)中,引用类型存储在托管堆(managed heap)上。值类型的值可以保存在栈(stack)的内存区域中,当然如果一个值类型被声明在一个方法体外并且在一个引用类型中,那它就会在堆(heap)上进行分配。结合我们学c/c++的时候,在学习指针那块的内容,再加上结合学习《计算机操作原理》中内存存储和读取的知识,一下子就很懂了。我们看一下这两种类型在编码上的表现:
// 我们假设Vector是一个类
class Vector
{
int value;
}
void Test()
{
// 这里测试的主体是对象,所以不要盯着value是int这看。
Vector x,y;
x = new Vector();
x.value = 30;
y = x;
WriteLine(y.value);
y.value = 50;
WriteLine(x.value);
}
以上代码,我们可以知道x是被创建对象的,然后y没有直接被创建对象,但是x把自己赋值给了y,这个时候整个过程只存在一个对象,就是x对应的创建出来的对象。x和y都指向了包含该对象的内存位置,因为x,y都是引用类型,那x的实例化就是y的实例化,所以y.value就是30;同理,y后来实例化该对象为50,那x.value就是50。要注意他们是被引用的,在C++指针直接指向值地址,由其地址关联到其值。~~那如果设定测试的主体不是对象而是两个值类型如int x,y呢,c#值类型时直接指向其值的,所以int y其实是复制了int x的值,另开一块栈空间存储y的值,由此可知,xy互不影响。
结论:引用类型互相影响(string除外,它在这方面是跟值类型一样的,因此它被称为“特殊引用类型”),值类型互不影响。(提到这,我想起了C/C++后来出了一个“智能指针”的概念,当然提前还是他是面向过程的,只是它好像把搞C/C++的从关注何时释放、如何释放这块给解放了)。
引申一下,当我们每次进入一个方法或者创建一个对象,系统就会为方法体内的变量或对象重新创建一个空间。那么对于类变量呢?那就要看它是什么类型,如果是值类型,地址就会每次你使用就会改变,引用对象地址就一直都在,除非你赋予新值。如:
// 假设b、c都是类属性
int b=90;
string c="nihao";
// 有个方法A() 将b、c地址输出
WriteLine($"c堆栈地址: {v.getMemory(v.c)}"); // c堆栈地址: 0x2802398
WriteLine($"c堆栈地址: {v.getMemory(v.c)}"); // c堆栈地址: 0x2802398
WriteLine($"b堆栈地址: {v.getMemory(v.b)}"); // b堆栈地址: 0x28049F8
WriteLine($"b堆栈地址: {v.getMemory(v.b)}"); // b堆栈地址: 0x2804AA4
// 这里提醒一下,b之所以不一样,是因为你是用过方法去寻址,如果直接来也一样,如下:
// GCHandle handle = GCHandle.Alloc(v.b, GCHandleType.Pinned);
// IntPtr addr = handle.AddrOfPinnedObject();
// WriteLine($"v.b堆栈地址: {$"0x{addr.ToString("X")}"}"); // v.b堆栈地址: 0x2D923A8
// WriteLine($"v.b堆栈地址: {$"0x{addr.ToString("X")}"}"); // v.b堆栈地址: 0x2D923A8
v.c = "新的";
v.b = 9;
WriteLine($"c堆栈地址: {v.getMemory(v.c)}"); // c堆栈地址: 0x2802368
WriteLine($"c堆栈地址: {v.getMemory(v.c)}"); // c堆栈地址: 0x2802368
WriteLine($"b堆栈地址: {v.getMemory(v.b)}"); // b堆栈地址: 0x2804C90
WriteLine($"b堆栈地址: {v.getMemory(v.b)}"); // b堆栈地址: 0x2804D3C
这里是不是想到:妈呀,每来一遍就创建那么多用完就不用的,好浪费。没错,很浪费,尤其当写下一堆你懒得看的,一半以上可以不要的变量的时候更浪费,费眼!C# 的CLR推出了人性化服务:CLR会定期删除不能访问的对象,把它们占用的内存返回给操作系统。这就由上面讲过的CLR组成中的垃圾回收器来完成的。
C#有15个预定义类型,其中13个类型时值类型,只有2个引用类型(string和object)。内置的.NET值类型表示基本类型,如整型(int)和浮点类型(float)、字符类型(char)和布尔类型(bool)。
点名一个类型——decimal,最近老用到它。.NET和c#数据类型一个重要的优点是提供了一个专用类型进行财务计算,就是decicmal类(decimal 表示 128 位数据类型。 同浮点型相比,decimal 类型具有更高的精度和更小的范围,更适合定义价格,金额等)。要把数字指定为decimal类型,需要在数字后面加上M或m字符,如:decimal d = 12.3M;
有一些常用的语句我们有时候会忽略它的一些小用法,我们可能就会错过一个更简洁的编码。
比如我经常忘记的switch语句,当我们没有及时的break跳出去时,它就会直接到下一条去了,而不管是不是匹配上。如:
// 假如输入“我爱下班”,输出的肯定是“我爱上班”!
switch (value)
{
case "我爱下班":
WriteLine("下班去老板娘的店里来打生蚝加罐冰啤酒,人生啊!!");
case "我爱上班":
WriteLine("上班才会使我快乐!!");
break;
case "我爱996":
WriteLine("996才会使我倍感幸福!!");
break;
default:
WriteLine("多么希望领导看到我多么热爱事业!!");
break;
}
当你希望忽视实际而去呈现特定结果的时候,这个会比较简洁或者你可以选择“委托”中的多播的写法(后面讲),不要你一遍遍的写调用后续的方法。
还有一个foreach和for循环。foreach不允许在循环中修改被循环的集合中的元素。用for就不会有这个问题。要用什么时候用foreach,我一般是在有数组和集合(列表、对象等)的时候用,正常按次数的情况下我用for。
枚举是用户定义的整数类型。在C#中,枚举真正强大之处是它们在后台会实例化为派生自基类System.Enum的结构。这表示可以对它们调用方法,执行有用的任务。注意因为.NET Framework的实现方法,在语法上把枚举当成结构不会造成性能损失。
名称空间提供了一种组织相关类和其它类型的方式。与文件或组件不同,名称空间是一种逻辑组合,而不是物理组合。我们通常使用using来使用其类型名称来引用文件其它地方的名称空间中的类型。我们也可以用“::”给名称空间起个别名。在c#中,::表示的是一个作用域符号,后面跟着在这个作用域内的变量或对象。
预处理器指令,这个就比较有用了,尤其当你想给不同的人使用不同的功能的时候。如果计划发布两个版本的代码,即基本版本和拥有更多功能的企业版本,就可以使用预处理器指令。C#没有一个像C++那样独立的预处理器,所以预处理器命令实际上是由编译器处理的。除了上述的用法,预处理器指令我经常这么用,有时候我们的程序debug不起作用,我们可以用这个方法去进行强制debug,当然前提是你要在debug模式下。如 :
#if DEBUG
// DO ......
#endif
当编译器遇到#if时,会先检查相关的DEBUG是不是存在,存在就编译里面的代码,不存在就编译,跳过它(它指的是#if-#endif)。我还常用一种,有时候我们程序因为种种原因会爆出很多警告,用#pragma warning去忽视它。其实预编译还有很多很不错的功能,有空可以研究下更为细致的用法。
最后来个就是很多人忽视的规则,它不是错,但是有关给下任接手时候的舒适度。
①、虽然C#是区分大小写的,但是不建议用一样的名字,当然如果你在局部做一个母子操作也没问题,关键是要明了,不然鬼知道这个和那个是什么区别,非要把代码看一遍梳理一下才懂;
②、保留字是极不建议做标识符的,但是在某些境况如果你非要这么做,就在前面加上@符号,告知编译器其后内容是一个标识符,而不是c#关键字。如:@abstract就是一个普通的标识符。
③、C#中有些称为“上下文关键字”,比如async。因为你单单async是没有任何意义的,你要和await连用。上下文就是说要联系境况才能做出判断的。
④、方法名称建议全部首单词大写如XxxYyy。普通xxxYyyy,成员字段_xxxYyy。
⑤、自己风格最好保持一致,有时候要用语法糖就都用,不要这用那不用,搞得好奇怪,就像去哪里抄来黏贴进来的。