Learning hard 学习笔记

第一章 你真的了解C#吗

1.什么是C#,

微软公司,面向对象,运行于.NET Framework之上,

2.C#能编写哪些应用程序,

Windows应用桌面程序,Web应用程序,Web服务,

3.什么是.NET Framework,

全面的类库,内存管理,通用类型系统,开发结构和技术,语言互操作性,

4..NET Framework组成,

公共语言运行时(CLR)和类库(FCL),

5.什么是CLR,

可以将CLR看成一个在执行时管理代码的代理,它提供了内存管理,线程管理和异常处理等服务,而且还负责对代码实施严格的类型安全检查,保证了代码的正确性,将受CLR管理的代码称为托管代码,

6.CLR由哪几部分组成,

通用类型系统(CTS)和公共语言规范(CLS),

7.什么是CTS,

CTS用于解决不同语言之间数据类型不同的问题,CTS类型主要分为两大类,引用类型和值类型,两种类型之间存在着相互转化即装箱和拆箱,

8.什么是CLS,

CLS是一种最低的语言标准,它制定了以.NET平台为目标的语言所必须支持的最小特征,

9..NET Framework类库就是一组DLL程序集的集合,程序集分为两种.exe和.dll,

10.C#与.NET Framework的关系,

C#源代码文件 >> C#编译器 >> 中间语言IL >> JIT编译器 >> 本机代码

第三章 C#语言基础

1.C#标识符必须满足的条件,

只能包含字母,数字,@,_,首位不能为数字,不能为C#关键字,

2.C#关键字,

3.C#基础数据类型,

3.1数值类型,

3.1.1整型,

3.1.2浮点型,

3.1.3十进制型,

3.1.4 布尔类型,true/false,

3.2字符串类型,

3.3枚举类型,

3.4结构体类型,

3.5数组类型,

4.什么是变量和常量,

变量代表一块内存空间,它存储的值是可以变化的,因为有了变量,不需要再去记忆复杂的内存地址,而是转为通过变量名来完成内存数据的存取操作,常量一旦被初始化就不能再次改变,使用const关键字来对常量进行定义,因为后续无法更改,常量必须在声明时就进行初始化,常量无法接受变量的赋值,即使变量是readonly(只读)也不可以,常量默认是静态的,不允许显示使用static关键字来声明,

5.运算符和表达式,

6.C#中的语句,

条件语句(if/switch),循环语句(while/do-while/for/foreach),跳转语句(break/continue/goto/return),

第四章 C#中的类

1.如果class关键字前面没有显示的指定访问修饰符,则类的访问修饰符为internal,表示仅在当前项目内可被访问,

2.类的成员的访问权限,

2.1字段,

字段最好设置为private,这样可以防止客户端直接对字段进行篡改,

2.2属性,

属性是对字段的扩展,属性定义主要由get访问器和set访问器组成,get访问器负责对字段值进行读取,set访问器负责为字段进行赋值,可以理解为两个方法,可读属性,可写属性,Ctrl+R+E,

2.3方法,

public void Test (string s)   //返回类型+参数,

{

   ~~~~~~

}

方法的访问级别,代码中为public,

方法的重载即方法的参数不同,返回类型要相同,

2.4构造函数,

构造函数主要用于创建类的实例对象,当调用构造函数创建一个对象时,构造函数会为对象分配内存空间,并初始化的类的成员,

2.4.1实例构造函数,new,

构造函数可以进行方法重载,C#编译器会自动生成空的默认无参的实例构造函数,可以对实例构造函数指定访问级别,如public,private等,构造函数必须和类同名,构造函数不允许有返回类型,

私有构造函数---------单例模式,

2.4.2静态构造函数

静态构造函数不能使用任何访问修饰符,静态构造函数不能带有任何参数,静态构造函数只会执行一次,不能直接调用静态构造函数,程序员无法控制执行静态构造函数的时机,

2.5析构函数,

析构函数用于在类销毁之前释放类实例所使用的托管和非托管资源,对于C#应用程序所创建的大多数对象,可以依靠.NET Framework的垃圾回收器(GC)来隐式的执行内存管理任务,但若创建封装了非托管资源的对象,在应用程序使用完这些非托管资源后,垃圾回收器将运行对象的析构函数即Finalize方法来释放这些资源,

析构函数不能在结构体中定义,只能对类使用,一个类只能有一个析构函数,无法继承或重载析构函数,无法显示的调用析构函数,析构函数是由垃圾回收器自动调用的,析构函数既没有修饰符也没有参数,

2.6索引器 ,

当一个类包含组成员时,索引器的使用将大大的简化对类中数组成员的访问,索引器定义类似属性,也具有get访问器和set访问器,

数据类型是类中要存取的数组的类型,索引类型表示该索引器使用哪一个类型的索引来存取数组元素,可以是整型也可以是字符串类型,this则表示所操作的是类对象的数组成员,

索引器也是一种针对私有字段进行访问的方式,但此时的私有字段是数组类型,而属性则一般只对简单数据类型的私有字段进行访问,

3.类如何实例化,

静态类中是不能定义实例构造函数的,

4.类与结构体的区别:

语法上,定义类使用关键字class,定义结构体则使用关键字struct,

结构体中不可对声明字段进行初始化,但类可以,

如果没有为类显示的定义构造函数,C#编译器会自动生成一个无参数的实例构造函数,称之为隐式构造函数,而一旦为类显示的定义了一个构造函数,C#编译器就不会在自动生成隐式构造函数了,但在结构体中,无论是否显示定义构造函数,隐式构造函数都是一直存在的,

结构体中不能显示定义无参的构造函数,这也是说明无参构造函数是一直存在的,所以不能在显示的为结构体添加一个无参的构造函数,而类可以,

在结构体的构造函数中,必须要为结构体中的所有字段赋值,

创建结构体对象可以不使用new关键字,但此时结构体对象中的字段是没有初始值的,而类必须使用new关键字来创建对象,

结构体不能继承结构或者类,但可以实现接口,而类可以继承类但不能继承结构,它也可以实现接口,

类是引用类型,而结构体是值类型,

结构体不能定义析构函数,而类可以有析构函数,

不能用abstract和sealed关键字修饰结构体,

第五章 C#中的面向对象编程

1.面向对象,

封装:把客观事物封装成类,并将类内部的实现隐藏,以保证数据的完整性,

继承:通过继承可以复用父类的代码,

多态:允许将子对象赋值给父对象的一种能力,

2.封装,

封装指的是把类内部的数据隐藏起来,不让对象实例直接对其操作,C#中提供了属性机制来对类内部的状态进行操作,封装可以通过public,private。protected和internal等关键字来体现,如一个人的年龄不可能为负数,直接对公有字段进行赋值显然是不合理的,于是将字段声明为private,为其配备public属性,在get和set两个方法中加一些逻辑判断即可,

在C#中,一个类可以继承另外一个已有的类(密封类除外),被继承的类称为基类(或父类),继承的类称为派生类(或子类),子类将获得基类除构造函数和析构函数以外的所有成员,此为,静态类是密封的,也不能被继承,子类还可以添加自己特有的成员,

子类并不能对父类的私有成员进行直接访问,它只可对保护成员和公有成员进行访问,私有成员也会被子类继承,但子类不能直接访问私有成员,子类可以通过调用保护或公有方法来间接的对私有成员进行访问,

密封类,关键字sealed,密封类不可以被另外一个类继承,

子类的初始化顺序:

初始化类的实例字段,

调用基类的构造函数,如果没有指明基类,则调用System.Object的构造函数,

调用子类的构造函数,

3.多态,

由于可以继承基类的所有成员,子类就都有了相同的行为,但有时子类的某些行为需要相互区别,子类需要覆写父类中的方法来实现子类特有的行为,这样的技术在面向对象编程中就是多态,

只有基类成员声明为virtual和abstract时,才能被派生类重写,如果子类想改变虚方法的实现行为,则必须使用override关键字,

如果子类还想继续访问基类定义的方法,可使用关键字base来完成调用,

使用abstract关键字来防止创建抽象类的实例,

阻止派生类重写虚成员也可使用关键字sealed,

如果想在派生类中定义与基类成员同名的成员,则可使用new关键字把基类成员隐藏起来,

如果此时仍然想访问基类的成员,则可使用强制类型转换,把子类强制转换成基类类型,从而访问隐藏的基类成员,

所有类的父类,System.object,

第六章 C#也有接口

1.C#提出了接口的方式,作为替代版的多重继承,

接口,

接口可以理解为对一组方法声明进行的统一命名,但这些方法没有提供任何实现,也就是说,把一组方法声明在一个接口中,然后继承于该接口的类都需要实现这些方法,

通过接口,你可以对方法进行统一管理,避免了在每种类型中重复定义这些方法,

接口使用关键字interface进行定义,

接口中定义方法不能添加任何访问修饰符,因为接口中的方法默认为public,如果显示的指定了修饰符,则会出现编译错误,

接口中除了可以定义方法外,还可以包含属性,事件,索引器,或者类,但接口中不能包含字段,运算符重载,实例构造函数和析构函数,

接口中的所有成员都默认是公共的,因此不能再使用public,private,protected等访问修饰符进行修饰,也不能使用static修饰符,

继承接口,: ICustomCompare

调用接口中的方法,

显示接口,即明确指出实现哪一个接口中的哪一个方法,

当多个接口中包含相同方法名称,相同返回类型和相同参数时,如果一个类同时实现了这些接口,隐式的接口实现就会出现命名冲突的问题,

使用显示接口需注意的问题:

若显示实现接口,方法不能使用任何访问修饰符,显示实现的成员都默认为私有,

显示实现的成员默认是私有的,所以这些成员都不能通过类的对象进行访问,正确的访问方式是把对象显示的转换为对应的接口,再来调用方法,

隐式接口与显示接口的区别及使用选择:

采用隐式接口实现时,类和接口都可以访问接口中的方法,而采用显示接口的实现方法,接口方法只能通过接口来完成访问,因为此时接口方法默认为私有,

当类实现单个接口时,通常使用隐式接口的实现方式,这样类的对象可以直接去访问接口方法,

当类实现了多个接口,并且接口中包含相同的方法名称,参数和返回类型时,则应使用显示接口实现方式,显示接口实现方式可以标识出哪个方法属于哪个接口,

接口与抽象类区别:

抽象类使用abstract关键字,接口使用interface关键字,它们都不能进行实例化,

抽象类中可以包含虚方法,非抽象方法和静态成员,但接口中不能包含虚方法和任何静态成员,并且接口中只能定义方法不能有具体实现,方法的具体实现由实现类完成,

抽象类不能实现多继承,接口则支持多继承,

抽象类是对一类对象的抽象,继承与抽象类的类与抽象类为属于的关系,而类实现接口只是代表实现类具有接口声明的方法,是一种CanDo的关系,所以一般接口后都带有able字段,表示“我能做”的意思,

面向对象编程的应用,

举个例子,animals抽象父类,dog子类,子类继承抽象父类后,dog可以吃可以跑,但specialDog还可以表演杂技,这个属于能力,就用接口来做,

极客李黑浅谈C#多态的魅力

第七章 IL语言

第八章 委托

委托使得C#中的函数可以作为参数来被传递,

委托的例子,

public delegate void MyDelegate(int para1 , string para2);

方法的签名必须与委托一致,方法的签名包括参数的个数,类型和顺序,

方法的返回类型要和委托一致,注意,方法的返回类型不属于方法签名的一部分,

委托的使用步骤,

定义委托类型,声明委托变量,实例化委托,作为参数传递给方法,调用委托,

委托链,

C#中的委托也同样可以封装多个方法,C#中把封装多个方法的委托称作委托链或多路广播委托,

委托链其实就是委托类型,只是委托链把多个委托连接在一起而已,

-=与+=相同,

张子阳C#中的委托与事件Part1+Part2

第九章 事件揭秘

什么是事件,

事件涉及两类角色,事件发布者和事件订阅者,当某个事件发布后,事件发布者会发布消息,事件订阅者会接受到事件已发生的通知,并作出相应处理,其中触发事件的对象称为事件发布者,捕获事件并对其做出处理的对象称为事件订阅者,

如何定义事件,

访问修饰符一般定义为public,因为事件的订阅者需要对事件进行订阅与取消操作,定义为公共类型可使事件对其它类可见,事件定义还包含委托类型,它既可以是自定义的委托,也可以是.NET类库中预定义的委托类型EventHandler,

订阅和取消事件+=和-=,

第十章 深入理解类型

值类型主要包括简单类型,枚举类型,结构体类型等,值类型的实例通常被分配在线程的堆栈上,变量保存的内容就是实例数据本身,

引用类型的实例则被分配在托管堆上,变量保存的是实例数据的内存地址,引用类型主要包括类类型,接口类型,委托类型和字符串类型等,

值类型通常被分配到线程的堆栈上,引用类型则被分配到托管堆上,值类型的管理由操作系统负责,引用类型的管理则由垃圾回收器GC负责,管理主要是指对内存空间进行分配和释放,因为内存大小毕竟是有限的,

如上,值类型与引用类型的区别在于实际数据的存储位置,值类型的变量和实际数据都存储在堆栈中,而引用类型则只有变量存储在堆栈中,变量存储着实际数据的地址,实际数据存储在与地址相对应的托管堆中,

前面叙述值类型实例的存在位置时,用到了“通常”这个词,就是说值类型实例不一定总会分配在线程栈上,在引用类型中嵌套值类型时,或者在值类型装箱的情况下,值类型的实例就会被分配到托管堆中,

引用类型中嵌套定义值类型,

如果类的字段类型是值类型,它将作为引用类型实例的一部分,被分配到托管堆中,但那些作为局部变量的值类型,则仍然会被分配到线程堆栈中,

值类型中嵌套定义引用类型,

值类型嵌套定义引用类型时,堆栈上将保存该引用类型的引用,而实际的数据则依然保存在托管堆中,

结论:值类型实例总会被分配到它声明的地方,声明的是局部变量时,将被分配到栈上,而声明为引用类型成员时,则被分配到托管堆上,,,而引用类型实例总是分配到托管堆上, 

值类型与引用类型的区别:

值类型继承自ValueType,ValueType又继承自System.Object,而引用类型则直接继承于System.Object,

值类型的内存不受GC控制,作用域结束后,值类型会被操作系统自行释放,从而减少了托管堆的压力,而引用类型的内存管理则由GC来完成,所以与引用类型相比,值类型在性能方面更具优势,

若值类型是密封的(sealed),你将不能把值类型作为其它任何类型的基类,而引用类型则一般具有继承性,这里指的是类和接口,

值类型不能为null值,它会被默认初始化为数值0,而引用类型在默认情况下会初始化为null值,表示不指向托管堆中的任何地址,

由于值类型变量包含其实际数据,因此在默认情况下,值类型之间的参数传递不会影响变量本身,而引用类型变量保存的是数据的引用地址,它们作为参数传递时,参数会发生改变,从而影响引用类型变量的值,

装箱与拆箱,

类型转换指的是将一种数据类型转换成另一种数据类型的过程,

隐式类型转换,由低级别类型向高级别类型的转换过程,如派生类可以隐式的转换为它的父类,装箱过程就属于这种隐式类型转换,

显示类型转换,也叫强制类型转换,这种转换这能会导致精度损失或者出现运行时异常,

通过is和as运算符进行安全的类型转换,

通过.NET类库中的Convert类来完成类型转换,

装箱指的是将值类型转换为引用类型的过程,而拆箱指的是将引用类型转换为值类型,装箱过程中,系统会在托管堆中生成一份堆栈中值类型对象的副本,而拆箱则是从托管堆中将引用类型所指向的已装箱数据复制回值类型对象的过程,

装箱三步骤:

内存分配,在托管堆中分配好内存空间以存放复制的实际数据,

完成实际数据的复制,将值类型实例的实际数据复制到新分配的内存中,

地址返回,将托管堆中的对象地址返回给引用类型变量,

拆箱三步骤:

检擦实例:首先检查要进行拆箱操作的引用类型变量是否为null,如果为null,则抛出NullReferenceException异常,如果不为null,则继续检查变量是否和拆箱后的类型是同一类型,若结果为否,会导致InvalidCastException,

地址返回,返回已装箱变量的实际数据部分的地址,

数据复制,将托管堆中的实际数据复制到栈中,

装箱和拆箱操作对性能有很大的影响,两个过程都需要进行复制,耗时,并且装箱拆箱过程中必然会产生多余对象,进一步加重GC的负担,导致程序性能降低,写代码时尽量避免装箱和拆箱,最好使用泛型编程,

第十一章 泛型

泛型提供了代码重用的另一种机制,它不同于面向对象中通过继承方式实现的代码重用,更确切的说,泛型所提供的代码重用是算法的重用,即某个方法实现不需要考虑所操作数据的类型,

泛型的英文表述是generic,这个单次意为“通用的”,从字面意思可知,泛型代表的就是“通用类型”,它可以代替任意的数据类型,使类型参数化,从而达到只实现一个方法就可以操作多种数据类型的目的,

List<T>是.NET类库中实现的泛型类型,T是泛型参数(可以理解为形参),如果想实例化一个泛型类型,必须传入实际的类型参数,如代码中的int和string,就是实际的类型参数,

为什么要引入泛型,

两个比较方法的大部分代码非常相似,如果只定义一个比较方法就能比较不同类型的大小该多好,

泛型,

泛型List<int>的Add( )和非泛型ArrayList的.Add(object valu)相比,同时for循环添加10000000个int类型,泛型比非泛型性能高,因为其避免了装箱操作,泛型还可以保证类型安全,

全面解析泛型,

类型参数,

T就是类型参数,无论调用类型方法还是初始化泛型实例,都需要用真实类型来代替T,你可以把T理解为类型的一个占位符,即告诉编译器,再调用泛型时必须为其指定一个实际类型,没有为类型参数提供实际类型称为未绑定的泛型,指定了实际类型作为参数称为已构造的泛型,

泛型中静态字段和静态函数问题,

普通类中定义一个静态字段x,不管之后创建了多少个该类的实例,也不管该类派生出多少实例,都只存在一个静态字段x,但泛型类并非如此,每个封闭的泛型类型中都有仅属于它自己的静态数据,

类型参数的推断,即省略<>符号,

以上代码中,编译器会根据传递的方法实参来判断传入的实际类型参数,如果编译器根据传入的参数不能推断出实际参数类型,就会出现编译错误,

从这段代码可以看出,适用类型推断后,可以省略<>符号,从而在泛型代码变多时,增强代码的可读性,类型推断只能用于泛型方法,它对泛型类则并不适用,因为编译器不能通过泛型类的构造函数推断实际的类型参数,

类型参数的约束,

where T : IComparable ,where语句用来使类型参数继承于IComparable接口,从而对类型参数进行约束,

让C#编译器知道这个类型参数一定会有CompareTo方法,

第十二章 可空类型 匿名方法和迭代器

可空类型也是值类型,但它是包含null值得值类型,如,int? nullable = null;

int?,就是可空的int类型,C#中肯定没有int?这个类型,对于编译器而言,int?会被编译成Nullable<int>类型,

可空类型两种情况,有值情况和无值情况,通过HasValue属性来判断可空类型是否有值,

??空合并操作符,它会对左右两个操作数进行判断,如果左边的数不为null,就返回左边的数,如果左边的数为null,就返回右边的数,这个操作符可以用于可空类型,也可以用于引用类型,但是不能用于值类型,因为??运算符会将其左边的值与null进行比较,但除了可空类型外,其它的值类型都是不能与null类型进行比较的,所以??运算符不能应用于值类型,

可空类型

匿名方法,就是没有名字的方法,匿名方法只能在函数定义的时候被调用(匿名方法是把方法的定义和实现嵌套在了一起),在其它任何情况下都不能被调用,对于编译器来说,匿名方法并不是没有名字的,编译器在编译匿名方法时会为其生成一个方法名,

匿名方法不能在其它地方被调用,即不具有复用性,

匿名方法

迭代器,foreach就是在使用迭代器,C#1.0中,一个类型要想使用foreach关键字进行遍历,它必须实现IEnumerable或IEnumerable<T>接口,因为foreach是迭代语句,想要使用foreach就需要有一个迭代器,IEnumerable接口中定义了一个GetEnumerator方法用来返回迭代器,类型如果实现了IEnumerable接口,则也必须实现GetEnumerator方法,

要获得迭代器就必须实现IEnumerable接口中的GetEnumerator方法,而要实现一个迭代器,则必须实现IEnumerable接口中的MoveNext和Reset方法,C#2.0中,微软提供了yield关键字来简化迭代器的实现,

寒江独钓详解C#迭代器

第十三章 C#3.0中的智能编译器(自动的属性 隐式类型 对象集合初始化器 匿名类型)

自动实现的属性,C#3.0之前,

C#3.0之后,

之所以可以这样定义属性,主要是因为编译器在编译时会帮助我们创建一个私有字段,

在结构体中struct使用自动属性时,需注意,所有构造函数都需要显示的调用无参构造函数this,否则会出现编译错误,

隐式类型,

这样是会报错的哦,无法将类型int隐式转换为string,

使用隐式类型时有一些限制:

被声明的变量是一个局部变量,不能为字段(包括静态字段和实例字段),

变量在声明时必须被初始化,因为编译器要根据变量的赋值来推断类型,如果未被初始化,编译器也就无法完成推断了,C#是静态语言,变量类型未知就会出现编译时的错误,

变量不能初始化为一个方法组,也不能为一个匿名函数,

变量不能初始化为null,因为null可以隐式的转换为任何引用类型或可空类型,编译器将不能推断出该变量到底为什么类型,

不能用一个正在声明的变量来初始化隐式类型,如,string s;   var stringvariable = s;

不能用var来声明方法中的参数类型,

隐式类型的优点和缺点,

隐式类型数组,

C#构造函数中this和base的使用参考(1)

C#构造函数中this和base的使用参考(2)

对象集合初始化器,

C#3.0之前定义类,往往需要定义多个构造函数来完成不同情况下的初始化,C#3.0之后引入对象初始化器后,

要使用对象初始化器,必须确保类具有一个无参构造函数,如果自定义了一个有参构造函数而把无参构造函数覆盖,你则需要重新定义一个无参构造函数,否则会出现编译错误,

如果类中没有定义任何构造函数,编译器会帮我们生成一个无参的构造函数,

再说说集合初始化器,它用来完成对集合中某一元素的初始化,在集合初始化器提出之前,

有了集合初始化器特性之后,

匿名类型,顾名思义就是没有指明类型的类型,通过隐式类型和对象初始化器两种特性创建了一个类型未知的对象,使我们再不定义类型的情况下可以实现对象的创建,从而减少类定义过程的代码,减少了开发人员的工作量,

第十四章 Lambda表达式

Lambda表达式可以理解为一个匿名方法,它可以包含表达式和语句,并且用于创建委托或转换为表达式树,在使用Lambda表达式时,都会使用“=>”运算符,该运算符的左边是匿名方法的输入参数,右边则是表达式或语句块,

Lambda表达式的演变过程,

namespace _14._1
{
    class Program
    {
        static void Main(string[] args)
        {
            // Lambda表达式的演变过程
            // 下面是C# 1中创建委托实例的代码
            Func<string, int> delegatetest1 = new Func<string, int>(Callbackmethod);

            //// C# 2中用匿名方法来创建委托实例,此时就不需要额外定义回调方法Callbackmethod
            Func<string, int> delegatetest2 = delegate(string text)
            {
                return text.Length;
            };
            //// C# 3中使用Lambda表达式来创建委托实例
            Func<string, int> delegatetest3 = (string text) => text.Length;

            //// 可以省略参数类型string,把上面代码再简化为:
            Func<string, int> delegatetest4 = (text) => text.Length;

            //// 此时可以把圆括号也省略,简化为:
            Func<string, int> delegatetest = text => text.Length;

            // 调用委托
            Console.WriteLine("使用Lambda表达式返回字符串的长度为: " + delegatetest("learning hard"));
            Console.Read();
        }

        // 回调方法
        private static int Callbackmethod(string text)
        {
            return text.Length;
        }
    }
}

Lambda表达式的使用可以明显减少代码的书写量,从而有利于开发人员更好的维护代码,理清程序的结构,

Lambda表达式除了可以用来创建委托外,还可以转换成表达式树,表达式树是用来表示Lambda表达式逻辑的一种数据结构,它将代码表示成一个对象树,而非可执行的代码,你可以将表达式树理解为一种数据结构,即类似于数据结构课程中的栈和队列,只不过表达式树用于表示Lambda表达式的逻辑罢了,

动态的构造一个表达式树,

namespace _14._6
{
    class Program
    {
        // 构造a+b表达式树结构
        static void Main(string[] args)
        {
            // 表达式的参数
            ParameterExpression a = Expression.Parameter(typeof(int), "a");
            ParameterExpression b = Expression.Parameter(typeof(int), "b");
            
            // 表达式树主体部分
            BinaryExpression be = Expression.Add(a, b);

            // 构造表达式树
            Expression<Func<int, int, int>> expressionTree = Expression.Lambda<Func<int, int, int>>(be, a, b);

            // 分析树结构,获取表达式树的主体部分
            BinaryExpression body = (BinaryExpression)expressionTree.Body;

            // 左节点,每个节点本身就是一个表达式对象
            ParameterExpression left = (ParameterExpression)body.Left;

            // 右节点
            ParameterExpression right = (ParameterExpression)body.Right;

            // 输出表达式树结构
            Console.WriteLine("表达式树结构为:"+expressionTree);
            // 输出
            Console.WriteLine("表达式树主体为:");
            Console.WriteLine(expressionTree.Body);
            Console.WriteLine("表达式树左节点为:{0}{4} 节点类型为:{1}{4}{4} 表达式树右节点为:{2}{4} 节点类型为:{3}{4}", left.Name, left.Type, right.Name, right.Type, Environment.NewLine);
            
            Console.Read();
        }
    }
}

 

通过Lambda表达式来构造表达式树,

namespace _14._4
{
    class Program
    {
        static void Main(string[] args)
        {
            // 将lambda表达式构造成表达式树
            Expression<Func<int, int, int>> expressionTree = (a, b) => a + b;

            // 获得表达式树参数
            Console.WriteLine("参数1:{0},参数2: {1}", expressionTree.Parameters[0], expressionTree.Parameters[1]);
            // 获取表达式树的主体部分
            BinaryExpression body = (BinaryExpression)expressionTree.Body;

            // 左节点,每个节点本身就是一个表达式对象
            ParameterExpression left = (ParameterExpression)body.Left;

            // 右节点
            ParameterExpression right = (ParameterExpression)body.Right;

            Console.WriteLine("表达式树主体为:");
            Console.WriteLine(expressionTree.Body);
            Console.WriteLine("表达式树左节点为:{0}{4} 节点类型为:{1}{4}{4} 表达式树右节点为:{2}{4} 节点类型为:{3}{4}", left.Name, left.Type, right.Name, right.Type, Environment.NewLine);
            Console.Read();
        }
    }
}

如何把表达式树转换成可执行代码,

namespace _14._5
{
    class Program
    {
        static void Main(string[] args)
        {
            // 将lambda表达式构造成表达式树
            Expression<Func<int, int, int>> expressionTree = (a, b) => a + b;
            // 通过调用Compile方法来生成Lambda表达式的委托
            Func<int,int,int> delinstance =expressionTree.Compile();
            // 调用委托实例获得结果
            int result = delinstance(2, 3);
            Console.WriteLine("2和3的和为:" + result);
            Console.Read();
        }
    }
}

园子的蜗牛C#表达式树讲解1-4

第十五章 扩展方法

扩展方法,首先是一种方法,它可以用来扩展已定义类型中的方法成员,

在扩展方法诞生前,如果想为一个已有类型自定义含有特殊逻辑的新方法时,你必须重新定义一个类型来继承已有类型,以这种方式来添加方法,如果基类有抽象方法,则还要重新去实现这些抽象方法,这样为了扩展一个方法,我们需要承担更多因继承而产生的开销,使用继承来扩展现有类型总有点大材小用的的感觉,并且值类型或密封类等也不能被继承,不能由此而获得扩展,

扩展方法的使用,

扩展方法的定义规则:

扩展方法必须在一个非嵌套,非泛型的静态类中定义,

它至少要有一个参数,

第一个参数必须加上this关键字作为前缀(第一个参数类型也称为扩展类型,即指方法对这个类型进行扩展),

第一个参数不能使用任何其它的修饰符,如ref,out等修饰符,

第一个参数的类型不能是指针类型, 

编译器如何发现扩展方法,

对于C#3.0编译器而言,当它看到某个类型的变量再调用方法时,它会首先去该对象的实例方法中进行查找,如果没有找到与调用方法同名并参数一致的实例方法,编译器就会去查找是否存在合适的扩展方法,

在VS中,扩展方法前面都有一个向下的箭头标志,这只是人们识别扩展方法的方式,编译器则会根据System.Runtime.CompilerServices.ExtensionAttribute属性来识别扩展方法,

从编译器发现扩展方法的过程来看,方法调用的优先级顺序应为:

类的实例方法--》当前命名空间下的扩展方法--》导入命名空间的扩展方法,

根据下面代码思考一下,

namespace CurrentNamespace
{
    // 要使用不同命名空间的扩展方法前要先引入该命名空间
    using CustomNamesapce;
    class Program
    {
        static void Main(string[] args)
        {
            Person p = new Person { Name = "Learning hard" };
            p.Print();
            
            p.Print("Hello");
            Console.Read();
        }
    }




    // 自定义类型
    public class Person
    {
        public string Name { get; set; }
        //public void Print()
        //{
        //    Console.WriteLine("Person类的实例方法");
        //}
    }

    // 当前命名空间下的扩展方法定义
    public static class Extensionclass
    {
       
        // 扩展方法定义
        public static void Print(this Person per)
        {
            Console.WriteLine("调用的是当前命名空间下的扩展方法输出,姓名为: {0}", per.Name);
        }
    }
}

namespace CustomNamesapce
{
    using CurrentNamespace;

    public static class CustomExtensionClass
    {  
        // 扩展方法定义
        public static void Print(this Person per)
        {
            Console.WriteLine("调用的是CustomNamesapce命名空间下扩展方法输出,姓名为: {0}", per.Name);
        }
     
        // 扩展方法定义
        public static void Print(this Person per, string s)
        {
            Console.WriteLine("调用的是CustomNamesapce命名空间下扩展方法输出,姓名为: {0}, 附加字符串为{1}", per.Name, s);
        }
    }
}

如果扩展的类型中定义了无参的Print实例方法,则在p后面键入“.”运算符时,VS智能提示将不会给出扩展方法,

使用扩展方法时需要特别注意,如果在同一个命名空间下的两个类中含有扩展类型相同的方法,编译器便不知道该调用哪个方法了,就会出现编译错误,

namespace CurrentNamespace
{
    class Program
    {
        static void Main(string[] args)
        {
            Person p = new Person { Name = "Learning hard" };
            // 由于同一命名空间下存在两个Print扩展方法,此时编译器不能判断调用哪个,则出现编译错误
            //p.Print();
            Console.Read();
        }
    }

    // 自定义类型
    public class Person
    {
        public string Name { get; set; }
    }

    // 当前命名空间下的扩展方法定义
    public static class Extensionclass1
    {
        /// <summary>
        ///  扩展方法定义
        /// </summary>
        /// <param name="per"></param>
        public static void Print(this Person per)
        {
            Console.WriteLine("调用的是当前命名空间下的扩展方法输出,姓名为: {0}", per.Name);
        }
    }

    // 当前命名空间下的扩展方法定义
    public static class Extensionclass2
    {
        
        // 同一命名空间下定义了两个相同的扩展方法Print
        public static void Print(this Person per)
        {
            Console.WriteLine("调用的是当前命名空间下的扩展方法输出,姓名为: {0}", per.Name);
        }
    }
}

空引用也可以调用扩展方法,

在c#中,空引用即null上调用实例方法是会引发NullReferenceException异常的,但在空引用上却可以调用扩展方法,

namespace CurrentNamespace
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("空引用上调用扩展方法演示:");

            // s为空引用
            string s = null;

            // 在空引用上调用扩展方法不会发生NullReferenceException异常
            Console.WriteLine("字符串S为空字符串:{0}", s.IsNull());

            Console.Read();
        }
    }

    public static class NullExten
    {
        // 不规范定义扩展方法的方式
        //public static bool IsNull(this object obj)
        //{
        //    return obj == null;
        //}

        public static bool IsNull(this string s)
        {
            return s == null;
        }
    }
}

 CSDN C#扩展方法详解

第十六章 LINQ解析

LINQ即语言集成查询的意思,

它主要包含4个组件,Linq to Objects,Linq to XML,Linq to DataSet和Linq to SQL,对不同的数据类型进行增删改查等操作,

查询表达式,

点标记方式,

使用Linq to Objects查询集合,

namespace _16._1
{
    class Program
    {
        static void Main(string[] args)
        {
            // 初始化查询的数据
            List<int> inputArray = new List<int>();
            for (int i = 1; i < 10; i++)
            {
                inputArray.Add(i);
            }

            Console.WriteLine("使用老方法来对集合对象查询,查询结果为:");
            OldQuery(inputArray);
            Console.Read();
        }

        // 使用foreach返回集合中为偶数的元素
        private static void OldQuery(List<int> collection)
        {
            // 创建保存查询结果的集合
            List<int> queryResults = new List<int>();
            foreach (int item in collection)
            {
                // 判断元素是偶数情况
                if (item % 2 == 0)
                {
                    queryResults.Add(item);
                }
            }

            // 输出查询结果
            foreach (int item in queryResults)
            {
                Console.Write(item+"  ");
            }
        }
    }
}
namespace _16._2
{
    class Program
    {
        static void Main(string[] args)
        {
            // 初始化查询的数据
            List<int> inputArray = new List<int>();
            for (int i = 1; i < 10; i++)
            {
                inputArray.Add(i);
            }
            Console.WriteLine("使用Linq方法来对集合对象查询,查询结果为:");
            LinqQuery(inputArray);
        }

        // 使用Linq返回集合中为偶数的元素
        private static void LinqQuery(List<int> collection)
        {
            // 创建查询表达式来获得集合中为偶数的元素
            var queryResults = from item in collection
                               where item % 2 == 0
                               select item;
            // 输出查询结果
            foreach (var item in queryResults)
            {
                Console.Write(item+"  ");
            }
        }
    }
}

使用Linq to XML查询XML文件,

namespace _16._3
{
    class Program
    {
        // 初始化XML数据
        private static string xmlString =
            "<Persons>" +
            "<Person Id='1'>" +
            "<Name>张三</Name>" +
            "<Age>18</Age>" +
            "</Person>" +
            "<Person Id='2'>" +
            "<Name>李四</Name>" +
            "<Age>19</Age>" +
            "</Person>" +
             "<Person Id='3'>" +
            "<Name>王五</Name>" +
            "<Age>22</Age>" +
            "</Person>" +
            "</Persons>";

        static void Main(string[] args)
        {
            Console.WriteLine("使用XPath来对XML文件查询,查询结果为:");
            OldLinqToXMLQuery();
            Console.Read();
        }     

        // 使用XPath方式来对XML文件进行查询
        private static void OldLinqToXMLQuery()
        {
            // 导入XML文件
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.LoadXml(xmlString);

            // 创建查询XML文件的XPath
            string xPath = "/Persons/Person";

            // 查询Person元素
            XmlNodeList querynodes = xmlDoc.SelectNodes(xPath);
            foreach (XmlNode node in querynodes)
            {
                // 查询名字为李四的元素
                foreach (XmlNode childnode in node.ChildNodes)
                {
                    if (childnode.InnerXml == "李四")
                    {
                        Console.WriteLine("姓名为: " + childnode.InnerXml + "  Id 为:" + node.Attributes["Id"].Value);
                    }
                }
            }
        }
    }
}
namespace _16._4
{
    class Program
    {
         // 初始化XML数据
        private static string xmlString = 
            "<Persons>"+
            "<Person Id='1'>"+
            "<Name>张三</Name>"+
            "<Age>18</Age>"+
            "</Person>" +
            "<Person Id='2'>"+
            "<Name>李四</Name>"+
            "<Age>19</Age>"+
            "</Person>"+
             "<Person Id='3'>" +
            "<Name>王五</Name>" +
            "<Age>22</Age>" +
            "</Person>"+
            "</Persons>";

        static void Main(string[] args)
        {
            Console.WriteLine("使用Linq方法来对XML文件查询,查询结果为:");
            UsingLinqLinqtoXMLQuery();
            Console.Read();
        }

        // 使用Linq 来对XML文件进行查询
        private static void UsingLinqLinqtoXMLQuery()
        {
            // 导入XML
            XElement xmlDoc = XElement.Parse(xmlString);

            // 创建查询,获取姓名为“李四”的元素
            var queryResults = from element in xmlDoc.Elements("Person")
                               where element.Element("Name").Value == "李四"
                               select element;

            // 输出查询结果
            foreach (var xele in queryResults)
            {
                Console.WriteLine("姓名为: " + xele.Element("Name").Value + "  Id 为:" + xele.Attribute("Id").Value);
            }
        }
    }
}

逆心 Linq学习笔记

第十七章 C#4.0中微小改动(可选参数 命名实参 泛型的协变与逆变)

可选参数和命名参数就如同一对好基友,因为它们经常一起使用,

可选参数重在“可选”,即在调用方法时,该参数可以明确指定实参,也可以不指定,如下代码中定义的方法就包含3个参数,一个必备参数和两个可选参数,

namespace _17._1
{
    class Program
    {
        static void Main(string[] args)
        {
           TestMethod(2, 4, "Hello");
           TestMethod(2, 14);
           TestMethod(2);
           Console.Read();
        }
        // 带有可选参数的方法
        static void TestMethod(int x, int y = 10, string name = "LearningHard")
        {
            Console.WriteLine("x={0} y={1} name={2}", x, y, name);
        }
    }
}

在以上代码中,参数x是必选参数,即调用方法必须为其指定实参,而参数y和参数name为可选参数,即可以不用为它们指定实参,

在使用可选参数时,需注意以下几个约束条件:

所有可选参数必须位于必选参数之后,例如下面的方法定义将会导致编译错误,

可选参数的默认值必须为常量,如数字,常量字符串,null,const成员和枚举成员等,如下面方法定义不合法的,

参数数组不能为可选参数,

 用ref,out关键字标识的参数不能被设置为可选参数,如下不合理示例,

命名实参,

当调用带有可选参数的方法时,如果我们省略了一个参数,编译器默认我们省略的是最后一个参数,但是如果我们只想省略第二个参数该怎么办呢, 

namespace _17._2
{
    class Program
    {
        static void Main(string[] args)
        {
            // 省略name参数
            TestMethod(2, 14);
            // 省略了y和name参数
            TestMethod(2);

            // 为部分实参指定名称,使用命名实参只省略第二个参数
            TestMethod(2, name : "Hello");
            // 为所有实参指定名称
            TestMethod(x: 2, y: 20, name: "Hello");
            Console.Read();
        }

        // 带有可选参数的方法
        static void TestMethod(int x, int y = 10, string name = "LearningHard")
        {
            Console.WriteLine("x={0} y={1} name={2}", x, y, name);
        }
    }
}

COM互操作的福音,

可选参数和命名实参是C#4.0中最简单的两个特性,它们最大的好处是简化了C#与COM组件的互操作,COM是微软曾经推崇的一种开发技术,自从有了高级语言,COM技术已经很少在用于实际开发了,如微软之前的产品,office和ie等,都是基于COM来开发的,

泛型的可变性,

协变性,协变性指的是泛型类型参数可以从一个派生类隐式的转化为基类,C#4.0引入out关键字来标记泛型参数,以示其支持协变性,下面以.NET类库中的public interface IEnumerable<out T>接口为示例,

namespace _17._5
{
    class Program
    {
        static void Main(string[] args)
        {
            // 初始化泛型实例
            List<object> listobject = new List<object>();
            List<string> liststrs = new List<string>();

            listobject.AddRange(liststrs);  //成功
            //liststrs.AddRange(listobject); // 出错
        }
    }
}

逆变性,逆变性指的是泛型类型参数可以从一个基类隐式的转化为派生类,C#4.0引入in关键字来标记泛型参数,以示其支持逆变型,下面以.NET类库中的接口public interface IComparer<in T>为例进行演示,

namespace _17._6
{
    class Program
    {
        static void Main(string[] args)
        {
            // 初始化泛型实例
            List<object> listobject = new List<object>();
            List<string> liststrs = new List<string>();

            // 初始化TestComparer实例
            IComparer<object> objComparer = new TestComparer();
            IComparer<string> stringComparer = new TestComparer();
            
            liststrs.Sort(objComparer);  // 正确

            // 出错
            //listobject.Sort(stringComparer);
        }
    }

    // 自定义类实现IComparer<object>接口
    public class TestComparer : IComparer<object>
    {
        public int Compare(object obj1, object obj2)
        {
            return obj1.ToString().CompareTo(obj2.ToString());
        }
    }
}

 

协变和逆变的注意事项:

只有接口和委托才支持协变和逆变,如Func<out TResult>,Action<in T>,类或泛型方法的类型参数都不支持协变和逆变,

协变和逆变只适用于引用类型,值类型不支持协变和逆变,所以List<int>无法转化为IEnumerable<object>

必须显示的用in或out来标记类型参数,

委托的可变性不要在多播委托中使用

深入理解C#协变逆变

那些年搞不懂的术语 概念 协变 逆变 不变体

C#泛型的协变和逆变

C#协变 逆变 看完这篇就懂了

第十八章 动态类型

1.

第十九章 你必须掌握的多线程编程

多线程编程必备知识,

namespace _19._1
{
    class Program
    {
        static void Main(string[] args)
        {
            Thread backThread = new Thread(new ThreadStart(Worker));
            backThread.IsBackground = true;   //指明该线程为后台线程,默认为前台线程,
            backThread.Start();
            Console.WriteLine("从主线程中退出");
        }

        public static void Worker()
        {
            Thread.Sleep(1000);
            Console.WriteLine("从后台线程退出");   //******
        }
    }

    //在日常生活中,你肯定会经常一心二用,例如在吃饭的时候看新闻,在看电视的时候吃零食等,
    //计算机也具有这种能力,它涉及今天要介绍的内容,多线程编程,

    //线程,进程与应用程序的概念,
    //线程是进程中的独立执行单元,对于操作系统而言,它通过调度线程来使应用程序工作,
    //一个进程中至少包含一个线程,我们把该线程称为主线程,
    //线程与进程之间的关系可以理解为,线程是进程的执行单元,操作系统通过调度线程来使应用程序工作,
    //而进程则是线程的容器,它由操作系统创建,又在具体的执行过程中创建了线程,

    //Windows支持7个相对线程优先级,ThreadPriority枚举类型,
    //Idle,Lowest,BelowNormal,Normal,AboveNormal,Highest,Time-Critical,

    //前台线程执行完毕后CLR会无条件终止后台线程的运行,

    //想要******行代码执行的三种方法,
    //1.注释掉这行代码,backThread.IsBackground = true;
    //2.加一行代码,Thread.Sleep(2000);
    //3.加一行代码来确保主线程会在后台线程结束后在运行,backThread.Join();

    //Join();
    //在某些情况下,需要两个线程同步运行,即一个线程必须等待另外一个线程结束之后才能运行,
}


namespace _19._2
{
    class Program
    {
        static void Main(string[] args)
        {
            Thread parmThread = new Thread(new ParameterizedThreadStart(Worker));
            parmThread.Start("123");
            Console.WriteLine("从主线程返回");
        }

        private static void Worker(object data)
        {
            Thread.Sleep(1000);
            Console.WriteLine("传入的参数为:" + data.ToString());
            Console.WriteLine("从线程1返回");
            Console.Read();
        }
    }

    //Thread类4个构造函数,都是委托类型,
    //Thread(ThreadStart)
    //Thread(ThreadStart,Int32)
    //Thread(ParameterizedThreadStart)
    //Thread(ParameterizedThreadStart,Int32)
}

线程的容器,线程池,

namespace _19._3
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("主线程ID = {0}", Thread.CurrentThread.ManagedThreadId);
            ThreadPool.QueueUserWorkItem(CallBackWorkItem);
            ThreadPool.QueueUserWorkItem(CallBackWorkItem, "work");
            Thread.Sleep(3000);
            Console.WriteLine("主线程退出");

        }

        private static void CallBackWorkItem(object state)
        {
            Console.WriteLine("线程池线程开始执行");
            if (state != null)
            {
                Console.WriteLine("线程池线程ID = {0} 传入的参数为 {1}", Thread.CurrentThread.ManagedThreadId, state.ToString());
            }
            else
            {
                Console.WriteLine("线程池线程ID = {0}", Thread.CurrentThread.ManagedThreadId);
            }
        }
    }

    //前面都是通过Thred类来手动创建线程的,然而线程的创建和销毁会耗费大量时间,这样的手动操作将造成性能损失,
    //因此,为了避免因通过Thread手动创建线程而造成的损失,.NET引入了线程池机制,

    //线程池是指用来存放应用程序中要使用的线程集合,你可以将它理解为一个存放线程的地方,这种集中存放的方式有利于对线程进行管理,

    //CLR初始化时,线程池中是没有线程的,在内部,线程池维护了一个操作请求队列,
    //当应用程序想要执行一个异步操作时,你需要调用QueueUserWorkItem方法来将对应的任务添加到线程池的请求队列中,
    //线程池实现的代码会从队列中提取任务,并将其委派给线程池中的线程去执行,
    //如果线程池中没有空闲的线程,线程池就会创建一个新线程去执行提取的任务,而当线程池线程完成了某个任务时,
    //线程也不会被销毁,而是返回到线程池中,等待响应另一个请求,
    //由于线程不会被销毁,所以也就避免了由此产生的性能损失,
    //由线程池创建的线程是后台线程,且它的优先级默认为Normal,

    //要使用线程池中的线程,需要调用静态方法ThreadPool.QueueUserWorkItem,以指定线程要调用的方法,
    //该静态方法有两个重载版本,
    //public static bool QueueUserWorkItem(WaitCallback callBack);
    //public static bool QueueUserWorkItem(WaitCallback callBack,Object state);
    //这两个方法用于向线程池队列添加一个工作项(work item)以及一个可选的数据状态,
    //然后,这两个方法就会立即返回,
    //工作项是指一个由callback参数标识的委托对象,被委托对象包装的回调方法将由线程池线程来执行,
    //传入的回调方法必须匹配System.Threding.WaitCallback委托类型,该委托的定义为,
    //public delegate void WaitCallback(Object state);
}


namespace _19._4
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("主线程运行");

            //创建CancellationTokenSource类实例,
            CancellationTokenSource cts = new CancellationTokenSource();

            //将该实例作为参数传入QueueUserWorkItem方法,
            //调用QueueUserWorkItem方法后,线程池会创建一个线程池线程,
            //运行该方法传入的回调函数callback,并在callback函数中执行Count函数来进行计数,
            ThreadPool.QueueUserWorkItem(callback, cts.Token);

            Console.WriteLine("按下回车键来取消操作");
            Console.Read();

            // 取消请求,
            cts.Cancel();

            Console.ReadKey();
        }

        private static void callback(object state)
        {
            CancellationToken token = (CancellationToken)state;
            Console.WriteLine("开始计数");

            // 开始计数,
            Count(token, 1000);
        }

        //Count函数中检查传入的CancellationToken类实例的状态,
        //当用户在控制台窗体中按下回车键时,该实例的IsCancellationRequested属性将返回true,因此退出Count方法,终止计数,
        private static void Count(CancellationToken token, int countto)
        {
            for (int i = 0; i < countto; i++)
            {
                if (token.IsCancellationRequested)
                {
                    Console.WriteLine("计数取消");
                    return;
                }
                Console.WriteLine("计数为: " + i);
                Thread.Sleep(300);
            }
            Console.WriteLine("计数完成");
        }
    }

    //.NET Framework提供了取消操作的模式,这个模式是协作式的,
    //为了取消一个操作,必须首先创建一个System.Threading.CancellationTokenSource对象,
}

线程同步,

namespace _19._5
{
    class Program
    {
        static int tickets = 100;

        static void Main(string[] args)
        {
            Thread thread1 = new Thread(SaleTicketThread1);
            Thread thread2 = new Thread(SaleTicketThread2);
            thread1.Start();
            thread2.Start();
            Thread.Sleep(4000);
        }

        private static void SaleTicketThread1()
        {
            while (true)
            {
                if (tickets > 0)
                    Console.WriteLine("线程一出票:" + tickets--);
                else
                    break;
            }
        }

        private static void SaleTicketThread2()
        {
            while (true)
            {
                if (tickets > 0)
                    Console.WriteLine("线程2出票:" + tickets--);
                else
                    break;
            }
        }
    }

    //线程同步技术是指在多线程程序中,为了保证后者线程,只有等待前者线程完成之后才能就执行,
    //这就好比生活中排队买票,前面的人没买到票之前,后面的人必须等待,

    //多线程应用程序可以提高程序的性能,并提供更好的用户体验,
    //然而当我们创建了多个线程后,它们就有可能同时去访问某一个共享资源,这将损坏资源中所保存的数据,
    //这种情况下,我们需要使用线程同步技术,确保某一时刻只有一个线程在操作共享资源,

    //举例来说,火车票售票系统程序允许多人同时购票,因此该系统肯定采用了多线程技术,

    //线程1和线程2在售票时,出售的火车票号码并不连续,说明该应用程序的售票过程是不正确的,这也就是多线程程序所存在的问题,
    //因为两个线程访问了同一个全局静态变量,static int tickets = 100;

    //为了避免这种情况的发生,我们需要对多个线程进行同步处理,确保在同一时间内只有一个线程访问共享资源,
    //以及保证前面的线程售票完成后,后面的线程才会访问资源,
}


namespace _19._6
{
    class Program
    {
        static int tickets = 100;

        //辅助对象,
        static object gloalObj = new object();

        static void Main(string[] args)
        {
            Thread thread1 = new Thread(SaleTicketThread1);
            Thread thread2 = new Thread(SaleTicketThread2);
            thread1.Start();
            thread2.Start();
        }

        private static void SaleTicketThread1()
        {
            while (true)
            {
                //在object对象上获得排它锁,
                Monitor.Enter(gloalObj);
                Thread.Sleep(1);
                if (tickets > 0)
                    Console.WriteLine("线程一出票:" + tickets--);
                else
                    break;
                //释放指定对象上的排它锁,
                Monitor.Exit(gloalObj);
            }
        }

        private static void SaleTicketThread2()
        {
            while (true)
            {
                Monitor.Enter(gloalObj);
                Thread.Sleep(1);
                if (tickets > 0)
                    Console.WriteLine("线程2出票:" + tickets--);
                else
                    break;
                Monitor.Exit(gloalObj);
            }
        }
    }

    //监视器对象(Monitor)能够确保线程拥有对共享资源的互斥访问权,
    //c#通过lock关键字来提供简化的语法,

    //以上代码,首先额外定义一个静态全局变量gloalObj,并将其作为参数传递给Enter方法,
    //使用Monitor锁定的对象需要为引用类型,而不能为值类型,
    //因为在将值类型变量传递给Enter方法时,它将被先装箱为一个单独的引用对象,之后再传递给Enter方法,
    //而在将变量传递给Exit方法时,也会创建一个单独的引用对象,
    //此时,传递给Enter方法的对象和传递给Exit方法的对象不同,Monitor将会引发SyncchronizationLockException异常,

    //执行过程,
    //线程1开始运行并进入while循环后,会调用Monitor.Enter函数,获取gloalObj对象上的排它锁,
    //线程1继续往下执行,调用Sleep函数,于是线程1挂起,
    //此时操作系统会调度线程2开始执行,该线程执行过程与线程1相同,进入while循环后,调用Monitor.Enter函数,
    //但此时线程1还没有释放gloalObj对象的排它锁,因此线程2在此处处于等待状态,
    //当线程1睡眠时间结束之后,线程1将继续执行,即销售一张火车票,
    //这时线程1调用Monitor.Exit函数释放gloalObj对象上的排它锁,
    //如果这个时候轮询到线程2执行的话,那么线程2的Monitor.Enter函数就可以获得gloalObj对象上的排它锁,
    //......就这样两个线程就可以同步交替的完成售票,

    //这个程序永远不会退出,
    //在售出最后一张票后,tickets变量的值减为0,假设正在运行的是线程1,
    //此时它将进入到else语句块执行break语句,这时线程1直接退出了,却没有执行Monitor.Exit函数,
    //所以线程2在Monitor.Enter函数处一直等待,导致线程2不能退出,
    //而线程2又是前台线程,使得整个程序不能退出,即发生我们常说的“死锁现象”,
    //所以必须把Monitor.Exit函数放在finally语句块中,
    //因为finally语句块中的代码即使在线程退出时也一样会执行,
}


namespace _19._7
{
    class Program
    {
        static void Main(string[] args)
        {
            int x = 0;
            //迭代次数为500万,
            const int iterationNumber = 5000000;
            Stopwatch sw = Stopwatch.StartNew();
            for (int i = 0; i < iterationNumber; i++)
            {
                x++;
            }
            Console.WriteLine("不使用锁的情况下花费的时间 :{0} ms", sw.ElapsedMilliseconds);
            sw.Restart();
            //使用锁的情况,
            for (int i = 0; i < iterationNumber; i++)
            {
                Interlocked.Increment(ref x);
            }
            Console.WriteLine("使用锁的情况下花费的时间 :{0} ms", sw.ElapsedMilliseconds);
            Console.Read();
        }
    }
}

第二十章 不得不说的异步编程

在平时的开发过程中,经常会遇到下载文件,加载资源一类的操作,它们都需要耗费一定的时间才能完成,如果这些程序的代码采用同步的方式来实现,将严重影响程序的可操作性,因为在文件下载或资源加载的过程中,我们什么都不能做,只能傻傻的等待,也无法获悉执行进度,为了解决这样的问题,异步编程就孕育而生了,

1.什么是异步编程,异步编程就是把耗时的操作放进一个单独的线程中进行处理(该线程需要将执行进度反映到界面上),由于耗时操作是在另外一个线程中被执行的,所以它不会堵塞主线程,主线程开启这些单独的线程后,还可以继续执行其他操作(例如窗体绘制等),

异步编程可以提高用户体验,避免在进行耗时操作时让用户看到程序“卡死”的 现象,

2.同步方式存在的问题,

namespace _20._1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            txbUrl.Text = "http://download.microsoft.com/download/7/0/3/703455ee-a747-4cc8-bd3e-98a615c3aedb/dotNetFx35setup.exe";
        }

        private void btnDownLoad_Click(object sender, EventArgs e)
        {
            rtbState.Text = "下载中.....";
            if (txbUrl.Text == String.Empty)
            {
                MessageBox.Show("请先输入下载地址!");
                return;
            }
            //开始下载,
            DownLoadFileSync(txbUrl.Text.Trim());
        }

        public void DownLoadFileSync(string url)
        {
            int BufferSize = 2048;
            byte[] BufferRead = new byte[BufferSize];
            string savepath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\dotNetFx35setup.exe";
            FileStream filestream = null;
            HttpWebResponse myWebResponse = null;
            if (File.Exists(savepath))
            {
                File.Delete(savepath);
            }
            filestream = new FileStream(savepath, FileMode.OpenOrCreate);
            try
            {
                //创建HttpWebRequest类实例,该实例使用HTTP协议向Web服务器发出请求,
                //然后调用GetResponse方法获得Web服务器返回的响应消息,
                //接着调用HttpWebResponse的GetResponseStream方法来获得服务器响应的网络流,
                //最后以循环方式从该网络流中读取字节数组,并把读取的字节数组写入本地文件,
                HttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create(url);   //1,
                if (myHttpWebRequest != null)
                {
                    myWebResponse = (HttpWebResponse)myHttpWebRequest.GetResponse();   //2,
                    Stream responseStream = myWebResponse.GetResponseStream();   //3,
                    int readSize = responseStream.Read(BufferRead, 0, BufferSize);
                    while (readSize > 0)
                    {
                        filestream.Write(BufferRead, 0, readSize);
                        readSize = responseStream.Read(BufferRead, 0, BufferSize);
                    }
                    rtbState.Text = "文件下载完成,文件大小为: " + filestream.Length + "" + "下载路径为:" + savepath;
                }
            }
            catch (Exception e)
            {
                rtbState.Text = "下载过程中发生异常,异常信息为:" + e.Message;
            }
            finally
            {
                if (myWebResponse != null)
                {
                    myWebResponse.Close();
                }
                if (filestream != null)
                {
                    filestream.Close();
                }
            }
        }
    }

    //单击下载按钮后,界面无法响应,程序处于假死状态,
    //既不能移动窗体也不能单击关闭和缩小按钮,而且下载中...字符串也没有被显示在界面中,

    //之所以能看到自己移动窗体的动作,是因为在窗体发生移动时,主线程监听到了该操作,并且重新绘制了新位置下的窗体,
    //在单击下载按钮后,主线程(也就是这里的UI线程)忙于执行下载操作,无法空出时间来重新绘制窗体,导致在下载过程中出现假死现象,
    //这样的程序是用户所不能接受的,异步编程可以很好的解决这个问题,.NET平台也对异步编程提供了有力的支持,
}

3.异步编程模型(APM),

APM是Asynchronous Programming Mode的缩写,即异步编程模型的意思,它允许程序用更少的线程去执行更多的操作,在.NET Framework中,要分辨某个类是否实现了异步编程模型,主要就是看该类是否实现了返回类型为IAsyncResult接口的Begin***方法和End***方法,

由于委托类型定义了BeginInvoke和EndInvoke方法,所以委托类型都实现了异步编程模型,

上面的4种方式,前3种都会堵塞调用线程,因为UI线程在调用Begin***方法进行异步操作后,会立即返回并继续执行,此时,已经有另一个线程在执行异步操作了(如文件下载操作),当UI线程执行到End***方法时,该线程会堵塞UI线程,直到异步操作完为止,所以,前3种方式虽然采用了异步编程模型,但结果却与同步方式是一样的,

最后1种方式由于是在回调方法种调用的End***方法,而回调方法又是在另一个线程中被执行,此时堵塞的只是执行异步任务的线程,完全不会堵塞UI线程,因此完美的解决了界面假死的状况,

第1种方法,

namespace _20._2
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            txbUrl.Text = "http://download.microsoft.com/download/7/0/3/703455ee-a747-4cc8-bd3e-98a615c3aedb/dotNetFx35setup.exe";
        }

        private void btnDownLoad_Click(object sender, EventArgs e)
        {
            rtbState.Text = "下载中.....";
            if (txbUrl.Text == String.Empty)
            {
                MessageBox.Show("请先输入下载地址!");
                return;
            }
            DownLoadFileAsync(txbUrl.Text.Trim());
        }

        public void DownLoadFileAsync(string url)
        {
            int BufferSize = 2048;
            byte[] BufferRead = new byte[BufferSize];
            string savepath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\dotNetFx35setup.exe";
            FileStream filestream = null;
            HttpWebResponse myWebResponse = null;
            if (File.Exists(savepath))
            {
                File.Delete(savepath);
            }
            filestream = new FileStream(savepath, FileMode.OpenOrCreate);
            try
            {
                HttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create(url);
                if (myHttpWebRequest != null)
                {
                    IAsyncResult result = myHttpWebRequest.BeginGetResponse(null, null);
                    myWebResponse = (HttpWebResponse)myHttpWebRequest.EndGetResponse(result);

                    Stream responseStream = myWebResponse.GetResponseStream();
                    int readSize = responseStream.Read(BufferRead, 0, BufferSize);
                    while (readSize > 0)
                    {
                        filestream.Write(BufferRead, 0, readSize);
                        readSize = responseStream.Read(BufferRead, 0, BufferSize);
                    }
                    rtbState.Text = "文件下载完成,文件大小为: " + filestream.Length + "下载路径为:" + savepath;
                }
            }
            catch (Exception e)
            {
                rtbState.Text = "下载过程中发生异常,异常信息为:" + e.Message;
            }
            finally
            {
                if (myWebResponse != null)
                {
                    myWebResponse.Close();
                }
                if (filestream != null)
                {
                    filestream.Close();
                }
            }
        }
    }

    //以上代码中,DownLoadFileAsync方法通过调用BeginGetResponse方法来异步的请求资源,
    //执行完该方法后会立即返回到UI线程中,UI线程继续执行代码,遇到EndGetResponse方法,此方法会堵塞UI线程,
    //使得程序的效果与同步实现的效果一样,
    //不同的是,此时下载操作由另一个线程执行,而在同步代码实现中,下载操作则由UI线程执行,
    //很显然,这样的效果同样是不能被用户接受的,
}

第4种方法,

namespace _20._4
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            txbUrl.Text = "http://download.microsoft.com/download/7/0/3/703455ee-a747-4cc8-bd3e-98a615c3aedb/dotNetFx35setup.exe";
        }

        //定义用来实现异步编程的委托,
        private delegate string AsyncMethodCaller(string fileurl);

        SynchronizationContext sc;

        private void btnDownLoad_Click(object sender, EventArgs e)
        {
            rtbState.Text = "下载中.....";
            btnDownLoad.Enabled = false;
            if (txbUrl.Text == String.Empty)
            {
                MessageBox.Show("请先输入下载地址!");
                return;
            }

            //获得调用线程的同步上下文对象,
            sc = SynchronizationContext.Current;

            AsyncMethodCaller methodCaller = new AsyncMethodCaller(DownLoadFileAsync);
            methodCaller.BeginInvoke(txbUrl.Text.Trim(), GetResult, null);
        }

        public string DownLoadFileAsync(string url)
        {
            int BufferSize = 2048;
            byte[] BufferRead = new byte[BufferSize];
            string savepath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\dotNetFx35setup.exe";
            FileStream filestream = null;
            HttpWebResponse myWebResponse = null;
            if (File.Exists(savepath))
            {
                File.Delete(savepath);
            }
            filestream = new FileStream(savepath, FileMode.OpenOrCreate);
            try
            {
                HttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create(url);
                if (myHttpWebRequest != null)
                {
                    myWebResponse = (HttpWebResponse)myHttpWebRequest.GetResponse();
                    Stream responseStream = myWebResponse.GetResponseStream();
                    int readSize = responseStream.Read(BufferRead, 0, BufferSize);
                    while (readSize > 0)
                    {
                        filestream.Write(BufferRead, 0, readSize);
                        readSize = responseStream.Read(BufferRead, 0, BufferSize);
                    }
                }
                return string.Format("文件下载完成,文件大小为:{0}, 下载路径为:{1}", filestream.Length, savepath);
            }
            catch (Exception e)
            {
                return string.Format("下载过程中发生异常,异常信息为: {0}", e.Message);
            }
            finally
            {
                if (myWebResponse != null)
                {
                    myWebResponse.Close();
                }
                if (filestream != null)
                {
                    filestream.Close();
                }
            }
        }

        //异步操作完成时需要执行的方法,
        private void GetResult(IAsyncResult result)
        {
            AsyncMethodCaller caller = (AsyncMethodCaller)((AsyncResult)result).AsyncDelegate;

            //调用EndInvoke方法等待异步调用完成并获得返回值,如果异步调用尚未完成,EndInvoke方法会一直阻止调用线程,直到异步调用完成,
            string returnstring = caller.EndInvoke(result);

            //通过获得GUI线程的同步上下文派生对象,调用Post方法使更新GUI操作方法由GUI线程执行,
            sc.Post(ShowState, returnstring);
        }

        //显示结果到RichTextBox,因为该方法是由GUI线程执行的,所以可以访问窗体控件,
        private void ShowState(object result)
        {
            rtbState.Text = result.ToString();
            btnDownLoad.Enabled = true;
        }
    }

    //在用户单击下载按钮后,以上代码将按钮设置为不可用状态,避免用户重复单击该按钮发送文件下载请求,
    //接着通过SynchronizationContext的Current属性获得UI线程的同步上下文对象,
    //出于安全考虑,.NET规定控件只能被创建它的线程访问,而此时下载文件的操作正在另外一个线程中执行,
    //故不能在该线程中访问UI线程的控件,
    //即此时要显示文件下载完成的状态信息,必须通过SynchronizationContext对象的Post方法,
    //把显示状态信息的代码推送给UI线程去执行,
    //如果在非UI线程访问控件,则会出现“不能跨线程访问控件”的异常,
    //最后通过调用委托对象的BeginInvoke方法来进行异步的文件下载操作,
    //下载完成时,将回调GetResult方法来获得操作结果,
    //单机下载按钮后,界面会立即显示下载中...字样,且界面此时可以被任意拖动和关闭,完美的解决了界面无法响应的问题,
}

4.异步编程模型(EAP),

虽然前面的异步编程模型可以解决执行耗时操作时界面无法响应的问题,但APM也同样存在着一些明显问题,如不支持对异步操作的取消以及不能提供下载进度报告等,然而对于桌面应用程序而言,进度报告和取消操作的功能是必不可少的,所以微软在.NET2.0发布时又提出了一个新的异步编程模型,基于事件的异步模式,即EAP(Event-based Asynchronous Pattern),

5.TAP又是什么,

基于任务的异步模式

该模式主要使用System.Threading.Tasks命名空间种的Task<T>类来实现异步编程,所以在采用TAP之前,首先要引入System.Threading.Tasks命名空间,

基于任务的异步模式(TAP)只使用一个方法就能表示异步操作的开始和完成,而APM却需要Begin***和End***两个方法分别表示开始和结束,EAP则要求具有以Async为后缀的方法和一个或多个事件,在基于任务的异步模式中,只需要一个以TaskAsync为后缀的方法,通过向该方法传入CancellationToken参数,我们就可以很好的完成异步编程了,而且还可以通过IProgress<T>接口来实现进度报告的功能,

总体来说,使用TAP会减少我们的工作量,使代码更加简洁,

namespace _20._5
{
    public partial class Form1 : Form
    {
        int DownloadSize = 0;
        string downloadPath = null;
        long totalSize = 0;
        FileStream filestream;

        //通知CancellationToken,告知其应被取消,
        CancellationTokenSource cts = null;

        //提供在各种同步模型中传播同步上下文的基本功能,
        SynchronizationContext sc;

        //表示一个异步操作,
        Task task = null;

        public Form1()
        {
            InitializeComponent();

            string url = "http://download.microsoft.com/download/7/0/3/703455ee-a747-4cc8-bd3e-98a615c3aedb/dotNetFx35setup.exe";
            txbUrl.Text = url;
            this.btnPause.Enabled = false;

            GetTotalSize();

            downloadPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\" + Path.GetFileName(this.txbUrl.Text.Trim());

            if (File.Exists(downloadPath))
            {
                FileInfo fileInfo = new FileInfo(downloadPath);
                DownloadSize = (int)fileInfo.Length;
                progressBar1.Value = (int)((float)DownloadSize / (float)totalSize * 100);
            }
        }

        private void btnDownLoad_Click(object sender, EventArgs e)
        {
            filestream = new FileStream(downloadPath, FileMode.OpenOrCreate);
            this.btnDownLoad.Enabled = false;
            this.btnPause.Enabled = true;

            filestream.Seek(DownloadSize, SeekOrigin.Begin);

            //捕捉调用线程的同步上下文派生对象,
            cts = new CancellationTokenSource();
            sc = SynchronizationContext.Current;

            //使用指定的操作初始化新的Task,
            task = new Task(() =>
            DownloadFileWithTAP(txbUrl.Text.Trim(), cts.Token, new Progress<int>(p =>
            {
                //通过同步上下文的Post方法,让主线程来执行更新UI的方法,
                sc.Post(new SendOrPostCallback((result) => progressBar1.Value = (int)result), p);
            })));

            //启动Task,并将它安排到当前的TaskScheduler中执行, 
            task.Start();
        }

        private void btnPause_Click(object sender, EventArgs e)
        {
            //发出一个取消请求,
            cts.Cancel();
        }

        //用TAP方式实现文件下载,
        public void DownloadFileWithTAP(string url, CancellationToken ct, IProgress<int> progress)
        {
            HttpWebRequest request = null;
            HttpWebResponse response = null;
            Stream responseStream = null;
            int bufferSize = 2048;
            byte[] bufferBytes = new byte[bufferSize];
            try
            {
                request = (HttpWebRequest)WebRequest.Create(url);
                if (DownloadSize != 0)
                {
                    request.AddRange(DownloadSize);
                }
                response = (HttpWebResponse)request.GetResponse();
                responseStream = response.GetResponseStream();
                int readSize = 0;
                while (true)
                {
                    //收到取消请求则退出异步操作,
                    if (ct.IsCancellationRequested == true)
                    {
                        MessageBox.Show(String.Format("下载暂停,下载的文件地址为:{0}
 已经下载的字节数为: {1}字节", downloadPath, DownloadSize));
                        response.Close();
                        filestream.Close();

                        //通过同步上下文的Post方法,让主线程来执行更新UI的方法,
                        sc.Post((state) =>
                        {
                            this.btnDownLoad.Enabled = true;
                            this.btnPause.Enabled = false;
                        }, null);

                        //退出异步操作,
                        break;
                    }

                    readSize = responseStream.Read(bufferBytes, 0, bufferBytes.Length);
                    if (readSize > 0)
                    {
                        DownloadSize += readSize;
                        int percentComplete = (int)((float)DownloadSize / (float)totalSize * 100);
                        filestream.Write(bufferBytes, 0, readSize);

                        //报告进度,
                        progress.Report(percentComplete);
                    }
                    else
                    {
                        MessageBox.Show(String.Format("下载已完成,下载的文件地址为:{0},文件的总字节数为: {1}字节", downloadPath, totalSize));

                        sc.Post((state) =>
                        {
                            this.btnDownLoad.Enabled = false;
                            this.btnPause.Enabled = false;
                        }, null);

                        response.Close();
                        filestream.Close();

                        break;
                    }
                }
            }
            catch (AggregateException ex)
            {
                //调用Cancel方法会抛出OperationCanceledException异常,
                //这里将任何OperationCanceledException对象都视为已处理,
                ex.Handle(e => e is OperationCanceledException);
            }
        }

        //获得文件总大小,
        private void GetTotalSize()
        {
            HttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create(txbUrl.Text.Trim());
            HttpWebResponse response = (HttpWebResponse)myHttpWebRequest.GetResponse();
            totalSize = response.ContentLength;
            response.Close();
        }
    }

    //以上代码只用了一个DownloadFileWithTAP方法就完成了下载,暂停和进度报告等功能,
    //与EAP中的3个事件相比是不是简洁多了,
    //程序首先在下载按钮的事件处理函数中,通过SynchronizationContext.Current获得了当前UI线程的同步上下文对象,
    //然后实例化了一个CancellationTokenSource对象,并告知异步方法取消操作,
    //最后通过Task类创建了一个任务对象,并调用对象的Start方法来启动该任务,
    //启动任务后,文件下载操作将由线程池线程异步执行,

    //在DownloadFileWithTAP方法中,程序通过检查CancellationToken的IsCancellationRequested属性来判断下载操作是否已被取消,
    //若收到取消请求,则关闭网络连接并退出DownloadFileWithTAP方法,从而实现暂停功能,
    //若没有接收到请求操作,DownloadFileWithTAP方法则继续执行文件下载的操作,
    //然后通过Progress<int>对象的Report方法报告进度,
}

6.c#5.0中的async和await,让异步编程So easy,

namespace _20._6
{
    public partial class Form1 : Form
    {
        int DownloadSize = 0;
        string downloadPath = null;
        long totalSize = 0;
        FileStream filestream;

        CancellationTokenSource cts = null;

        //不需要SynchronizationContext

        //Task task = null;

        public Form1()
        {
            InitializeComponent();

            string url = "http://download.microsoft.com/download/7/0/3/703455ee-a747-4cc8-bd3e-98a615c3aedb/dotNetFx35setup.exe";
            txbUrl.Text = url;
            this.btnPause.Enabled = false;

            GetTotalSize();

            downloadPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + "\" + Path.GetFileName(this.txbUrl.Text.Trim());
            if (File.Exists(downloadPath))
            {
                FileInfo fileInfo = new FileInfo(downloadPath);
                DownloadSize = (int)fileInfo.Length;
                progressBar1.Value = (int)((float)DownloadSize / (float)totalSize * 100);
            }
        }

        private async void btnDownLoad_Click(object sender, EventArgs e) 
        {
            filestream = new FileStream(downloadPath, FileMode.OpenOrCreate);
            this.btnDownLoad.Enabled = false;
            this.btnPause.Enabled = true;

            filestream.Seek(DownloadSize, SeekOrigin.Begin);

            cts = new CancellationTokenSource();

            await DownLoadFileAsync(txbUrl.Text.Trim(), cts.Token, new Progress<int>(p => progressBar1.Value = p));
        }

        private void btnPause_Click(object sender, EventArgs e)
        {
            //发出一个取消请求,
            cts.Cancel();
        }

        //C#5.0使用async和await方式实现文件下载,
        private async Task DownLoadFileAsync(string url, CancellationToken ct, IProgress<int> progress)
        {
            HttpWebRequest request = null;
            HttpWebResponse response = null;
            Stream responseStream = null;
            int bufferSize = 2048;
            byte[] bufferBytes = new byte[bufferSize];
            try
            {
                request = (HttpWebRequest)WebRequest.Create(url);
                if (DownloadSize != 0)
                {
                    request.AddRange(DownloadSize);
                }
                response = (HttpWebResponse)await request.GetResponseAsync();
                responseStream = response.GetResponseStream();
                int readSize = 0;
                while (true)
                {
                    if (ct.IsCancellationRequested == true)
                    {
                        MessageBox.Show(String.Format("下载暂停,下载的文件地址为:{0}
 已经下载的字节数为: {1}字节", downloadPath, DownloadSize));
                        response.Close();
                        filestream.Close();

                        this.btnDownLoad.Enabled = true;
                        this.btnPause.Enabled = false;

                        break;
                    }

                    readSize = await responseStream.ReadAsync(bufferBytes, 0, bufferBytes.Length);
                    if (readSize > 0)
                    {
                        DownloadSize += readSize;
                        int percentComplete = (int)((float)DownloadSize / (float)totalSize * 100);

                        await filestream.WriteAsync(bufferBytes, 0, readSize);

                        //报告进度,
                        progress.Report(percentComplete);
                    }
                    else
                    {
                        MessageBox.Show(String.Format("下载已完成,下载的文件地址为:{0},文件的总字节数为: {1}字节", downloadPath, totalSize));

                        this.btnDownLoad.Enabled = false;
                        this.btnPause.Enabled = false;

                        response.Close();
                        filestream.Close();

                        break;
                    }
                }
            }
            catch (AggregateException ex)
            {
                ex.Handle(e => e is OperationCanceledException);
            }
        }

        //获得文件总大小,
        private void GetTotalSize()
        {
            HttpWebRequest myHttpWebRequest = (HttpWebRequest)WebRequest.Create(txbUrl.Text.Trim());
            HttpWebResponse response = (HttpWebResponse)myHttpWebRequest.GetResponse();
            totalSize = response.ContentLength;
            response.Close();
        }
    }

    //以上代码可以看出,使用async和await关键字实现的异步编程与用基于任务的异步模式的实现非常类似,
    //此时,不再需要单独创建一个任务然后调用Start方法来启动它了,所有的一切都封装在了async和await关键字中,

    //另外,在async和await关键字的帮助下,你可以使用同步编程的思考方式来实现异步编程,
    //如果这段代码的DownLoadFileAsync异步方法实现中,除了在方法头多了一个async关键字,
    //以及在方法体处多了一个await关键字在外,其它都与同步方法的实现相同,
    //并且在该方法调用的btnDownLoad_Click处理函数中,也是通过await关键字的方式进行直接调用的,没有其它的额外代码,

    //第三,这段代码中也不存在获取同步上下文对象的代码,async和await关键字不会让调用方法运行在新线程中,
    //而是将方法分割成多个片段(片段的界限出现在方法内部使用await关键字的位置处),并使其中一些片段可以异步运行,
    //await关键字处的代码片段是在线程池线程上运行的,而整个方法的调用却是同步的,
    //所以此方式编程不用考虑跨线程访问UI控件的问题,从而大大降低了异步编程的出错率,
}

第二十一章 文件操作

.NET Framework提供的文件操作类基本上都位于System.IO命名空间下,

1.1File类和FileInfo类,这两个类用来操作硬盘上的文件,File类主要是通过静态方法实现的,而FileInfo类则是通过实例方法实现的,

File类的核心成员,

由于File类所提供的方法都是静态方法,所以如果只想执行一个操作,使用File类方法的效率要比使用FileInfo类方法的效率高,

namespace _21._1
{
    class Program
    {
        static void Main(string[] args)
        {
            FileStream fs = null;
            StreamWriter writer = null;
            string path = "D:\test.txt";
            if (!File.Exists(path))
            {
                fs = File.Create(path);
                Console.WriteLine("新建一个文件:{0}", path);
            }
            else
            {
                fs = File.Open(path, FileMode.Open);
                Console.WriteLine("文件已存在,直接打开");
            }
            writer = new StreamWriter(fs);
            writer.WriteLine("测试文本");
            Console.WriteLine("向文件写入文本数据");
            writer.Flush();
            writer.Close();
            fs.Close();
            Console.WriteLine("关闭数据流");
        }
    }

    //以上代码首先通过File.Exists方法判断指定路径下的某个文件是否存在,
    //如果该文件存在,则直接调用File.Open方法将文件打开,否则将调用Create方法创建一个文件,
    //然后,代码会初始化一个StreamWriter对象,实现向文本文件中写入字符串的操作,
    //最后程序会调用Flush方法清空缓冲区,将所有的缓冲区数据写入文件,并调用Close方法来关闭数据流,
}

1.2Directory类和DirectoryInfo类,这两个类中都包含了一组用来创建,移动,删除和枚举所有目录或子目录的成员,

Directory类的一些常用成员,

namespace _21._2
{
    class Program
    {
        static void Main(string[] args)
        {
            string dirPath = "D:\DirectorySample";
            string filePath = string.Format("{0}\{1}", dirPath, "test.txt");
            if (!Directory.Exists(dirPath))
            {
                Directory.CreateDirectory(dirPath);
                Console.WriteLine("创建一个目录: {0}", dirPath);
            }
            else
            {
                Console.WriteLine("目录{0}已存在", dirPath);
            }
            FileInfo file = new FileInfo(filePath);
            if (!file.Exists)
            {
                file.Create();
                Console.WriteLine("创建一个文件: {0}", filePath);
            }
            else
            {
                Console.WriteLine("文件: {0}已存在", filePath);
            }
            Console.ReadLine();
        }
    }

    //以上代码首先调用了Directory.Exists静态方法检查目录是否存在,
    //如果目录不存在就创建该目录,存在则打印目录已存在的信息,
    //然后,代码创建了一个FileInfo实例对象,并调用其Exists属性检测目录下是否存在该文件,
    //如果不存在就在该目录下创建文件,存在则直接打印文件已存在的信息,
}

2.前面曾用StreamWriter类来完成向文件中写入字符串的操作,文件操作自然离不开流的相关操作,

流(Stream)可以理解为内存中的字节序列,Stream是所有流的抽象基类,每个具体的存储实体都可以通过Stream派生类来实现,如FileStream类就表示“文件”这种存储实体,同样,流也涉及3个基本操作:

对流进行读取,将流中的数据读取到具体的数据结构(如数组等)中,

对流进行写入,把数据结构中的数据写入到流中,

对流进行查找,对流内的当前位置进行查询和修改,

Stream类的常用成员,

Stream类的继承层次结构如图,

namespace _21._3
{
    class Program
    {
        static void Main(string[] args)
        {
            string filePath = "D:\test.txt";
            using (FileStream fileStream = File.Open(filePath, FileMode.OpenOrCreate))
            {
                string msg = "Hello World";
                byte[] msgAsByteArray = Encoding.Default.GetBytes(msg);
                Console.WriteLine("开始写入 {0}到文件中", msg);
                fileStream.Write(msgAsByteArray, 0, msgAsByteArray.Length);
                fileStream.Seek(0, SeekOrigin.Begin);
                Console.WriteLine("写入文件中的数据为:");
                byte[] bytesFromFile = new byte[msgAsByteArray.Length];
                fileStream.Read(bytesFromFile, 0, msgAsByteArray.Length);
                Console.WriteLine(Encoding.Default.GetString(bytesFromFile));
                Console.Read();
            }
        }
    }

    //以上代码首先调用File.Open方法来创建一个FileStream实例对象,然后调用Write方法把字符串的字节数组写入到流中,
    //接着调用Seek方法重置流内部的位置,最后调用Read方法将数据从流中读取到字节数组,并把写入的信息输出到控制台,
}

System.IO命名空间提供了不同的读写器,以对流中的数据进行操作,这些类通常是成对出现的,一个用于从流中读取数据,另一个用于向流中写入数据,不同类型的读写器分别适用于处理文本,字符串,二进制数据和流等,

用StreamWriter和StreamReader读写器来修改代码,

namespace _21._4
{
    class Program
    {
        static void Main(string[] args)
        {
            string filePath = "D:\test.txt";
            using (FileStream fileStream = File.Open(filePath, FileMode.OpenOrCreate))
            {
                string msg = "Hello World";
                StreamWriter streamWriter = new StreamWriter(fileStream);
                Console.WriteLine("开始写入 {0} 到文件中", msg);
                streamWriter.Write(msg);
                StreamReader streamReader = new StreamReader(fileStream);
                Console.WriteLine("写入文件中的数据为:
{0}", streamReader.ReadToEnd());
                streamWriter.Close();
                streamReader.Close();
                Console.Read();
            }
        }
    }
}

3.对文件进行异步操作,

前面对文件进行的操作都是同步的,在同步操作中,如果向文件写入大量数据,方法将一直处于等待状态,直到写入完成,但若使用异步操作,方法就可以在写入操作的同时继续执行后面的操作,以FileStream类为例,介绍文件异步操作方法,

FileStream类有15个构造函数,其中只有1个构造函数可以指定异步操作,

这里,最后一个参数useAsync用于指定程序使用的是异步方式还是同步方式,如果设置为true,则表示使用异步方式来操作FileStream类,在对文件进行异步操作时,对大数据量读写操作使用BeginRead和BeginWrite方法的效果更好,而对于数据量较少的读写操作,使用异步方式的速度可能会比同步方式要慢,所以要针对应用程序的实际情况决定是否要选择异步处理方式,

namespace _21._5
{
    class Program
    {
        static void Main(string[] args)
        {
            FileStream fileStream = null;
            string filePath = "D:\test.txt";
            FileInfo fileInfo = new FileInfo(filePath);
            if (!fileInfo.Exists)
            {
                fileStream = File.Create(filePath);
                Console.WriteLine("新建文件:{0}", filePath);
                fileStream.Close();
            }
            else
            {
                Console.WriteLine("文件:{0}已存在,直接打开", filePath);
            }
            fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Write, FileShare.None, 4096, true);
            Console.WriteLine("开启异步操作: {0}", fileStream.IsAsync);
            string msg = "Hello World";
            byte[] buffer = Encoding.UTF8.GetBytes(msg);
            //开始异步操作,
            IAsyncResult asyncResult = fileStream.BeginWrite(buffer, 0, buffer.Length, new AsyncCallback(EndWriteCallback), fileStream);
            Console.WriteLine("开始异步写入,请稍候...");
            Console.Read();
        }

        //异步写操作完成后调用的方法,
        static void EndWriteCallback(IAsyncResult asyncResult)
        {
            Console.WriteLine("异步写入开始...");
            //转换为FileStream类型,
            FileStream stream = asyncResult.AsyncState as FileStream;
            if (stream != null)
            {
                stream.EndWrite(asyncResult);
                stream.Close();
            }
            Console.WriteLine("异步写入完毕!");
        }
    }

    //以上代码首先会判断文件是否存在,
    //如果文件不存在,则调用Create方法创建一个文件,然后调用Close方法关闭新创建的文件
    //(否则在运行后面代码时会出现“该文件正在被另一进程使用”的异常),
    //如果文件已存在,则会直接调用FileStream类的构造函数对FileStream类对象进行初始化,并把构造函数的最后一个参数指定为true,开启异步操作模式,
    //之后调用FileStream.BeginWrite方法,将内容异步的写入到文件流中,
}

第二十二章 网络编程

1.

原文地址:https://www.cnblogs.com/huangxuQaQ/p/11459553.html