您的位置:首页 > 编程语言 > C#

C#学习笔记(基础)

2008-11-08 09:56 344 查看
1、使用命名空间的语法using SpaceName;
2、C#可执行文件的入口点Main方法(M必须大写)
3、C#要求变量必须先被初始化然后才能被操作使用:
a、类、结构中的字段没有显示初始化则默认为0;
b、局部变量必须显示初始化;
c、引用类型必须初始化指向被引用对象;
否则编译出错
4、C#不允许隐藏变量,即,子作用域中不允许出现上层作用域同名的变量。
5、C#中只能把局部变量和字段声明为常量,没有其他常量了。
6、const常量声明时初始化,不可修改;编译时必须能确定其值,如果要总变量中提取值来初始化常量,可用只读字段;常量总是静态的,不需要也不允许常量+static修饰符
7、值类型和引用类型:值类型存储在堆栈上,而引用类型则是对存储在托管堆中的对象的引用。
8、C#中的空引用是null(小写)
9、需要自定义值类型,则定义结构
10、基本预定义类型:
===========
值类型如下:

整型:sbyte、byte 8位
ushort、short 16
ulong、long 64
uint、int 32

指定数字类型可在数字后面+u、U、l、L,注意l和1混淆。

浮点:指定为float可在数字后面+f、F

非基本类型:decimal:128位,性能损失 指定数字为decimal可在数字后面+M、m

bool:不能与整型值互换,不能0表示false,非0表示true。

char:16位 没有uchar类型

===========
引用类型如下:

object:根类型
string:字符串类型。赋值后string引用同一对象,但修改一个引用时会生成新的对象。
字符串前+@,则对字符串内容不做转义处理

11、C#中if后的条件语句必须是bool值。
12、switch的case后必须是常量表达式,可以是字符串。要么case后是空的,要么case后面必须有break。case后常量不能相同。
13、foreach对集合中各项进行只读操作。

14、goto:不能goto进入for循环中;不能跳出类的范围;不能退出try...catch后面的finally块。

15、int[] integers = new int[32]; C#中数组是引用类型,要用new生成实例,所以是动态的。
16、namespace X.X.X{}允许
17、using System;如果命名空间中类重名,则必须全名使用该类。
18、using ok = System; ok::object XXX = ....命名空间别名定义与使用。命名空间可以被返回,object.GetType().NameSpace;
19、如果存在多个Main方法,可以使用编译选项/main classname,指定入口Main函数。
20、string[] args是Main的参数。
21、C#编译成/t:exe /t:library /t:module /t:winexe。使用dll的方法:/r编译选项。
22、控制台输入输出:Console.ReadLine(); Console.WriteLine("{0, 5} is sb!/n", szOK);
{索引, 宽度:格式精度}
23、预编译命令
#define
#undef
#if
#elif
#else
#endif

允许嵌套,允许条件编译的条件为bool表达式(支持! == != || && 符号存在则为true,反之为false)。

#warning "warningWords" 给出警告信息,继续编译
#error "errorWords" 给出错误信息,停止编译

#region 给编辑器用,无语言、语义上的含义。
#endregion

#line 164 "Core.cs" 改变编译器在警告和错误信息中显示的文件名和行号信息。
#line default 恢复。

#pragma warning disable 169//抑制指定编译警告
...
pragma warning restore 169//恢复指定编译警告

24、保留字前+@可以作为标志符,例如if不是标志符,@if是标志符。标志符可以是Unicode字符,/uXXXX(16进制代码)语法即可。

25、C#代码风格约定:
a、变量命名不使用任何前缀
b、命名空间、类、方法、变量、公共|受保护的成员字段:Pascal大小写命名形式,所有单词首字母大写,其他小写。
c、私有成员字段、形参:camel大小写形式。私有成员字段,下划线开始。
d、仅大小写不同的变量会与语言的互操作性冲突。

26、结构体:值类型,堆栈上,不可被继承;类:引用类型,堆上,可被继承;字段默认值都是0
27、数据成员:字段、常量、事件。
28、函数成员:(静态)方法,属性,构造函数(类同名,无返回值),终结器(相比C++的析构很少使用,CLR自动进行垃圾收集,执行时间不可预期),运算符,索引器(以数组或者集合的方式进行索引)。
29、C#中没有全局函数,所有方法必须与类挂钩,但可以是静态方法(与类挂钩,但不与实例挂钩)。
30、C#中不能将实现代码与方法接口分离。
31、方法定义:[modifiers] return_type MethodName([parameters]){...}
注意:
a、方法的访问设置必须单独设置,即一个方法对应一个public或private。
b、返回值不可缺省,没有则是void。
32、C#静态方法调用:classname.staticmethod(),C++中:classname::staticmethod()
33、C#中string作为参数传递和值传递一样的表现。string对象是不能改变的,改变则创建新的字符串。
34、ref强制引用传递参数。相当于C++中的&。方法定义时,参数声明前+ref,调用时也需要+ref。
35、传递入函数的参数必须进行初始化。
36、使用out关键字表示用来接收返回值的参数,此时,该参数不需要初始化。声明与调用时都需要+out。
37、C#方法重载,不支持默认参数,不支持可选参数,此外,重载方法不能只有返回类型的区别,不能仅根据参数是ref还是out区分。
38、属性:客户机代码角度观察,是一个字段,但读写该字段时会触发指定操作。
语法:
public string SomeProperty
{
get//必须无参数
{
return "this is the property value"//返回属性声明相同的类型
}
set//无显式参数,但编译器假定有一个与属性同类型的参数value,值就是客户机代码赋入值。
{
//do whatever needs to be done to set the property
}
}
39、只读属性、只写属性。只有get,只有set。后者不是好的编程方式,可以用方法代替。
40、get、set访问级别可以不同。注意,get、set必须至少一个是public,否则编译错误。因为,此时语义上,可以是private 字段,而不需要是属性(供客户机代码使用)。
41、C#实例化时会对未初始化的数据成员自动初始化为默认值。
42、静态构造函数,用以初始化类的静态字段或属性,仅在第一次使用类之前执行一次。
a、没有访问修饰符
b、没有参数
c、只能访问静态成员,不能访问实例成员
d、只运行一次
43、构造函数中调用构造函数(同级构造函数):构造函数初始化器,用以初始化相同字段,可以在各个构造函数中使用。
public Car(string model, uint nWheels)
{
}
public Car(string model) : this(model, 4)
{
}
a、可以调用基类的构造函数,this替换成base即可。
b、仅调用一个构造函数。
44、只读字段:readonly关键字。可以是静态或实例的,仅在构造函数中对只读字段赋值。赋值不需要是常量,可以经过计算得到。
45、结构与类的区别:
a、结构是值类型,类是引用类型,分别存储在堆栈和托管堆中。
b、结构不支持继承。
c、编译器总对结构提供一个无参数的默认构造函数,且不允许替换。
d、结构可以指定字段如何在内存中布局。
46、结构的语法可以使用类的语法,如,使用new在生成对象。但直接以值类型的语法使用也是正确的,即声明后立即使用。
值类型来说,声明变量即意味着分配存储空间。此时的new仅仅调用构造函数而已。
47、结构派生于System.ValueType,System.ValueType派生于System.Object。所有的结构都是如此,且不能被继承。
48、结构不能提供字段的初始值,默认构造函数初始化所有字段为0。
49、partial允许把类、结构或者接口放在多个文件中。编译器自动进行合并。字段,方法,基类或接口都会被合并。
50、静态类:如果类只有静态方法与属性,则该类是静态的。使用static修饰类,编译器会检查该类必须为静态类,且不允许为该类添加实例。
51、实现继承:继承基类函数的实现代码。
接口继承:继承基类函数的签名,但不继承实现代码。
52、C#不允许多重继承,但允许继承自多个接口,即,C#中允许一个类继承自一个基类和任意多个接口。
53、结构可以继承自多个接口。
54、C#中仅有继承,即,公有继承,没有私有继承。
55、C#中如果没有指定基类,则假定System.Object是基类。
56、C#中虚函数与虚属性性质类似,基类中必须标记为virtual(访问修饰符后),同时重载时必须用override标识,重写函数不需要virtual标识。Java中所有函数都是虚拟的。
57、C#中,如果基类和派生类存在相同签名的函数,但是没有用virtual和override标识,那么派生类隐藏基类方法。如果是隐藏方法,必须在派生类中使用new修饰重名方法。
58、派生类中调用基类方法的语法:base.<methodname>()。
59、C#可以使用abstract修饰类与函数,表示抽象类和抽象函数。含有抽象函数的类必须也修饰为abstract,即抽象类。抽象类不能实例化,抽象函数没有执行代码,必须在非抽象的派生类中重写。
60、密封类与密封方法:C#中可以终止继承与重写,sealed修饰类,表示该类不能被继承;修饰函数表示该方法不能被重写。
61、sealed可以保证virtual声明的函数代码的最终形式,其他人不能重写。如果一开始就不希望被重写,就不要声明为virtual函数。
62、C#中类的构造函数既可以调用其他构造函数,也可以调用基类的构造函数。
63、Internal修饰符:只能在包含它的程序集中访问该方法。
64、C#中接口相关:
a、一般接口只能包含方法、属性、索引器、事件的声明。
b、接口不能被实例化,所以接口不允许有构造函数。
c、接口隐藏内部执行方式,所以不允许有字段。
d、接口定义也不允许包含运算符重载,因为包含运算符重载可能会引起一些与其他.net语言不兼容的问题。vb.net不支持运算符重载。
e、接口定义中还不允许声明成员上的修饰符。接口成员总是公共的,不能声明为虚拟或者静态,这由执行的类去完成。
f、接口定义的成员必须被执行的类实现。
65、接口仅仅保证其成员的存在性,类负责确定这些成员是虚拟的还是抽象的。
66、接口引用的强大之处:接口可以引用任何实现该接口的对象,调用该接口成员。如果需要调用该接口以外的方法,则必须强制转换为其他类型,然后再调用。
67、接口的派生等于生成并集。a:b,那么a包含b以及,a中不属于b的部分。
68、C#中只有不安全的代码块中,才允许使用指针。
69、checked和unchecked运算符:
a、被checked标记的代码块执行时会进行溢出检查,发生溢出就抛出异常。
b、如果编译时/checked选项使用,则检查程序中所有未标记代码中的溢出。
c、unchecked标记的代码块,则是禁止溢出检查。
d、unchecked是默认的。使用上来说,unchecked是嵌套在checked代码块中使用的,在需要检查的代码块中,禁止几处代码的溢出检查。
70、is运算符:is是否与特定类型兼容:是该类型,或者派生于该类型。
语法:if (i is object)...
71、as运算符:引用类型的显示类型转换。与类型兼容,则类型转换成功,否则返回false,类似C++的dynamic_cast<>
72、sizeof,与C++中的sizeof同。
注意:
a、只能用于值类型!
b、只能在不安全的代码中使用sizeof运算符。
73、typeof返回一个表示特定类型的System.Type对象。
74、可空类型:
语法:int? a = null;
如果使用可空类型,就必须考虑null值与各种运算符一起使用时的影响。通常,一元、二元操作符中一个操作数为null则结果是null。
可空类型在< <= == != >= >运算符时,只要有一个操作数为null,则结果为false。
75、空接合运算符:
语法:b = a ?? c;
语义:如果a不是null,则b = a,否则b = c
76、C#中两个byte加在一起,返回应当是一个int类型,而不是另一个byte(需要强制转换为byte)。这点跟C++不同。(C#的防溢出处理!)
77、隐式转换;只要能保证值不会发生任何变化,类型转换就可以自动进行。
C#支持的隐式转换的规则基本与C++类似:短的可以转换为长的(包括整型转化为浮点型)。注意:long/ulong转换为float是允许的,会丢失4个字节数据,但此仅表示得到的float值比使用double得到的值精度要低。编译器认为是可以的,所以不会给出警告与错误。无符号的只要数据在有符号范围内,则亦可以进行转换。
78、可空类型-》可空类型 同上(必须显式)
不可空类型-》可空类型 同上(隐式)
可空类型-》不可空类型 不允许隐式类型转换
79、显式转换:只有C风格的类型转换语法,注意,只有!显示转换是否溢出可以用checked关键字检查。
80、C#中类型转换失败可能触发异常,所以要有处理类型转换失败的代码(异常机制)。
可空类型转换为不可空类型时,null为源值,则会触发InvalidOperationException异常。
90、不能在bool类型和其他类型之间任意转换。这点和C++差别相当大~要从字符串中分析得到一个数字或则bool类型,可以用所有类型都有的Parse方法。
91、装箱和拆箱:
作用:值类型----》引用类型 装箱;引用类型----》值类型 拆箱
实质:装箱:运行库为栈上的对象创建一个临时性的引用类型“box”。
注意:
a、只能拆掉装过的箱子!
b、必须确保拆箱得到的值变量有足够的空间存储!否则异常!
代码范例:
long a = 3333
object o = a;//装箱
int j = (int)o;//拆箱
92、对象的相等比较:
a、引用类型的相等比较:System.Object类的方法
ReferenceEquals(),静态方法,相当于C++中的指针比较
虚拟版本的Equals()方法,也是C++中的指针比较,重点在于:可以被重写
静态版本的Equals()方法,实际上内部调用虚拟版本的Equals,但是,该版本有两个参数。如果两个参数都null,则true,一个为null,则false,再不然,调用虚拟版本的进行比较
==运算符一般是比较引用,但是System.String类被微软重载,是比较内容。
93、值类型的相等比较:System.ValueType类的方法
ReferenceEquals(),静态方法,会将值类型装箱,然后再比较引用。注意,对于各个参数会分别装箱,即使参数是同一个,这样使得各个参数绝对不会引用同一个对象,那么值类型调用该函数时,肯定返回false。
Equals()已经被System.ValueType重载,相当于C++的==,真正的值比较。可以继续被重载。
==运算符:默认,结构不提供==运算符(编译错误);可以自行重载该运算符。
94、C#要求所有的运算符重载都声明为public和static,这表示它们与它们的类或结构相关联,而不是与实例相关联,所以运算符重载的代码体不能访问非静态类成员,也不能访问this标识符;这是可以的,因为参数提供了运算符执行任务所需要知道的所有数据。
95、同一运算符亦可以重载成多种形式。注意运算符重载的形式:public static ClassName operator+(...){...}
96、比较运算符的重载。
C#中要求比较运算符必须成对重载,其中==和!= >和< >=和<=各自配对。
97、如果重载==和!=,同时应当重载Equals()和GetHashCode()方法,否则会给出编译警告。Equals()应当和==结果保持一致。
98、不要通过Equals()的静态版本来重载运算符。如果出现==左侧为null,则会出现null.Equals的调用,会异常。
99、赋值运算符不能被显示重载,会自动隐式重载。
100、自定义类型的类型转换:定义为类的一个成员运算符,数据类型必须标记为隐式或者显式。
注意语法与运算符重载的区别:
public static implicit operator float(...){...}
101、定义为隐式类型转换的运算符,既可以隐式转换,也可以显示转换。
102、uint到float是隐式转换。会有精度损失,但是微软认为这种错误并不重要。
103、选择作为隐式转换还是显式转换的原则与C#默认原则相同,虽然可以违反,但容易导致错误。
104、圆整错误:
unit dollars = (uint)value;//value是float类型
ushort cent = (ushort)((value - dollars) * 100);
若float = 50.35,cent为多少?答案是34。
value是50.35,但存储为50.34...,value - dollars = 0.34... * 100 = 34.*** 最后得到cent为34
必须用智能圆整操作:System.Convert类中有相关转换方法。
unit dollars = (uint)value;
ushort cent = Convert.ToUInt16((value - dollars) * 100);
注意:System.Convert类有性能损失,只在需要的时候使用。
此外,System.Convert有自己的溢出检查,不需要checked,但是unit dollars = (uint)value;需要。
105、类之间的数据类型转换的两个限制:
a、如果两个类之间存在继承关系,那么就不能在这两个类之间进行数据类型的转换(存在默认的数据类型的转换)。
b、数据类型转换必须在源或者目标数据类型定义的内部定义。一旦一方有了定义,就不能在另一方定义了。只能有一个数据类型转换。
106、基类和派生类之间的数据类型转换。
派生类--》基类,隐式,默认提供
基类--》派生类,自定义(构造函数的形式)
107、装箱和拆箱数据类型的转换:
结构--》引用对象,隐式,装箱过程 复制数据到托管堆中,且对引用对象的操作不会影响结构
引用对象--》结构,显示,拆箱过程 复制数据到结构中,如果类型不对,会抛出异常
108、多重数据类型转换:当进行要求的数据类型转换时,C#编译器没有可用的直接转换方式,C#编译器就会寻找一种方式,把几种转换合并起来。即如果存在a--》b,b--》c的转换,现在要求a--》c(未提供该转换),那么C#编译器可以自动进行此种转换。如果所有转换步骤都是隐式,则合并后的转换也是隐式,否则是显式转换。此种转换可以被用户自定义替换。
109、当存在多条合并路径的时候,C#有其规则做选择。最好的情况是,我们自己设计转换,是所有路径的转换都得到相同的结果。
110、某个方法有多个重载版本。如果传入参数,数据类型不符合任何一个参数版本,那么就会迫使编译器做类型转换。自动转换会进行,但不一定与我们预期的一致。所以最好还是使用显式转换。
111、回调函数:实际上就是方法调用的指针,即,函数指针。这是一个很强大的编程特性。
112、.net以委托的形式实现函数指针的概念。.net中函数指针是有类型特征的,包括其参数和返回值。此种做法好处就在于,类型安全。
113、.net中如果要传递方法,就必须把方法的细节封装在一种新类型的对象中,即委托。委托只是一种特殊的对象类型,其特殊之处在于,其他对象都包含数据,而委托包含的只是方法的细节。
114、在C#中声明委托:
delegate void VoidOperation(uint x);
对比在C++中声明函数指针类型别名:
typedef void (*VoidOperation)(unsigned x);
115、委托与委托的实例,皆称为委托。
116、定义一个委托就是定义一个新类。
117、委托使用示例:
private delegate string GetAString();

GetAString firstStringMethod = new GetAString(x.ToString);

Console.WriteLine("String is" + firstStringMethod());

118、C#在语法上总是带有一个参数的构造函数,这个参数就是委托引用的方法,类型必须匹配定义委托时的签名。
119、静态或者实例方法对于委托来说没有区别,只要匹配签名。
120、匿名方法:委托的另一种形式,用作委托参数的一个代码块。
示例:
delegate string delegateTest(string val);

delegateTest anonDel = delegate(string param)
{
param += mid;
param += " and this was added to the string.";
return param;
};
121、匿名方法的优点:减少系统开销。方法仅在由委托使用的时候才定义。同时会使代码显得比较简单。
122、匿名方法的两条规则:跳转语句不能从匿名方法外部跳到内部,也不能从内部跳到外部。
123、多播委托:包含多个方法的委托,按顺序调用多个方法。
多播委托必须返回void,而编译器看到一个返回void的委托就假定它是多播委托。
用+ += - -=对多播委托进行运算,以增加或去除委托。
注意:其实就是一个入口,按序调用多个同样参数,void返回的函数。
调用方法链的顺序没有正式定义,因此,最好不要编写依赖于特定顺序执行的方法链。
124、事件(事件内容本身不难,但不好表述,可以形式化使用,转载他人文章一篇,以作说明)。
============================================================================
C#委托、事件、自定义事件的理解
Girmo 发表于 2006-5-13 11:22:00

一、委托
委托类似于函数指针,但函数指针只能引用静态方法,而委托既能引用静态方法,也能引用实例方法。

委托使用分三步:1、委托声明。2、委托实例化。3、委托调用。
例程一:
using System;

namespace 委托
{
delegate int NumOpe(int a,int b); //委托声明
class Class1
{
static void Main(string[] args)
{
Class1 c1 = new Class1();
NumOpe p1 = new NumOpe(c1.Add); //委托实例化
Console.WriteLine(p1(1,2)); //委托调用
Console.ReadLine();
}

private int Add(int num1,int num2)
{
return(num1+num2);
}
}
}
例中,委托NumOpe引用了方法Add。
委托声明了以后,就可以象类一样进行实例化,实例化时把要引用的方法(如:Add)做为参数,这样委托和方法就关联了起来,就可以用委托来引用方法了。
委托和所引用的方法必须保持一致:
1、参数个数、类型、顺序必须完全一致。
2、返回值必须一致。

二、事件

事件有很多,比如说鼠标的事件:MouserMove,MouserDown等,键盘的事件:KeyUp,KeyDown,KeyPress。

有事件,就会有对事件进行处理的方法,而事件和处理方法之间是怎么联系起来的呢?委托就是他们中间的桥梁,事件发生时,委托会知道,然后将事件传递给处理方法,处理方法进行相应处理。

比如在WinForm中最常见的是按钮的Click事件,它是这样委托的:this.button1.Click += new System.EventHandler(this.button1_Click);按按钮后就会出发button1_Click方法进行处理。EventHandler就是系统类库里已经声明的一个委托。

三、自定义事件及其处理

EventHandler以及其它自定义的事件委托都是一类特殊的委托,他们有相同的形式:

delegate void 事件委托名(object sender,EventArgs e);

object用来传递事件的发生者,比如二中的Button控件就是一个事件发生者;EventArgs用来传递事件的细节。

例程二:
using System;

namespace 最简单的自定义事件
{
/// <summary>
/// 事件发送类
/// </summary>
class Class1
{
public delegate void UserRequest(object sender,EventArgs e); //定义委托
public event UserRequest OnUserRequest; //定义一个委托类型的事件

public void run()
{
while(true)
{
if(Console.ReadLine()=="a")
{//事件监听
OnUserRequest(this,new EventArgs()); //产生事件
}
}
}
}

/// <summary>
/// 事件接收类
/// </summary>
class Class2
{
static void Main(string[] args)
{
Class1 c1 = new Class1();
c1.OnUserRequest += new Class1.UserRequest(c1_OnUserRequest); //委托实例化后绑定到事件
c1.run();
}

private static void c1_OnUserRequest(object sender, EventArgs e)
{//事件处理方法
Console.WriteLine("/t你触发了事件!");
}
}
}
例程三:
using System;

namespace 带事件数据的事件
{
/// <summary>
/// 带事件数据的事件类,从EventArgs继承
/// </summary>
class OnUserRequestEventArgs:EventArgs
{
private string inputText;
public string InputText
{
get
{
return inputText;
}
set
{
inputText = value;
}
}
}

/// <summary>
/// 事件发送类
/// </summary>
class Class1
{
public delegate void UserRequest(object sender,OnUserRequestEventArgs e);
public event UserRequest OnUserRequest;

public void run()
{
while(true)
{
Console.WriteLine("请输入内容:");
string a=Console.ReadLine();
//if(a=="a")
//{
OnUserRequestEventArgs e1 = new OnUserRequestEventArgs();
e1.InputText = a;
OnUserRequest(this,e1);
//}
}
}
}

/// <summary>
/// 事件接收类
/// </summary>
class Class2
{
[STAThread]
static void Main(string[] args)
{
Class1 c1 = new Class1();
c1.OnUserRequest += new Class1.UserRequest(c1_OnUserRequest);
c1.run();
}

private static void c1_OnUserRequest(object sender, OnUserRequestEventArgs e)
{
Console.WriteLine("/t你输入的是:"+e.InputText);
}
}
}
============================================================================
125、windows使用的是虚拟寻址系统。该系统把程序可用的内存地址映射到硬件内存的实际地址上,这些任务又windows在后台管理,其实际结果是32位处理器上的每个进程都可以使用4GB的内存--无论机器上有多少硬盘空间。这个4GB内存实际上包含程序的所有部分--包括可执行代码、代码加在的所有DLL以及程序运行时使用的所有变量的内存。这4GB内存成为虚拟地址空间,或虚拟内存,我们为了方便把它们当作一般的内存使用(实际上这也是完全正确的)。
4GB内存中的每个存储单元都是从0开始往上排序的。
在进程的虚拟内存中,有一个区域称为堆栈。堆栈存储不是对象成员的的值数据类型。另外,在调用一个方法时,也使用堆栈复制传递给方法的所有参数。堆栈实际上是向下填充的,自高字节向低字节填充。
如果两个变量同时声明,例如:int a, b;则先后顺序不定的。不过这不会对使用有任何影响。
126、值类型存储在堆栈中。堆栈的优势:性能高;缺点:不够灵活,生命周期限制太死。
127、为了弥补堆栈的缺点,堆出现,用以存储在退出方法后仍然还需要存在的数据。
注意:C#托管堆与C++的堆目的相同,但是不完全相同:C++由程序员完全控制堆内数据,C#则是由GC负责释放不再使用的引用对象。
128、堆上的内存是向上填充的。堆栈中仅存储对堆中对象的引用。在托管堆中的对象只有没有任何变量引用后,才会被删除。
129、GC,垃圾收集器。一般来说,垃圾收集器启动后,会将所有不再使用的对象删除,然后是正在使用和未使用的内存会混合在一起,而不是各自形成一个连续的块。这样导致一个问题,如果分配新的内存,寻找一块合适大小的内存时需要遍历整个堆。C++中堆的使用就是如此,当然C++中一般不使用GC,而是程序员自己控制在适当的时间删除对象。因此,C++中常常会使用内存池来自定义对象的new与delete。C#中则是从更基础的层次试图解决该问题:当垃圾收集启动后,删除不使用的对象后,还会对正在使用中的对象进行压缩,使其移动回到堆的端部,形成一个连续的块。在移动对象的时候,所有引用都需要用正确的新地址来更新,这会由垃圾收集器自动完成。
C#中垃圾收集器的压缩操作是与其他堆的根本区别所在。这样会使平时分配内存的速度大大加快。但是注意:在更新引用,压缩对象时候,是非常消耗资源的,此时,性能会大幅度降低。
130、可以强制调用垃圾收集器(System.GC),但是不常用。此外,垃圾收集器并不保证一次收集过程中能将所有的未引用的对象都从堆中删除。
131、C#堆的特性仅仅针对托管资源,未托管的对象是不能自动释放的。托管类在封装对未托管的资源的直接或者间接引用的时候,需要制定专门的规则,确保未托管的资源在回收类的一个实例时释放。
未托管的资源,主要是与C#中使用指针相关代码联系起来的,指的是垃圾收集器不能访问和管理的资源。
132、为了实现对未托管资源的的自动释放,有两种机制可以选择:
a、使用析构函数。在.net环境中被称为Finalize。
实际上,析构函数会被翻译为如下的IL的对应C#代码:
protect override void Finalize()
{
try
{
//implementation
}
finally
{
base.Finalize()
}
}
目的在于实现C++中的先调用派生类析构函数,在调用基类析构函数的机制。

注意:C#中析构函数的运行时间是不确定的。所以,不能把必须马上要完成的事情放在此处。不能强制调用实例的析构函数。

b、IDisposable接口。
在C#中推荐使用System.IDisposable接口替换析构函数。IDisposable接口定义了一个模式(具有语言及的支持),为释放未托管的资源提供了确定你个的机制,并避免产生析构函数固有的与垃圾收集器相关的问题。
IDisposable接口声明一个方法void Dispose();
Dispose()执行代码显式释放由对象直接使用的哦所有未托管的资源,并在所有实现IDisposable接口的封装对象上调用Dispose()。

优点:完全的对未托管资源的释放控制。
注意:要在实例上调用,也就是不能null.Dispose()。

try
{
theInstance = new ResourceGobbler();
//do your processing
}
finally
{
if (theInstance != null)theInstance.Dispose();
}
C#中使用一个语法来简化对出作用域的继承IDisposable接口类实例的Dispose()调用。
using(theInstance = new ResourceGobbler())
{
//do your processing
}
即使异常,Dispose()也会被调用。

如果对于某些类来说,使用Close()更为恰当,那么在Close中调用Dispose()即可。这样既满足使用Close(),还可以使用using。

133、最安全的方法是,同时使用IDisposable接口和析构函数。用一个变量标识是否调用过资源释放,在Dispose()和析构中根据标识选择是否调用资源释放。
134、一般,Dispose()意味着要清除所有资源,包括托管的与未托管的。而析构则仅负责未托管的,其他由垃圾收集器负责。
135、.net平台下,除开C++.net外,类都是默认托管的。也就是,如果不涉及到非托管资源,那么类不使用两种机制也是托管的,new出来用就可以了。
136、C#中使用引用的原因:使C#语言易于使用,降低用户内存管理所需要考虑的难度。
137、C#中使用指针的两个原因:
a、向后兼容性。windows api函数可能是以指针作为参数的,这样我们可以继续使用。
b、性能。指针使我们可以自己控制对数据的访问与修改,这样我们可以自行优化性能。
缺点:使用指针容易出错。
138、强烈推荐,C#中不要轻易使用指针:难以编写和调试,而且无法通过CLR的内存类型安全检查。
139、C#只允许在标记为不安全的代码中使用指针。标记不安全代码的关键字unsafe。
140、unsafe可以用来标记类成员(包括函数、字段等)、代码块;不能用来标记局部变量。不安全的局部变量,必须在不安全的方法或者语句块中声明和使用。
141、编译带有不安全代码的代码,必须告诉C#编译器。编译命令为:
csc /unsafe MySource.cs
csc -unsafe MySource.cs
142、int *pX, pY;
C++中表示一个指针,一个整型;C#中表示两个指针。
C#中*是与int一起构成一个类型的,即,C#中*是类型相关的,而不像C++是变量相关的。
142、&取地址运算符。*间接寻址运算符。
143、跨越DWORD边界存储数据通常会降低硬件的性能。因此,.net运行库通常会给某些数据类型加上一些空间,使它们占用的内存是4的倍数。例如,short占用2个字节,但是放在堆栈中,仍然会是堆栈指针减少4,而不是2。
144、可以把指针声明指向任意的值类型(大部分预定义类型和结构)。
注意:不能使用指针处理类,否则将会破坏堆中.net运行库为垃圾收集器维护的与类相关的信息。
垃圾收集器可以访问的数据类型为托管类型,而指针只能声明指向非托管类型,因为垃圾收集器不能处理他们。
145、指针类型--》整数类型 显式 目的在于显示地址。
指针,在32位机器上,仅能转换为uint、long和ulong,否则溢出。
在64位机器上,仅能转换为ulong,否则溢出。
注意:使用指针的地方,必须自己控制是否会溢出。即使使用checked检查溢出,溢出发生也不会抛出异常的。
146、指针类型之间可以显式转换。
147、void *指针类型,主要用于调用需要void *类型参数的win api。
148、指针的算术运算。
149、sizeof运算符。
注意:sizeof仅用于值类型,且必须用于不安全的代码。
150、->指针访问运算符。
151、C++的指针变量定义与赋值方式在C#中会出现问题:垃圾收集器会移动未删除对象,此时指针则不会指向原来的对象。当然,C#根本与会允许编译通过的。
fixed关键字,告诉垃圾收集器,类的某些实例的成员有指针指向它们,所以这些实例不能移动。
fixed(long *pObject = &(MyObject.X))
{
//do your processing
}
如果声明多个可以
fixed(...)
...
fixed(...)
{
//do your processing
}
如果fixed后括号内指针类型相同,则可以放在一起,例如:
fixed(long *pX = &(MyObject.X), pX2 = &(myObject2.X))
{
//do your processing
}
fixed允许嵌套。
152、创建基于堆栈的数组:
stackalloc关键字,在堆栈上分配指定个数元素的空间,返回其指针。
char *pChars = stackalloc char[100];
仅作空间分配,而不会初始化。为了性能。
然后就可以像C++中使用数组一样使用pChars了。
注意:stackalloc分配的数组中没有任何类实例,或者说对象,因此操作出界不会抛出异常。
和C++一样。

153、string类存在一个问题:重复修改给定的字符串,效率会非常低,它实际上是一个不可变的数据类型,一旦对字符串对象进行了初始化,该字符串对象就不能改变了。修改字符串内容的方法和运算符实际上是创建一个新的字符串。
154、string进行文字处理会有严重的性能问题,所以微软提供了System.Text.StringBuilder类,其上可以进行的处理仅限于替换和添加删除字符串中的文本,但工作效率非常高。
155、StringBuilder对象可以设置最大容量,超出的话会抛出异常。
156、格式化字符串:.net运行库定义了一种标准方式:使用接口IFormattable。
157、字符串格式化的具体过程:
Console.WriteLine()->StringBuilder实例的AppendFormat()
--->IFormattable接口实现->接口中的ToString()方法处理
I->IFormattable接口未实现->Object继承下来的ToString()方法处理
158、IFormattable接口的ToString(string format, IFormatProvider formatProvider)方法的第二个参数指定转化时考虑的文化背景信息,可以传null使用系统指定的文化背景信息。
159、正则表达式语言:一种专门用于字符串处理的语言,包括一组用于标识字符类型的转移代码和一个系统。主要用于对字符串执行复杂、高级的操作。
160、Regex.Matches()方法。
161、/b表示一个字(或者说单词)的边界,例如:/ba,以a开头的字,ion/b,以ion结尾的字。
162、/S表示任何不是空白的字符。*称为数量词,其含义是前面的字符可以重复任意次,包括0次。
163、在正则表达式中,可以把任何字符组合起来(包括元字符和转义序列),得到的序列称为一个组。
164、匹配、组合和捕获:正则表达式的匹配是不能重叠的,如果有可能重叠,在默认情况下就选择最长的匹配。
165、Pattern:/b(/S+)://(/S+)(?::(/S+))?/b
Match:http://www.wrox.com
以Pattern得到一个字符串Match,这就是一次匹配,
第一个/S+是http,这就是一个组,
http只重复一次,所以只有一次捕获。
166、C#中,数组是System.Array类的一个实例,C#为其提供了特殊语法。
优点:随机访问,效率高。
缺点:实例化时必须指定大小,以后也不能添加、插入或删除元素。
167、C#提供了许多接口,类实现了这些接口之后则支持了某种数据结构类型的全部功能。这些数据结构类型包括:集合、数组列表、栈、队列、有序列表、字典。
168、foreach循环是集合的主要目的,集合并没有提供其他特性。
169、对象如果可以提供相关对象的引用,就是一个集合,或者称为枚举,它可以遍历集合中的数据项。本质上,集合即实现了接口System.Collections.IEnumerable的类。IEnumerable只定义了一个方法:
interface IEnumerable
{
IEnumerator GetEnumerator();//返回枚举对象,可以看出,同时实现了接口System.Collections.IEnumerator。
}
170、另有一个集合接口ICollection,派生于IEnumerable。它有IEnumerable的GetEnumerator()方法,同时有一个属性,返回集合中的元素个数,并且可以把集合复制到数组中,并提供信息说明它是否是线程安全的。
171、IEnumerator接口:
interface IEnumerator
{
object Current{get;}
bool MoveNext();
void Reset();
}
172、数组列表:System.Collections.ArrayList类。类似于C++的vector。Capacity指定容量,如果超出,会自动翻倍。Count返回元素个数。Add添加元素(?复制一份实例还是添加引用,尚未明白),Insert方法在指定位置添加元素,RemoveAt()删除指定位置元素,Remove删除指定引用的元素,但是有性能损失,会线性搜索查找对象,AddRange可以添加整个集合,RemoveRange可以删除多个连续元素,GetRange、SetRange、InsertRange都与一次操作多个元素有关。
173、注意:添加和删除元素都会使后续元素在内存中作相应改变。此外,ArrayList把所有元素都当作对象引用,因此可以在ArrayList中存储任何想存储的对象,但在访问的时候,需要把他们的数据类型转会回合适的数据类型。
174、Stack类:Pop、Peek方法的区别。foreach循环从顶向底读。查msdn。
175、Queue类:查msdn。
176、SortedList类:一个key和value组,同时可以通过索引访问,key访问,还能取得key集合与value集合。和字典有点类似,但没有那么强大。
177、字典和散列表:
基本的字典是由类Hashtable表示的,但假定键和条目都是Object类型。
抽象基类:DictionaryBase,具有基本的字典功能,可以从它派生。
还有一个已经建立好的.net基类System.Collections.Specialized.StringDictionary,不过键是字符串的。
178、哈希表被填满的比例称为负载(load),可以指定一个最大负载,达到这个值,则为Hashtable重新分配内存。负载越低,工作效率越高,但是消耗的内存也就越大。
179、散列算法必须一致:如果两个对象包含相同的数据,那么他们就必须给出相同的散列值。System.Object的Equal和GetHashCode方法必须一致。一般重写Equal,则需要重写GetHashCode,否则会给出编译警告。反之不一定成立:即,有相同的散列值不一定会是含有相同的数据的对象。
180、默认的Equal使用引用进行比较,GetHashCode()会根据对象地址返回一个散列。
181、Object类在编译期间没有类型安全性,此外,给值类型使用Object类会有性能损失。
182、泛型类使用泛型类型,并可以根据需要使用特定的类型替换泛型类型,这就保证了类型安全性:如果某个类型不支持泛型类,编译器会报错。
183、C#泛型与C++模板的区别:
C++模板在用特定的类型实例化模型时,需要模板的源代码;而泛型不仅是C#的一种结构,而且是CLR定义的,所以在C#和VB.net中可以通用。
184、C#泛型对性能有帮助:由于使用时定义类型,使用泛型类对于值类型不需要进行装箱和拆箱的操作。ArrayList与ArrayList<int>。
185、C#泛型对类型安全有帮助:可以进行强类型检查。ArrayList的Add()和ArrayList<int>的Add()。、
186、C#泛型可以更好地重用二进制代码。泛型类可以定义一次,用许多不同的类型实例化。不需要源代码,且可以在CLR支持的语言中通用。
187、泛型的命名约定:
a、泛型类型的名称使用字母T作为前缀。
b、如果没有特殊要求,且泛型类型允许用任意类型替代,且只使用了一个泛型类型,就可以使用字符T作为泛型类型的名称。
c、如果对泛型类型有特殊要求,或者使用了两个或以上的泛型类型,就应该给泛型类型使用描述性的名字。
188、常用泛型集合接口与类:
接口如下:
a、ICollection<T>
b、IList<T>
c、IEnumeralbe<T>
d、IDictionary<TKey, TValue>
e、IComparer<T>:使用Compare()方法给集合中的俄元素排序
f、IEqualityComparer<T>:对象进行相等比较的接口。Equals()和GetHashCode()方法
类如下:
a、List<T>
b、Dictionary<TKey, TValue>
c、SortedList<TKey, TValue>
d、LinkedList<T>
e、Queue<T>
f、Stack<T>
g、Collection<T>
h、ReadOnlyCollection<T>
i、KeyedCollection<TKey, TItem>
189、创建定制的泛型类:泛型类的定义类似于一般的类,只要使用泛型类型声明,之后就可以在类中把泛型类型用作字段成员,或方法的参数类型。
190、不能把null赋值给泛型类型。泛型类型可能被实例化为值类型,而null只能用于引用类型。为了避免这个问题,可以使用default关键字。它会把null赋予引用类型,把0赋值给值类型。
191、约束:定义泛型类之后可以接一个where子句,要求指定的泛型类型实现一个接口或者派生于某个基类。
192、泛型方法:定义同普通方法类似,在名称后参数列表前接泛型类型声明,也可以借where子193、泛型委托:相当于泛型方法的对应升级版本,不需要where子句。
194、可空值类型:Nullable<T> 即前面所说的int? x = 1;的推广版本。
Nullable<int> x = 1;
对于可空类型,二元操作符两边有一个为null则结果为null。布尔操作符,则是false。
可空类型--》不可空类型,强制转换,可能出现异常
反之,隐式转换,总是成功
接合运算符:?? 例如 int y1 = x1 ?? 0;如果x1不是null则y1 = x1,否则y1 = 0
195、EventHandler<TEventArgs>:使用该泛型委托则不需要为每个事件处理程序定义新的委托了。
196、ArraySegment<T>:结构类型,表示数组的一段。数组段,不会赋值原数组的元素,而是赋值引用而已,所以元素的改变对两者都有效。
197、定制特性:允许把定制的元数据与程序元素关联起来。这些元数据是在编译过程中创建的,并嵌入到程序集中。接着就可以在运行期间使用反射的一些功能检查这些元数据了。
198、定制特性不会影响编译过程,因为编译器不能识别它们,但这些特性在应用于程序元素时,可以在编译好的程序集中用作元数据。
199、使定制特性非常强大的因素是使用反射,代码可以读取这些元数据,使它们在运行期间做出决策,也就是说,定制特性可以直接影响代码运行的方式。
200、C#发现这个属性有一个特性FieldName时,首先会把字符串Attribute添加到这个名称的后面,形成一个组合名称,然后在其搜索路径的所有命名空间中搜索有相同名称的类。如果该特性名称本来就以Attribute结尾,编译器就不会做修改。
201、编译器找不到对应特性类,或者对应特性类的使用方式与特性类中的信息不匹配,则编译报错。
202、特性类本身用一个特性System.AttributeUsage来标记。这是微软已经定义好的一个特性,C#编译器为其提供特殊的支持。AttributeUsage主要用来表示定制特性可以应用到哪些类型的程序元素上。
203、特性可以作为一个整体应用到程序集或模块中,而不是应用到代码中的一个元素上,在这种情况下,这个特性可以放在源代码的任何地方,但需要使用关键字assembly或module来做前缀:
[assembly:SomeAssemblyAttribute(Parameters)]
[module:SomeAssemblyAttribute(Parameters)]
204、AllowMultiple = false, Inherited = false
前者表示一个特性是否可以多次应用到同一项上;后者表示对于接口或者类应该该特性时,会自动应用到所有派生类或接口上,如果用到方法或者属性上,则对其重载也有效。
205、指定特性参数:
[FieldName("sos")]
public string SocialSecurityNumber
{
...
}
C#编译器会检查传递给特性的参数,并查找该特性中带这些参数的构造函数。如果找到这样一个构造函数,则编译器将制定的元数据传递给程序集;没找到则生成编译错误。
反射可以从程序集中读取元数据,并实例化它们表示的特性类。编译器必须确保存在这样的构造函数,才能在运行期间实例化指定的特性。
206、指定特性的可选参数:
[FieldName("SOS", Comment = "1234")]
public string SocialSecurityNumber
{
...
第二个参数是为FieldName类实例的公共属性或者字段传递参数。
207、System.Type:通过其能访问任何给定数据类型的信息。
System.Reflection.Assembly:通过其能访问给定程序集的信息,或者把这个程序集加载到程序中。
208、Type是一个抽象的基类,实例化出来的都是其派生类,但够用了,一般不用自己添加新的方法和属性。
209、取得给定类型的Type引用的3种常用方式:
a、C#的typeof运算符
b、GetType()方法。注意:运行于实例之上,但是得到的都是与类型相关的信息,不包含任何与实例相关的信息。
c、Type类的静态方法GetType()。
210、Type的属性:Name、FullName、Namespace、BaseType、UnderlyingSystemType、IsAbstract、IsArray、IsClass、IsEnum、IsInterface、IsPointer、IsPrimitive、IsPublic、IsSealed、IsValueType等。还可以通过Type实例得到对应类型的程序集的引用:
Assembly containingAssembly = new Assembly(t);
211、Type的方法:多用于取得对应数据类型的成员信息:构造函数、属性、方法、时间等。形式类似,取得一个XX的信息和所有的XX的信息的函数各一个,例如:GetMethod(),GetMethods()。
212、Assembly类允许访问给定程序集的元数据,包含可以加载和执行程序集的方法。
213、Assembly.Load()和Assembly.LoadFrom()静态方法的区别:前者会在本地目录和全局程序集高速缓存上搜索指定名称的程序集,后者则是需要提供完整路径名,而不会搜索。
214、查找方法:Assembly的实例方法GetTypes(),返回该程序集的顶一个所有类型的信息(Type类型的引用数组)。
215、查找定制特性:Attribute的静态方法GetCustomAttributes(),返回该程序集一组特性的信息(Attribute的引用数组)。第一个参数是程序集,第二个参数是可选的,指定了特定的特性类的Type对象则返回该特性类的所有特性。此外还有GetCustomAttribute(),两个参数,仅返回一个Attribute对象,如果存在多个匹配则抛出异常。
216、C#使用异常机制来对不能预料的错误进行处理。在C#中,当出现某个异常错误条件的时候,就会创建一个异常对象。这个对象包含有助于跟踪问题的信息。
217、对于.net类来说,异常类都派生于System.Exception,而它派生于System.Object。一般不会直接抛出System.Exception对象,因为它无法确定错误情况的本质。
218、System.SystemException派生于System.Exception,通常由.net运行库生成。.net中预定义的异常类都派生于此。
219、System.ApplicationException是用户自定义的异常类的基类。
220、try、catch、finally关键字。
finally关键字在C++的异常处理机制中不存在。finally块包含的代码清理资源或执行要在try块或者catch块末尾执行的其他操作。无论是否产生异常,都会执行finally块,所以finally块中不能包含return语句,否则出错。finally块不是必需的。
221、catch的参数只能用于该catch块。C#只会执行匹配的第一个catch块,所以catch匹配的类应该先是特殊的,然后是一般的,否则编译器会报错。
222、catch{}不带任何参数,用来处理其他不使用C#编写的代码抛出的异常。其他语言抛出的异常没有必须派生于System.Exception的限制。这个块做的事情并不多,因为不知道抛出的异常的类型。
223、异常的属性:Data、HelpLink、InnerException、Message、Source、StackTrace、TargetSite。StackTrace和TargetSite有.net运行库自动提供。Source总是由.net库提供为产生异常的程序集名称(可以被修改,用以提供专门信息),其他的则必须有抛出异常的代码提供,也就是在try块产生实例后设置。
224、如果产生了异常却没有被处理,那么会由.net运行库的catch捕获,但此时代码执行会立即中断,并给用户一个对话框,说明代码没有处理异常,并给出.net运行库能检索到的异常信息。
225、try块嵌套,由内层try块产生异常,而内层catch没有对应的异常类时,会执行内层finally块,然后转到外层catch匹配异常类,找到则处理,然后外层finally。如果内层catch匹配后抛出异常,那么执行内层finally后也会转到外层catch匹配。
226、try块嵌套处理规则的好处在于:
a、可以修改抛出的异常的类型;
b、在代码的不同地方处理不同类型的异常;
227、InnerException属性包含对另一个相关异常的引用,最后的处理代码可能需要这个额外的信息。
228、windows操作系统表面上可以处理多个任务,这个过程成为抢先式多任务处理。线程间(注意不是进程)时间片轮询的方式。
229、在应用程序中创建执行某个任务的线程,通常称为工作线程(worker thread)。
230、C#线程启动与windows平台下C++完全类似,提供一个方法的委托作为入口参数创建线程。这个委托C#预定义了一个,在System.Threading命名空间下,即ThreadStart。将将线程要运行的方法赋值给该委托的一个实例,然后利用该委托实例创建线程即可。例如:
TreadStart entryPoint = new ThreadStart(ChangeColorDepth);
Thread depthChangeThread = new Thread(entryPoint);
depthChangeThread.Name = "DepthChangeThread";
depthChangeThread.Start();
231、线程的入口点方法或者说委托,不能带有任何参数,也不能有任何返回值。那么传递参数和返回信息就必须使用别的方法,最简单的是使用类的成员字段。
232、也可以使用匿名方法来启动新线程。例如:
void ChangeColorThread()
{
Thread depthChangeThread = new Thread(delegate()
{
//todo

});
depthChangeThread.Name = "DepthChangeThread";
depthChangeThread.Start();
}
233、线程还可以进行挂起、中止、恢复操作。
挂起一个线程,则暂停该线程,让其进入睡眠状态,此时,线程仅停止运行一段时间,不占用cpu时间,并且可以恢复,从挂起时的状态开始运行。线程被中止则是停止运行。操作系统会永久删除该线程的所有数据,所以该线程不能重新启动。
xxxThread.Suspend();
xxxThread.Resume();
xxxThread.Abort();
234、Abort()会在受影响的线程中产生一个ThreadAbortException异常,但该异常从来不被处理。如果线程执行try块中代码时中止,则线程中之前还是会执行finally块的。finally块执行时间是不定的,所以,如果想要在主线程中等finally块执行完毕后再执行其他操作,就应当中之后立即调用xxxThread.Join(),等待工作线程真正结束。Join()重载版本还可以指定等待时间,等待一定时间则执行。
235、主线程想要在自己的线程上执行某些操作,可以用Thread.CurrentThread静态属性,取得自己的线程。例如,Thread myOwnThread = Thread.CurrentThread;
236、Sleep方法同C++的。线程的静态方法。
237、为不同的线程指定不同优先级,则优先级较高的线程先工作完,才会让低优先级的线程工作,不在时间片轮询。一般来说,接收用户输入的线程指定较高的优先级。C#中,线程优先级为ThreadPriority枚举的值,Highest、AboveNormal、Normal、BelowNormal、Lowest5中。
238、操作系统自己的线程当然是高优先级的。
239、线程的同步问题。所谓同步,是指在某一个时刻只有一个线程可以访问变量。如果不能确保对变量的访问时同步的,就会产生错误。
240、只要一个C#语句翻译为多个本机代码命令,则线程的时间片就有可能在执行该语句的进程中中止。可能读到未定义的中间状态变量。可能多线程同时写一个变量。
241、C#提供多种同步的方法,lock关键字是其中常用且比较简单的方式。
lock(x)
{
DoSomething();
}
lock语句把变量放在圆括号中,包装对象,称为独占锁或排他锁。当执行lock关键字的复合语句时,独占锁会保留下来。只要变量在独占锁中,则其他线程都不能访问。其他需要访问该变量的线程会先停下,等待独占锁解锁后再执行。
System.Threading.Monitor类负责控制变量访问。lock其实是封装对该类的使用。
242、同步需要注意的地方:
a、性能下降。lock语句越少越好。
b、死锁。例如:
在一个线程中:
lock(a)
{
//todo
lock(b)
{
//todo
}
}
另一个线程中:
lock(b)
{
//todo
lock(a)
{
//todo
}
}
c、竞态。头脑不清晰的结果。
243、通过Thread类来使用线程属于CPU密集型,所以,CLR包含一个内置的线程池,共应用程序使用。ThreadPool会在线程的托管池中重用已有的线程,使用完县城后,线程就返回线程池,供以后使用。ThreadPool对每一个处理器有25个可用的线程。
244、应到仔细考虑该使用哪种方式创建线程:
使用ThreadPool的好处:
a、最简单的方式创建和删除线程;
b、应用程序使用线程的性能需要优先考虑;
使用Thread类:
a、要控制所有线程的优先级;
b、各个线程都需要单独或协同进行各种操作;
c、使用的线程寿命较长;
245、使用ThreadPool示例:
ThreadPool.QueueUserWorkItem(new WaitCallback(xxxMethod));
不使用WaitCallback委托也可以:
ThreadPool.QueueUserWorkItem(xxxMethod);
使用WaitCallback委托还可以传入参数作为xxxMethod的参数。
ThreadPool.QueueUserWorkItem(new WaitCallback(xxxMethod), "First Thread");
注意:此处xxxMethod是静态的(具体是不是必须static需要查看msdn),且有参数,不同于前面所说方法必须无参数,无返回值。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: