程序
程序集是代码进行编译是的一个逻辑单元,把相关的代码和类型进行组合,然后生成PE文件。程序集只是逻辑上的划分
【公共语言运行库CLR】加载器 管理 应用程序域,这种管理包括 将每个程序集加载到相应的应用程序域 以及 控制每个程序集中类型层次结构的内存布局
一个程序运行起来以后,有一个应用程序域(AppDomain),在这个应用程序域(AppDomain)中放了我们用到的所有程序集(Assembly)。我们所写的所有代码都会编译到【程序集】文件(.exe .dll)中,并在运行时以【Assembly对象】方式加载到内存中运行,每个类(Class Interface)以【Type对象】方式加载到内存,类的成员(方法,字段,属性,事件,构造器)加载到内存也有相应的对象。
一、 c#底层
跨语言
只要是面向.NET平台的编程语言((C#、Visual Basic、C++/CLI、Eiffel、F#、IronPython、IronRuby、PowerBuilder、Visual COBOL 以及 Windows PowerShell)),用其中一种语言编写的类型可以无缝地用在另一种语言编写的应用程序中的互操作性。
例子:C#调用vb生成的dll中的一个类
需遵循它就是
公共语言规范 - Common Language Specification ,简称CLS
跨平台:
一次编译,不需要任何代码修改,应用程序就可以运行在任意有.NET框架实现的平台上,即代码不依赖于操作系统,也不依赖硬件环境。
CTS(Common Type System 公共类型系统)
CLS是CTS(Common Type System 公共类型系统)这个体系中的子集
CLI(Common Language Infrastructure)公共语言基础结构。
软已经将CTS和.NET的一些其它组件,提交给ECMA以成为公开的标准,最后形成的标准称为cli
由微软开发的类库统称为:FCL,Framework Class Library ,.NET框架类库
汇编语言
不同厂商的CPU有着不同的指令集,为了克服面向CPU的指令集的难读、难编、难记和易出错的缺点,后来就出现了面向特定CPU的特定汇编语言, 比如我打上这样的x86汇编指令 mov ax,bx ,然后用上用机器码做的汇编器,它将会被翻译成 1000100111011000 这样的二进制01格式的机器指令.
用C语言写的代码文件,会被C编译器先转换成对应平台的汇编指令,再转成机器码,最后将这些过程中产生的中间模块链接成一个可以被操作系统执行的程序。
CLR
实际上,.NET不仅提供了自动内存管理的支持,他还提供了一些列的如类型安全、应用程序域、异常机制等支持,这些 都被统称为CLR公共语言运行库。
CLR是.NET类型系统的基础,所有的.NET技术都是建立在此之上,熟悉它可以帮助我们更好的理解框架组件的核心、原理。
在我们执行托管代码之前,总会先运行这些运行库代码,通过运行库的代码调用,从而构成了一个用来支持托管程序的运行环境,进而完成诸如不需要开发人员手动管理内存,一套代码即可在各大平台跑的这样的操作。
程序集
可执行文件(.exe文件)和 类库文件(.dll文件)。
在VS开发环境中,一个解决方案可以包含多个项目,而每个项目就是一个程序集。
程序集的结构:
程序集元数据,类型元数据,MSIL代码,资源。
①程序集元数据,程序集元数据也叫清单,它记录了程序集的许多重要信息,是程序集进行自我说明的核心文档。当程序运行时,CLR 通过这份清单就能获取运行程序集所必需的全部信息。清单中主要主要包含如下信息:标识信息(包括程序集的名称、版本、文化和公钥等);文件列表(程序集由哪些文件组成);引用程序集列表(该程序集所引用的其他程序集);一组许可请求(运行这个程序集需要的许可)。
②类型元数据,类型元数据列举了程序集中包含的类型信息,详细说明了程序集中定义了哪些类,每个类包含哪些属性和方法,每个方法有哪些参数和返回值类型,等等。
③MSIL代码,程序集元数据和类型元数据只是一些辅助性的说明信息,它们都是为描述MSIL代码而存在的。MSIL 代码是程序集的真正核心部分,正是它们实现了程序集的功能。比如在“Animals”项目中,五个动物类的C#代码最终都被转换为MSIL 代码,保存在程序集Animals.dll 中,当运行程序时,就是通过这些MSIL 代码绘制动物图像的。
④资源,程序集中还可能包含图像、图标、声音等资源。
私有程序集和共享程序集
私有程序集是仅供单个软件使用的程序集,安装很简单,只需把私有程序集复制到软件包所在文件夹中即可。而那些被不同软件共同使用的程序就是共享程序集,.NET类库的程序集就是共享程序集,共享程序集为不同的程序所共用,所以它的部署就不像私有程序集那么简单,必须考虑命名冲突和版本冲突等问题。解决这些问题的办法是把共享程序集放在系统的一个特定文件夹内,这个特定文件夹称为全局程序集高速缓存(GAC)。这个过程可用专门的.NET 工具完成
托管代码
Clr管理内存、处理安全性、垃圾回收,利用其托管功能让.net自己与操作系统进行交互
资源
值类型
- 垃圾收集器释放存储在托管堆中的托管对象,但不释放本机堆中的对象。必须由开发人员自己释放它们。
- 32位处理器上的每个进程都可以使用4GB的内存
- 选择x86时,就调试运行在32位和64位系统上的32位应用程序;选择x64时,就调试运行在64位系统上的64位应用程序
- 栈实际上是向下填充的,即从高内存地址向低内存地址填充。当数据入栈后,栈指针就会随之调整,以始终指向下一个空闲存储单元
- 首先,声明一个Customer引用arabel,在栈上给这个引用分配存储空间,但这仅是一个引用,而不是实际的Customer对象
- 分配堆上的内存,以存储Customer对象(一个真正的对象,不只是一个地址)。然后把变量arabel的值设置为分配给新Customer对象的内存地址
- 为了在堆上找到存储新Customer对象的一个存储位置,.NET运行库在堆中搜索,选取第一个未使用的且包含Customer对象个字节的连续块。
- 只要保持对数据的引用,该数据就肯定存在于堆上。
- 只要它释放了能释放的所有对象,就会把其他对象移动回堆的端部,再次形成一个连续的内存块。因此,堆可以继续像栈那样确定在什么地方存储新对象。当然,在移动对象时,这些对象的所有引用都需要用正确的新地址来更新,但垃圾回收器也会处理更新问题。
- NET下,较大对象有自己的托管堆,称为大对象堆。使用大于85000个字节的对象时,它们就会放在这个特殊的堆上,而不是主堆上。.NET应用程序不知道两者的区别,因为这是自动完成的。其原因是在堆上压缩大对象是比较昂贵的,因此驻留在大对象堆上的对象不执行压缩过程。
引用类型
垃圾回收
强引用和弱引用
1.如果对象相互引用,但没有在根表中引用,例如,对象A引用B, B引用C, C引用A,则GC可以销毁所有这些对象
2. 在应用程序代码内实例化一个类或结构时,只要有代码引用它,就会形成强引用。例如,如果有一个类MyClass,并创建了一个变量myClassVariable来引用该类的对象,那么只要myClassVariable在作用域内,就存在对MyClass对象的强引用
- 垃圾回收器不知道如何释放非托管的资源(例如,文件句柄、网络连接和数据库连接
- 在底层的.NET体系结构中,这些函数称为终结器(finalizer)。在C#中定义析构函数时,编译器发送给程序集的实际上是Finalize()方法。
- 没有析构函数的对象会在垃圾回收器的一次处理中从内存中删除,但有析构函数的对象需要两次处理才能销毁:第一次调用析构函数时,没有删除对象,第二次调用才真正删除对象。另外,运行库使用一个线程来执行所有对象的Finalize()方法。如果频繁使用析构函数,而且使用它们执行长时间的清理任务,对性能的影响就会非常显著。
非托管资源
二、语言基础
1、变量
● 变量是类或结构中的字段,如果没有显式初始化,则创建这些变量时,其默认值就是0。
●方法的局部变量必须在代码中显式初始化,之后才能在语句中使用它们的值。此时,初始化不是在声明该变量时进行的,但编译器会通过方法检查所有可能的路径,如果检测到局部变量在初始化之前就使用了其值,就会标记为错误。
3、一个是在类级别上定义的j,一个是在Main()中定义的j。这里,在Main()方法中声明的新变量j隐藏了同名的类级别变量,如果要引用类级别变量可以使用语法object.fieldname,
可以使用语法object.fieldname
2、常量
● 常量必须在声明时初始化。指定了其值后,就不能再改写了。
● 常量的值必须能在编译时用于计算。因此,不能用从变量中提取的值来初始化常量。如果需要这么做,应使用只读字段(详见第3章)。
● 常量总是隐式静态的。但注意,不必(实际上,是不允许)在常量声明中包含修饰符static。
3、值类型和引用类型
值类型存储在堆栈(stack)中,而引用类型存储在托管堆(managed heap)上
整型
16进制 需加上前缀0x
浮点类型
编译器一般假定该变量是double。如果想指定该值为float,可以在其后加上字符F(或f)
decimal类型
decimal类型不是基本类型,所以在计算时使用该类型会有性能损失。要把数字指定为decimal类型而不是double、float或整数类型,可以在数字的后面加上字符M(或m)
bool类型
如果变量(或函数的返回类型)声明为bool类型,就只能使用值true或false。如果试图使用0表示false,非0值表示true,就会出错
字符类型
Object类型
String类型
流程控制
switch语句
- 也可以在switch语句中包含一条default子句,如果表达式不等于任何case子句的值,就执行default子句的代码
- 也可以在switch语句中包含一条default子句,如果表达式不等于任何case子句的值,就执行default子句的代码
- 如果一条case子句为空,就可以从这条case子句跳到下一条case子句,这样就可以用相同的方式处理两条或多条case子句了(不需要goto语句)。
Foreach
Goto
枚举
- 一旦代码编译好,枚举就成为基本类型,与int和float类似
Using关键字
using关键字的另一个用途是给类和名称空间指定别名
Main方法
C#预处理器指令
#define
类似于声明一个变量,但这个变量并没有真正的值,只是存在而已。这个符号不是实际代码的一部分,而只在编译器编译代码时存在
#undef正好相反——它删除符号的定义
#if
当编译器遇到#if指令后,将先检查相关的符号是否存在,如果符号存在,就编译#if子句中的代码。否则,编译器会忽略所有的代码,直到遇到匹配的#endif指令为止
#warning和 # error
#region和#endregion
#line
#line指令可以用于改变编译器在警告和错误信息中显示的文件名和行号信息
#pragma
#pragma指令可以抑制或还原指定的编译警告。与命令行选项不同,#pragma指令可以在类或方法级别实现,对抑制警告的内容和抑制的时间进行更精细的控制。
规则
标识符
- 尽管可以包含数字字符,但它们必须以字母或下划线开头。2.
- 不能把C#关键字用作标识符。
- 可以在标识符的前面加上前缀符号@
- 标识符也可以包含Unicode字符,用语法\uXXXX来指定,其中XXXX是Unicode字符的4位十六进制编码
属性和方法的使用
果该对象的外观像变量,就应使用属性来表示它
字段
字段的用法非常简单。字段应总是私有的,但在某些情况下也可以把常量或只读字段设置为公有。原因是如果把字段设置为公有,就不利于在以后扩展或修改类。
三、类和对象
结构体(System.ValueType类)
- 结构不同于类,因为它们不需要在堆上分配空间(类是引用类型,总是存储在堆(heap)上),而结构是值类型,通常存储在栈(stack)上,另外,结构不支持继承
- 都使用关键字new来声明实例
- 因为结构是值类型,所以new运算符与类和其他引用类型的工作方式不同。new运算符并不分配堆中的内存,而是只调用相应的构造函数,根据传送给它的参数,初始化所有的字段
- 对于结构,变量声明实际上是为整个结构在栈中分配空间,所以就可以为它赋值了
- 只要把结构作为参数来传递或者把一个结构赋予另一个结构(如A=B,其中A和B是结构),结构的所有内容就被复制,而对于类,则只复制引用
- 但当把结构作为参数传递给方法时,应把它作为ref参数传递,以避免性能损失——此时只传递了结构在内存中的地址
类成员
属性
属性(property)的概念是:它是一个方法或一对方法,在客户端代码看来,它(们)是一个字段。
get访问器不带任何参数,且必须返回属性声明的类型。也不应为set访问器指定任何显式参数,但编译器假定它带一个参数,其类型也与属性相同,并表示为value。
方法
如果params关键字与方法签名定义的多个参数一起使用,则params只能使用一次,而且它必须是最后一个参数
静态构造函数
- 编写静态构造函数的一个原因是,类有一些静态字段或属性,需要在第一次使用类之前,从外部源中初始化这些静态字段和属性。
只读字段
按值和按引用传递参数
2
12
1111.1吧
2
11111
枚举
- 默认情况下,enum的类型是int。这个基本类型可以改为其他整数类型(byte、short、int、带符号的long和无符号变量)。命名常量的值从0开始递增
- 部分方法:代码应该调用可能不存在的方法,用partial关键字声明;因此不需要任何实现代码。如果没有实现代码,编译器将删除这个方法调用:部分方法必须是void类型,否则编译器在没有实现代码的情况下无法删除调用
部分类
扩展方法
- 扩展方法是静态方法,它是类的一部分,但实际上没有放在类的源代码中
- 如果类型还定义了同名的实例方法,扩展方法就永远不会使用。类中已有的任何实例方法都优先
MemberwiseClone()
四、继承
1. 多重继承允许一个类派生自多个类。C#不支持类的多重继承,但允许接口的多重继承。
2. 不能编码实现结构的类型层次,但结构可以实现接口。换言之,结构并不支持实现继承,但支持接口继承
3. 如果类和接口都用于派生,则类总是必须放在接口的前面。
Virtual
- 成员字段和静态函数都不能声明为virtual,因为这个概念只对类中的实例函数成员有意义
- 虚方法必须有实现部分,抽象方法没有提供实现部分
New和base
抽象
- 抽象方法本身也是虚拟的(尽管也不需要提供virtual关键字,实际上,如果提供了该关键字,就会产生一个语法错误)。如果类包含抽象方法,则该类也是抽象的,也必须声明为抽象的。
- 1. 如果不应创建派生自某个自定义类的类,该自定义类就应密封。给类添加sealed修饰符,就不允许创建该类的子类。密封一个方法,表示不能重写该方法。
- public、protected和private是逻辑访问修饰符。internal是一个物理访问修饰符,其边界是一个程序集。
- 一般情况下,接口只能包含方法、属性、索引器和事件的声明。
- 接口既不能有构造函数(如何构建不能实例化的对象?)也不能有字段(因为这隐含了某些内部的实现方式)。接口定义也不允许包含运算符重载
- 在接口定义中还不允许声明成员的修饰符。接口成员总是隐式为public,不能声明为virtual
- 返回对象的引用。然而,它从不抛出InvalidCastException异常。相反,如果对象不是所要求的类型,这个运算符就返回null
- is运算符根据条件是否满足,对象是否使用指定的类型,返回true或false
密封
修饰符
接口
As和is
五、泛型
1.性能
1.
2.安全
1. ArrayList类一样,如果使用对象,就可以在这个集合中添加任意类型,泛型类List<T>中,泛型类型T定义了允许使用的类型。有了List<int>的定义,就只能把整数类型添加到集合中
3、协变和抗变
对参数和返回值的类型进行转换
4、泛型方法
5、泛型类
1、默认值
2、约束
3、静态成员
6、泛型接口
六、数组
简单数组
1. 数组是引用类型,所以必须给它分配堆上的内存。为此,应使用new运算符,指定数组中元素的类型和数量来初始化数组的变量
2. 数组初始化器只能在声明数组变量时使用,不能在声明数组之后使用。
3. 如果用花括号初始化数组,则还可以不指定数组的大小,因为编译器会自动统计元素的个数:
- 使用花括号可以同时声明和初始化数组,编译器生成的代码与前面的例子相同:
锯齿状数组
Array类
- Array类是一个抽象类,所以不能使用构造函数来创建数组
- 因为数组是引用类型,所以将一个数组变量赋予另一个数组变量,就会得到两个引用同一数组的变量。而复制数组,会使数组实现ICloneable接口。这个接口定义的Clone()方法会创建数组的浅表副本。
- Clone()方法会创建一个新数组,而Copy()方法必须传递阶数相同且有足够元素的已有数组。
- Sort()方法需要数组中的元素实现IComparable接口。因为简单类型(如System.String和System.Int32)实现IComparable接口,所以可以对包含这些类型的元素排序。
- 如果对数组使用自定义类,就必须实现IComparable接口。这个接口只定义了一个方法CompareTo(),如果要比较的对象相等,该方法就返回0。如果该实例应排在参数对象的前面,该方法就返回小于0的值。如果该实例应排在参数对象的后面,该方法就返回大于0的值。
复制
排序
IEnumerator接口
Yield
- yield return语句返回集合的一个元素,并移动到下一个元素上。yield break可停止迭代。
- 数组合并了相同类型的对象,而元组合并了不同类型的对象
- 不同的泛型Tuple类支持不同数量的元素。例如,Tuple<T1>包含一个元素,Tuple<T1, T2>包含两个元素,依此类推。
元组
七、运算符
1、checked和unchecked运算符
1. byte数据类型只能包含0~255的数,给byte.MaxValue分配一个字节,得到255。对于255,字节中所有可用的8个位都得到设置:11111111。所以递增这个值会导致溢出,得到0。
2、is运算符
is运算符可以检查对象是否与特定的类型兼容。短语“兼容”表示对象或者是该类型,或者派生自该类型
3、as运算符
as运算符用于执行引用类型的显式类型转换。如果要转换的类型与指定的类型兼容,转换就会成功进行;如果类型不兼容,as运算符就会返回null值。如下面的代码所示,如果object引用实际上不引用string实例,把object引用转换为string就会返回null:
4、sizeof运算符
5、typeof运算符
typeof运算符返回一个表示特定类型的System.Type对象。例如,typeof(string)返回表示System.String类型的Type对象。在使用反射技术动态地查找对象的相关信息时
6、??空合并运算符
如果第一个操作数不是null,整个表达式就等于第一个操作数的值。● 如果第一个操作数是null,整个表达式就等于第二个操作数的值。
7、空值传播运算符?.
8、类型转换
1、使用.NET类库中提供的一些方法。Object类实现了一个ToString()方法,该方法在所有的.NET预定义类型中都进行了重写,并返回对象的字符串表示:
2、
9、相等比较
1.ReferenceEquals()
是一个静态方法,其测试两个引用是否指向类的同一个实例,特别是两个引用是否包含内存中的相同地址。作为静态方法,它不能重写
3. Equals()虚方法
Microsoft已经在System.ValueType类中重载了实例方法Equals(),以便对值类型进行合适的相等性测试。如果调用sA.Equals(sB),其中sA和sB是某个结构的实例,则根据sA和sB是否在其所有的字段中包含相同的值而返回true或false
4. 静态的Equals()方法
Equals()的静态版本与其虚实例版本的作用相同,其区别是静态版本带有两个参数,并对它们进行相等性比较。这个方法可以处理两个对象中有一个是null的情况;因此,如果一个对象可能是null,这个方法就可以抛出异常,提供额外的保护。静态重载版本首先要检查传递给它的引用是否为null。如果它们都是null,就返回true(因为null与null相等)
4. 比较==方法
Microsoft重写了这个运算符,以比较字符串的内容,而不是比较它们的引用。
10、运算符重载
八、委托和事件
委托
Delegate int Calculator(int x,int y)
Calculator c = new Calculator(Add);
Test(Calculator c,X,Y)//把方法当作参数传递 ,传递入参,返回值相同的同一类方法
{
C(x,y);
}
委托只是一种特殊类型的对象,其特殊之处在于,我们以前定义的所有对象都包含数据,而委托包含的只是一个或多个方法的地址。
多播委托只会返回最后一次注册的方法的执行结果,其他的方法执行了,但是方法的执行结果无法用变量接到。
委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递,这种将方法动态地赋给参数的做法,可以避免在程序中大量使用If-Else(Switch)语句,同时使得程序具有更好的可扩展性。
入参和返回值相同即可建立委托,赋值不同的方法
switch转换为委托
Action<T>
- 泛型Action<T>委托表示引用一个void返回类型的方法
- Action<in T>调用带一个参数的方法,Action<in T1,in T2>调用带两个参数的方法
Func<T>
Func<T>允许调用带返回类型的方法。与Action<T>类似,Func<T>也定义了不同的变体,至多也可以传递16个参数类型和一个返回类型。Func<out TResult>委托类型可以调用带返回类型且无参数的方法,Func<in T, out TResult>调用带一个参数的方法,Func<in T1, in T2, in T3,in T4, out TResult>调用带4个参数的方法。
事件
参数由委托类型定义
发布器
是一个包含事件和委托定义的对象。事件和委托之间的联系也定义在这个对象中。发布器(publisher)类的对象调用这个事件,并通知其他的对象。
这个方法的实现确认委托是否为空,如果不为空,就引发事件
订阅器
九、字符串与正则表达式
1.string常用方法
重复修改给定的字符串,效率会很低,它实际上是一个不可变的数据类型,这意味着一旦对字符串对象进行了初始化,该字符串对象就不能改变了。表面上修改字符串内容的方法和运算符实际上是创建一个新字符串
2stringbuilder
StringBuilder类有两个主要的属性:
● Length指定包含字符串的实际长度。
● Capacity指定字符串在分配的内存中的最大长度。
对字符串的修改就在赋予StringBuilder实例的内存块中进行,这就大大提高了追加子字符串和替换单个字符的效率。删除或插入子字符串仍然效率低下,因为这需要移动随后的字符串部分
一般而言,使用StringBuilder类执行字符串的任何操作,而使用String类存储字符串或显示最终结果。
不能把StringBuilder强制转换为String(隐式转换和显式转换都不行)。如果要把StringBuilder的内容输出为String,唯一的方式就是使用ToString()方法。前面介绍了StringBuilder类,说明了使用它提高性能的一些方式。但要注意,这个类并不总能提高性能。StringBuilder类基本上应在处理多个字符串时使用。但如果只是连接两个字符串,使用System.String类会比较好。
3正则表达式
十、集合
大多数集合类都可在System.Collections和System.Collections.Generic名称空间中找到。泛型集合类位于System.Collections.Generic名称空间中线程安全的集合类位于System.Collections.Concurrent名称空间中。不可变的集合类在System.Collections.Immutable名称空间中。
1列表
- 泛型类List<T>中,必须为声明为列表的值指定类型,ArrayList是一个非泛型列表,它可以将任意Object类型作为其元素。
- 元素添加到列表中后,列表的容量就会扩大,每次都会将列表的容量重新设置为原来的2倍。如果列表的容量改变了,整个集合就要重新分配到一个新的内存块中,为节省时间,如果事先知道列表中元素的个数,就可以用构造函数定义其容量,可以调用TrimExcess()方法,去除不需要的容量。但是,因为重新定位需要时间,所以如果元素个数超过了容量的90%, TrimExcess()方法就什么也不做。
- 使用List<T>类的AddRange()方法,可以一次给集合添加多个元素。因为AddRange()方法的参数是IEnumerable<T>类型的对象,所以也可以传递一个数组
- 删除
搜索
排序
- List<T>类可以使用Sort()方法对元素排序。Sort()方法使用快速排序算法,比较所有的元素,直到整个列表排好序为止。
- 如果传递给Compare方法的两个元素的顺序相同,该方法则返回0。如果返回值小于0,说明第一个参数小于第二个参数;如果返回值大于0,则第一个参数大于第二个参数。传递null作为参数时,Compare方法并不会抛出一个NullReferenceException异常。相反,因为null的位置在其他任何元素之前,所以如果第一个参数为null,该方法返回-1,如果第二个参数为null,则返回+1
队列
栈
链表
字典
1. 允许按照某个键来访问元素。字典也称为映射或散列表。字典的主要特性是能根据键快速查找值。也可以自由添加和删除元素,这有点像List<T>类,但没有在内存中移动后续元素的性能开销
2.键会转换为一个散列。利用散列创建一个数字,它将索引和值关联起来。然后索引包含一个到值的链接。该图做了简化处理,因为一个索引项可以关联多个值,索引可以存储为一个树型结构。
3. 用作字典中键的类型必须重写Object类的GetHashCode()方法。只要字典类需要确定元素的位置,它就要调用GetHashCode()方法。GetHashCode()方法返回的int由字典用于计算在对应位置放置元素的索引
- 键类型还必须实现IEquatable<T>.Equals()方法,或重写Object类的Equals()方法。因为不同的键对象可能返回相同的散列代码,所以字典使用Equals()方法来比较键。字典检查两个键A和B是否相等,并调用A.Equals(B)方法。这表示必须确保下述条件总是成立:如果A.Equals(B)方法返回true,则A.GetHashCode()和B.GetHashCode()方法必
十一、异步编程
当一个方法被调用时,调用者需要等待该方法执行完毕并返回才能继续执行,我们称这个方法是同步方法;当一个方法被调用时立即返回,并获取一个线程执行该方法内部的业务,调用者不用等待该方法执行完毕,我们称这个方法为异步方法。
windows系统是一个多线程的操作系统。一个程序至少有一个进程,一个进程至少有一个线程。进程是线程的容器,一个C#客户端程序开始于一个单独的线程,CLR(公共语言运行库)为该进程创建了一个线程,该线程称为主线程。例如当我们创建一个C#控制台程序,程序的入口是Main()函数,Main()函数是始于一个主线程的。它的功能主要是产生新的线程和执行程序
一个进程内可以包括多个应用程序域,也有包括多个线程,线程也可以穿梭于多个应用程序域当中。但在同一个时刻,线程只会处于一个应用程序域内。线程也能穿梭于多个上下文当中,进行对象的调用。
AppDomain
- 一个进程中可以有多个AppDomain,并且每个之间相互隔离(只保证安全代码的隔离,不安全代码并不能保证),此可以理解为AppDomain是.net程序中的"进程",在一个AppDomain中创建的对象只属于本AppDomain,多个AppDomain之间的对象不能相互访问,除非遵循CLR的一些规则。
- .net程序启动时在进程中创建一个默认的AppDomain,入口代码将运行于此AppDomain,默认应用程序域只有在进程终止时才会被销毁
每个AppDomain都单独的加载程序集,这意味着在A应用程序域中加载了的程序集,并不一定在B应用程序域中也被加载了。每个APPDomain有单独的Loader堆
- 有一种程序集可以被多个AppDomain使用,这种程序集叫做"AppDomain中立"的程序集,比如MSCorLib.dll,该程序集包含了System.Object,System.Int32以及其它的与.net framework密不可分的类型,这个程序集在CLR初始化时会自动加载,JIT会为这些程序集创建一个特殊的Loader堆,并且程序集中的方法被编译成的本地代码可被所有AppDomain共享,这种程序集不可被卸载只有当进程结束时这种程序集才会被卸载。
进程
线程是操作系统分配处理器时间的基本单元,在进程中可以有多个线程同时执行代码。进程之间是相对独立的,一个进程无法访问另一个进程的数据(除非利用分布式计算方式),一个进程运行的失败也不会影响其他进程的运行,Windows系统就是利用进程把工作划分为多个独立的区域的。进程可以理解为一个程序的基本边界。是应用程序的一个运行例程,是应用程序的一次动态执行过程。
线程
线程的最大并行数量上限是CPU核心的数量,但是,往往电脑运行的线程的数量远大于CPU核心的数量,所以 还是需要CPU时间片的切换。
线程(Thread)是进程中的基本执行单元,是操作系统分配CPU时间的基本单位
线程主要是由CPU寄存器、调用栈和线程本地存储器(Thread Local Storage,TLS)组成的。CPU寄存器主要记录当前所执行线程的状态,调用栈主要用于维护线程所调用到的内存与数据,TLS主要用于存放线程的状态信息。
async/await
任务
Task是在ThreadPool的基础上推出的,我们简单了解下ThreadPool。ThreadPool中有若干数量的线程,如果有任务需要处理时,会从线程池中获取一个空闲的线程来执行任务,任务执行完毕后线程不会销毁,而是被线程池回收以供后续任务使用。当线程池中所有的线程都在忙碌时,又有新任务要处理时,线程池才会新建一个线程来处理该任务,如果线程数量达到设置的最大值,任务会排队,等待其他任务释放线程后再执行。线程池能减少线程的创建,节省开销
Task的阻塞方法(Wait/WaitAll/WaitAny)
Task的延续操作(WhenAny/WhenAll/ContinueWith)
Cpu
缓存
L1最靠近CPU核心;L2其次;L3再次。运行速度方面:L1最快、L2次快、L3最慢;容量大小方面:L1最小、L2较大、L3最大。CPU会先在最快的L1中寻找需要的数据,找不到再去找次快的L2,还找不到再去找L3,L3都没有那就只能去内存找了。
L1和L2cache是每个cpu核独享的,L3cache是多个cpu核共享
超线程
CPU 上剩余的部分,也就是 UnCore 部分Core 的基础上再做扩展,将一个 Core 分裂成多个虚拟核心,即对应两组ALU
CPU计算速度是纳秒级别,内存读写却是百纳秒,那么为了充分利用CPU,可以把多项任务的数据都放在缓存里
上下文切换
调度开销中的最大成分是上下文切换,即CPU从执行一道进程或线程切换到执行另一道进程或线程的动作,如图3所示。上下文切换时要进行保护现场和恢复现场,即保存某道进程被抢占或阻塞前最后一次运行时的执行环境,和在下一次运行时复现
社会就是应用程序域,我们所住的住宅社区就是上下文的容器,社区的门卫就是上下文的行为,门卫+社区=上下文。而我们就是对象,社区的门卫对于进出社区的陌生人都会问一句:你进来找哪家?找谁?干什么的?
而真正的上下文也是干这个的
String转字符串数组
string.ToCharArray();
string.Trim()删除空格 TrimStart() TrimEnd()
string.PadLeft()在左边添加
string.Split(数组) 把string转为string数组并在指定的位置分隔开
参数数组
static int get(params int[] i){
return i[0];
}
引用参数
static void show(ref int i)
show(ref a); 必须使用初始化的变量
全局变量
Static 或const禁止修改变量的值
输出参数 可以传入未初始化的参数
public void getValues(out int x, out int y )
{
Console.WriteLine("请输入第一个值: ");
x = Convert.ToInt32(Console.ReadLine());
Console.WriteLine("请输入第二个值: ");
y = Convert.ToInt32(Console.ReadLine());
}
n.getValues(out a, out b);
它们传递的都是参数的引用
结构函数
不需要static关键字
Struct customName{
Public string firstName,lastName;
Public string Name(){
Return firstName+””+ lastName;
}
}
函数的重载
函数的签名包含函数名及参数,不能仅是返回类型不同
委托
- delegate double ProcessDelegate(double param1,double param2);声明
指定了一个返回类型和参数列表
- ProcessDelegate process; 委托变量
- process=new processDelegate(Multiply); 初始化为有相同返回类型参数列表的函数引用
- process(param1,param2); 使用委托变量调用函数
调试
常量
静态
静态类
1:仅包含静态变量和静态方法。
2:无法实例化。
3:不能包含实例构造函数。
4:是密封的。
静态方法
1.静态方法不能引用非静态变量
4静态方法只能被重载,而不能被重写,因为静态方法不属于类的实例成员;当然也不能是Virtual和abstract类型的
静态变量
1.只有一个副本,实例化类,不会初始化静态变量的值。属于类所有,生命周期和网站运用程序一样长
2.C# 不支持静态局部变量(在方法内部定义静态变量)。
类可以不显示实例化,因为内部有一个默认的静态构造函数,不可重载,当创建类实例或引用任何静态成员之前,静态构造函数被自动执行,并且只执行一次。
也可以通过 类名.方法名或变量名 访问静态成员
基础
数据类型
小数不加后缀F默认是double类型
Decimal类型 十进制 后缀M
数字类型不可为null 布尔类型可以为null
内置的 引用类型有:object、dynamic 和 string。
int num;
num = Convert.ToInt32(Console.ReadLine());
函数 Convert.ToInt32() 把用户输入的数据转换为 int 数据类型,因为 Console.ReadLine() 只接受字符串格式的数据。
字符常量是括在单引号里,
字符串常量是括在双引号 "" 里,或者是括在 @"" 里。
常量是使用 const 关键字来定义的
sizeof() |
返回数据类型的大小。 |
sizeof(int),将返回 4. |
typeof() |
返回 class 的类型。 |
typeof(StreamReader); |
& |
返回变量的地址。 |
&a; 将得到变量的实际地址。 |
* |
变量的指针。 |
*a; 将指向一个变量。 |
? : |
条件表达式 |
如果条件为真 ? 则为 X : 否则为 Y |
is |
判断对象是否为某一类型。 |
If( Ford is Car) // 检查 Ford 是否是 Car 类的一个对象。 |
as |
强制转换,即使转换失败也不会抛出异常。 |
Object obj = new StringReader("Hello"); |
C# 可空类型(Nullable)
int? num1 = null;
int? num2 = 45;
double? num3 = new double?();
Null 合并运算符( ?? )
如果第一个操作数的值为 null,则运算符返回第二个操作数的值,否则返回第一个操作数的值
double? num1 = null;
double? num2 = 3.14157;
double num3;
num3 = num1 ?? 5.34;
foreach循环
foreach (int j in n )
{
int i = j-100;
Console.WriteLine("Element[{0}]
= {1}", i, j);
}
循环的终止
string常用方法
public static int
Compare( string strA, string strB )
比较两个指定的 string 对象,并返回一个表示它们在排列顺序中相对位置的整数。该方法区分大小写
public static string
Concat( string str0, string str1 )
连接两个 string 对象。
public bool Contains( string value )
返回一个表示指定 string 对象是否出现在字符串中的值。
public static string
Copy( string str )
创建一个与指定字符串具有相同值的新的
String 对象。
public bool EndsWith(
string value )
判断 string 对象的结尾是否匹配指定的字符串。
public bool Equals( string value )
判断当前的 string 对象是否与指定的 string 对象具有相同的值。
public static string Format( string format,
Object arg0 )
把指定字符串中一个或多个格式项替换为指定对象的字符串表示形式。
public int IndexOf( string value )
返回指定字符串在该实例中第一次出现的索引,索引从 0 开始。
public string Insert( int startIndex, string
value )
返回一个新的字符串,其中,指定的字符串被插入在当前 string 对象的指定索引位置。
public static string Join( string separator, string[] value )
连接一个字符串数组中的所有元素,使用指定的分隔符分隔每个元素。
public string Replace( string oldValue,
string newValue )
把当前 string 对象中,所有指定的字符串替换为另一个指定的字符串,并返回新的字符串。
public string[] Split( params char[] separator )
返回一个字符串数组,包含当前的 string 对象中的子字符串,子字符串是使用指定的 Unicode 字符数组中的元素进行分隔的
public string ToLower()
把字符串转换为小写并返回。
public string Trim()
移除当前 String 对象中的所有前导空白字符和后置空白字符。
枚举
结构体
- · 结构可带有方法、字段、索引、属性、运算符方法和事件。
- · 结构可定义构造函数,但不能定义析构函数。但是,您不能为结构定义无参构造函数。无参构造函数(默认)是自动定义的,且不能被改变。
- · 与类不同,结构不能继承其他的结构或类。
- · 结构不能作为其他结构或类的基础结构。
- · 结构可实现一个或多个接口。
- · 结构成员不能指定为 abstract、virtual 或 protected。
- · 当您使用 New 操作符创建一个结构对象时,会调用适当的构造函数来创建结构。与类不同,结构可以不使用 New 操作符即可被实例化。
- · 如果不使用 New 操作符,只有在所有的字段都被初始化之后,字段才被赋值,对象才被使用
- 类是引用类型,结构是值类型。
- 结构不支持继承。
- 结构不能声明默认的构造函数。
- 1、结构是值类型,它在栈中分配空间;而类是引用类型,它在堆中分配空间,栈中保存的只是引用。
- 2、结构类型直接存储成员数据,让其他类的数据位于堆中,位于栈中的变量保存的是指向堆中数据对象的引用。
C# 中的简单类型,如int、double、bool等都是结构类型。如果需要的话,甚至可以使用结构类型结合运算符运算重载,再为 C# 语言创建出一种新的值类型来。
由于结构是值类型,并且直接存储数据,因此在一个对象的主要成员为数据且数据量不大的情况下,使用结构会带来更好的性能。
因为结构是值类型,因此在为结构分配内存,或者当结构超出了作用域被删除时,性能会非常好,因为他们将内联或者保存在堆栈中。当把一个结构类型的变量赋值给另一个结构时,对性能的影响取决于结构的大小,如果结构的数据成员非常多而且复杂,就会造成损失,接下来使用一段代码来说明这个问题。
结构和类的适用场合分析:
- 1、当堆栈的空间很有限,且有大量的逻辑对象时,创建类要比创建结构好一些;
- 2、对于点、矩形和颜色这样的轻量对象,假如要声明一个含有许多个颜色对象的数组,则CLR需要为每个对象分配内存,在这种情况下,使用结构的成本较低;
- 3、在表现抽象和多级别的对象层次时,类是最好的选择,因为结构不支持继承。
- 4、大多数情况下,目标类型只是含有一些数据,或者以数据为主。
enum Day { Sun, Mon, Tue, Wed, Thu, Fri, Sat };
数组
运算符重载
public static Box operator+ (Box b, Box c)
{
Box box = new Box();
box.length = b.length + c.length;
box.breadth = b.breadth + c.breadth;
box.height = b.height + c.height;
return box;
}
赋值运算符不能被重载
函数重载
同名不同参数个数、参数类型
返回值不同不算
预处理器指令
#define |
它用于定义一系列成为符号的字符。 |
#undef |
它用于取消定义符号。 |
#if |
它用于测试符号是否为真。 |
#else |
它用于创建复合条件指令,与 #if 一起使用。 |
#elif |
它用于创建复合条件指令。 |
#endif |
指定一个条件指令的结束。 |
#line |
它可以让您修改编译器的行数以及(可选地)输出错误和警告的文件名。 |
#error |
它允许从代码的指定位置生成一个错误。 |
#warning |
它允许从代码的指定位置生成一级警告。 |
#region |
它可以让您在使用 Visual Studio Code Editor 的大纲特性时,指定一个可展开或折叠的代码块。 |
#endregion |
它标识着 #region 块的结束。 |
文件操作
FileMode |
FileMode 枚举定义了各种打开文件的方法。FileMode 枚举的成员有:
|
FileAccess |
FileAccess 枚举的成员有:Read、ReadWrite 和 Write。 |
FileShare |
FileShare 枚举的成员有:
|
FileStream F = new FileStream("test.dat",
FileMode.OpenOrCreate, FileAccess.ReadWrite);
for (int i = 1; i <= 20; i++)
{
F.WriteByte((byte)i);
}
F.Position = 0;
for (int i = 0; i <= 20; i++)
{
Console.Write(F.ReadByte() + " ");
}
F.Close();
类型转换
在C#中提供的很好的类型转换方式总结为:
Object => 已知引用类型——使用as操作符完成;
Object => 已知值类型——先使用is操作符来进行判断,再用类型强转换方式进行转换;
已知引用类型之间转换——首先需要相应类型提供转换函数,再用类型强转换方式进行转换;
已知值类型之间转换——最好使用系统提供的Conver类所涉及的静态方法。
as运算符只执行引用转换和装箱转换。as运算符无法执行其它转换,如果用户定义的转换,这类转换应使用强制转换表达式来执行。
as运算符的工作方式与强制类型转换一样,只是它永远不会抛出一个异常。相反,如果对象不能转换,结果就是null。
Is
Is:检查对象是否与给定的类型兼容。例如,下面的代码可以确定MyObject类型的一个实例,或者对象是否从MyObject派生的一个类型:
标签:string,c#,程序,笔记,学习,对象,线程,类型,方法 From: https://www.cnblogs.com/axinGMX/p/18164108