c#零碎整理
注:本文中大部分图片来自老师的PPT,感谢邵老师!文中所有内容为自己按照PPT整理,欢迎指正!
标识符
标识符(类名、变量名、方法名、表空间名等)
大小写敏感
正则表达式 小括号(组合),中括号[可有可无],大括号{重复}
只能以字母/下划线(_)/@符号开头,后边跟若干字母或数字或下划线(_)
关键字是标识符的子集
在关键字前加“@”可以把关键字转换为标识符,可以当变量等使用
可以是中文,只要是Unicode编码就可以
整型:
如果整型变量有后缀u/U,这个变量为uint / ulong里最小的那个,
如果整型变量有后缀l/L,这个变量为long / ulong里最小的那个,
如果整型变量没有后缀,这个变量为uint/ulong/int/long里最小的那个
浮点数:
如果浮点型变量有后缀f/F,这个变量为float
如果浮点型变量有后缀d/D,这个变量为double
如果浮点型变量有后缀m/M,这个变量为decimal(占16个字节,有28-29位有效位)
如果浮点型变量没有后缀,这个变量为double
字符类型:
可以是任意除了后引号(closing quote)、换行符、反斜杠\
\' 单引号
\" 双引号
\\ 反斜杠
\0 空字符
\a 警报符 (alert)
\b 退格 (backspace)
\f 换页 (form feed)
\n 换行 (new line)
\r 回车 (carriage return)
\t 水平制表 (horizontal tab)
\v 垂直制表 (vertical tab)
转义符应用:
字符串以@打头:
@的作用是后面跟着的字符串添加转义字符
例:c:\data\这个路径用字符串表示
string str = "c:\\data\\";
string str =@"c:\data\";
示例解释:如果以@打头并且要使字符串里有双引号,那么双引号的地方需要写成上图中下边的这个例子,这个语句等同于 “\”C:\\sample.txt\””也就是会为需要添加转义符的地方自动添加转义符,双引号特殊,需要写两遍来表示用引号
类型
不同的类型
所有的类型都和object兼容
值类型和引用类型的区别
关于堆和栈:
类(引用类型)的空间分配和构造过程
声明 Rectangle r;
r = new Rectangle();
第一条,在栈顶分配一个字节左右的小空间,里边是空的
第二条,在堆的顶部分配一个空间,并把首地址填到第一步执行时的空间中供调用
参照c#笔记图,在stack和heap增长到接触时,会向操作系统请求分配更多的空间
结构体(值类型)会把以上过程全都在栈中进行
基本类型强制转换
sbyte是有符号8位整型,-128 - 127
byte是无符号8位整型,0 – 255
枚举类型
继承自Object类
在名字空间里声明,默认是int值,可以强制转换为int值,默认是从0开始,依次递增,每个增1,也可以自己指定枚举值
设置了byte的基础类型,而且指定从1开始编号,依次为1-7
强制转换为int值,x的值为2
自己指定了每个枚举值
在使用时可以通过 Access类型的值是否等于Access.Personal判断是否是第一项,也可以把Access类型的值强制转换为数字类型判断数字为几确定是哪一项。
枚举类型的运算符有
位运算取反
注意:
不进行类型转化的话不能够直接给int或其他数值类型直接赋值
System.Enum命名空间里有枚举类型的各种操作
数组
一维数组与java中非常类似
三种声明方式:
int[] a = new int[3];
int[] b = new int[] {3,4,5};
int[] c = {3,4,5};
交错数组,数组的数组,数组元素的维度和大小可以不相同,是一维数组(通过数组名.rank求出),存的是子数组的引用(地址),在使用时声明交错数组以后要继续声明每个子数组,声明后的子数组才可以使用,每个子数组的长度都可以是任意的,不要求相同。如下图
多维数组
多维数组更像是矩阵,为了与交错数组分开,声明时采用中括号里加逗号隔开的方式指明数组的维数。如下图
System.Array提供了数组的操作接口,Array.Copy(a, b, 2) 把数组a的前两个元素拷贝给b(会把b相应位置的内容覆盖掉)
Arraylist.Insert(int index, object value) 将元素插入ArrayList的指定索引处
Array.Sort(b) 排序
字符串String
string有不可变性,给字符串赋值时会令字符串指向一个新的字符串对象(防止指向同一个字符串对象的变量因为其中一个修改了导致其他变量同时被修改(串池的应用),也为了缓存字符串的哈希值避免重复计算)
字符串可以连接、可以通过下标访问字符、可以通过.length查看长度、可以通过“==”号判断内容是否相同
可变长度数组
链表,可以直接通过.add()插入不同的值(写在add方法的括号里),可以多种类型的数据混用(注意此时调用排序等方法会抛出异常),长度通过.Count查看
关联数组
哈希表,通过键值对的方式存储
结构体和类
结构体
声明时直接在栈顶或其他对象内分配一个object
是值类型,在栈里存储,调用速度快,等同于栈上的类
不允许声明没有参数的构造函数,因为没有参数的构造函数是默认自带的,不能够覆盖,调用时对所有的数据成员赋初值(各个类型默认的初值)
new时只初始化,不再分配空间
可以实现接口,不能继承类
不能有析构函数
类
引用类型,存在堆里,声明时分配一个object并调用构造函数,声明的构造函数可以没有参数
可以实现接口,可以继承类
可以有析构函数
Object类
所有类的基类,对象可以被赋值(编译器会把object映射到System.Object,即Object首字母大小写均可)
装箱和拆箱
boxing
把一个栈上的值类型拷贝一个放入堆中,把首地址交给object
例如:值类型转换成引用类型
unboxing
强制转换,把堆里的东西拷贝进栈中
例如:引用类型强制转换为值类型,在强制转换时都需要一次装箱和拆箱过程,先转换成Object,再转换成需要的类型。
支持使用“泛型”容器,在声明方法时参数写object,可以适配不同参数类型
例如队列使用泛型,那么一个队列里可以有多种类型,拆箱需要强制类型转换
表达式
表达式
操作数需要是numeric/char,++/--还可以是枚举类型
算术运算后的结果是包含操作数类型且不小于int的类型
无符号数和有符号数做算术运算时,-uint会转换成Long, -ulong会报错
uint和long以外的有符号整型做运算,会转换成Long
ulong和有符号整型做运算会报错
decimal不能和float/double做运算
判断对象是否属于某个类,用is运算符
sizeof运算符只能用在值类型
位运算 结果是两个操作数的较大的类型,整型类型和字符类型的最小结果类型是int
移位运算 结果是移位的操作数,最小int
代码级溢出检查
用checked{
}包装要检测发生溢出的代码
执行命令 csc /checked Test.cs
typeof运算符,取类型
声明
声明
namespace
class, interface, struct
enumeration
block 方法块
可见性规则
名字可以在声明之前就使用(除了局部变量)
如果在内部声明空间里定义了和外部空间里同名的变量,在内部,内部的变量会把外部的同名变量覆盖
枚举常量名称需要枚举类型名称限定才能访问
namespace
两个程序拥有同名的namespace,两个程序可以合并,同名的namespace会合并,namespace相当于容器,把这些容器中的东西纳入考虑范围,即使用namespace中的A时,可以直接调用,而使用不在namespace中的B时,要写***.B
namespace的子空间必须写明,Util和Util.Figures不能通过一个using Util全包括了,不能够访问子名字空间
关于语句块内的变量的生命周期,语句块本身不是声明空间,而是属于封闭方法块的声明空间。一个方法块的命名空间包含了它的嵌套块的命名空间。
所以最后一行声明的 c与前边嵌套块里的变量重复了。注意嵌套块与嵌套块之间可以有同名变量,但不能在方法块里调用,也不能在方法块里声明一个同名变量。
声明(续)
语句
赋值语句 左值提供地址,右值提供值
string Split分割,split(',')以,分割
string String.Join("", 分割的string数组)连接
switch语句
判断条件不能是浮点数,比较采用的是减法,浮点数相减可能有精度问题
每个case和default必须以break结束,不能够像C一样执行完一个case不break自动执行下一个case的语句
可以通过goto case *来跳转到某个case
foreach语句
集合/数组的迭代器
跳转
goto语句 在switch语句块中可以直接跳转到某个case,可能会破坏程序的结构化特点,不能从循环体外跳进循环里(不能跳进块里)或者try/catch的finally块
return 语句
函数和过程:函数有返回值,过程没有返回值,返回void的函数可看做过程,return可以直接跳出函数/过程
栈里有局部变量(活动记录区), 堆里有全局变量
Main函数里的返回值返回给了操作系统(与java不同)
控制台输出
输出格式 占位符 0开始递增表示第几个变量(表达式中的n)
中括号表示可选的
"{" n ["," width] [":" format [precision]] "}"
width宽度,format格式,precision保留的小数位数(精度)
format
d 整数
f 浮点数(默认2位)
n 逗号(自动在每三位加逗号,千,百万,十亿)
e 科学计数法
c 货币
x 16进制
g 默认
字符串格式转换
String.Format( )括号里和控制台输出的格式一致
文件数据流
输出
FileStream 数据流,输出到文件,建立连接
StreamWriter 写到文件
先建立连接,再写/读
数据流用完要关闭,否则一直占有资源
同一时刻只能由一个StreamWriter
输入
键盘输入 ReadLine可以读入整个一行的内容(读到第一个回车/换行),Read会读到第一个空格
文件读入如果用Read(),会读到EOF(end of file)文件的最后会有EOF
读的写法把StreamWriter 换成StreamReader
在控制台运行程序,文件名 +” *&@&^%(@ “
后边的东西是参数,就是static void Main(string[] args)的args,按照空格拆分,每遇到一个空格认为是一个参数
类和结构体
类
分配在堆里,,存储的是值的引用。指针放在栈里供调用,用new创建对象,可以继承一个别的类,可以实现多个接口
结构体
分配在栈上,存储的是值。消耗内存少,不需要垃圾回收,可以用new分配空间,不能在声明属性的地方给属性初始化,不能够声明无参构造函数,不能继承/被继承类
可见性修饰符
默认类、结构体、接口、委托、枚举为internal,程序集内可见,接口的成员、枚举的成员默认为public,为了直接被实现/被调用,类和结构体的成员默认为private
名字空间里的内容不能显式声明为private、protected、protected internal
字段和常量
字段
类的字段可以在声明时初始化,初始化的值必须是可计算的,结构体的字段不能初始化
常量const
在声明时必须初始化,不可更改,属于类而不属于对象,默认是静态的
只读字段
readonly关键字,必须在声明时/构造函数里初始化,不可以在之后被修改,属于对象
静态字段
属于类,类名加点调用,常量不能被声明为静态(与默认的重复)
静态方法也属于类,只能修改静态字段
参数
值参数
值调用,形式参数是实际参数的拷贝,实际参数可以是表达式
引用参数 ref
引用调用,形式参数是实际参数的一个别名,传递实际参数的地址,实际参数必须是值(在调用前必须初始化)
输出参数 out
类似于引用参数,调用者不会传递任何值给被调用者,编译器保证从被调用者传出的值被赋值过。
可变参数
传入的形参可以用params 关键字实现动态变化(用数组,放在形参最后)
例如: void add(out int sum, params int[] val){}数组长度动态变化,其中传入的可变参数不能是引用或者输出参数
方法重载
低精度到高精度可以匹配,如果有多个可以匹配的重载函数可以调用时(最接近的不止一个函数时),编译器会报错
重载的方法不能只有返回类型不同,也不能同时声明同类型的可变参数和有限参数的方法,因为CIL只包含了方法的描述,不包含方法的地址。
重载是静态多态性的体现。
构造方法
可以通过在方法参数列表之后加: this()的方式(在方法头里!!!)调用其他的构造方法
默认构造方法
不同类型字段的默认值
结构体永远有默认构造方法,不能声明无参构造,所有的构造方法都要初始化所有字段
静态构造方法
不能有参数,没有可见性修饰符,每个类只能有一个,在类被第一次使用之前被调用,用于初始化静态字段
析构方法
没有可见性修饰符,在对象被垃圾回收机制删除前调用,可以用于关闭打开的文件,基类的析构在结束前自动被调用,结构体不能有析构
属性(get/set 特性)
为了访问私有成员,把属性用公有的方法封装,编译器会自动在涉及修改/查看某个字段时调用get/set,使用像重载了赋值符号,其中封装的方法可以只有一个,则只能使用封装的方法对应的功能,比如只有get,则使用时只能把别的变量赋值为和这个字段相等的值,相当于readonly/writeonly
静态的字段也可以如此封装
好处:允许只读或者只写字段;可以在访问时验证字段;接口和实现的数据可以不同;替换接口中的数据。
索引器
索引器 Indexers,用index把复合的变成数组
public int this[int i]{...},函数体内是get set
类似于对[]操作符的重载,可以直接通过下标访问字段内容,可以用不同的索引器类型重载
运算符重载
public static A operator +(A a, A b){} 必须用static
必须返回运算结果
可以被重载的运算符:
=和!=; <和>等不能单独重载,必须同时重载/不重载
转换操作符
类型转换
隐式implicit 在使用时可以直接赋值
显式explicit 在使用时需要前加括号写上转换成的类型
在声明时写明显式/隐式,调用时根据不同的规则调用,括号里的类型转换成声明的类型,所以把括号里的类型的值赋值给声明的类型的变量。
嵌套类型
内部类可以访问到所在外部类的所有成员,其所在外部类只能访问到内部类的公有成员
与java可见性的区别
类的成员c#是私有,类c#是程序集可见,java的两者都是包内可见
继承
构造方法不能被继承,继承的方法可以重写
继承除了private和构造函数之外的所有内容
Override重写 子类中和父类完全同名
Overlap 交叉、重复
没有显示声明父类的继承自Object类
子类和父类的方法调用:
从父类继承的函数如果有重写,调用重写的,在构造子类对象的时候先调用父类的构造函数,这个构造函数里边再调用到的方法调用子类的(继承来的也是从子类里去调用,用new的话静态类型是父类的时候才会调用父类的函数)
对象的静态类型和动态类型
静态类型:声明中指定的类型,只看声明,不看初始化调用的谁的构造
动态类型:引用对应的地址里放的对象的实际类型,看实际调用的构造
强制转换转换的是静态类型,null可以转换成任意引用类型
as运算符,可以在强制转换成功时进行转换,失败时把引用赋值为空引用(null)
输出时值为null的引用输出以后是空的……因为是空的所以也没有办法tostring()
重写方法
只有在声明时用了virtual关键字的方法可以被子类重写,重写时加override关键字,在子类中声明方法时 public new int x() 让编译器知道父类可以被子类覆盖掉,不会报warnning
方法的签名必须完全相同 可见性、参数数量、参数类型、方法类型
特性和索引器也可以被重写,静态方法不能被重写,因为静态方法属于全局,重载会造成重复定义。
重写是动态多态性的体现
动态绑定
调用方法时按照动态类型使用对象
隐藏
子类中可以通过在声明方法时加关键字new来隐藏从父类中继承来的相同签名的方法,在子类对象调用方法时默认调用子类的方法,如果要通过子类对象调用父类的方法,需要强制类型转换成父类类型的对象
动态绑定和隐藏结合
从声明时最父类静态方法向子类中往下(动态类型)找,找到非override(可能是最后重写的此方法也可能是第一次出现使用关键字new覆盖了之前的方法的地方(调用new之前的方法),这样一来new之后的方法就和原来的方法不是一个方法了)的方法就调用,否则一直往下找
可以解决Java语言中比较脆弱的基类问题,对调用的函数如果不能分清是应该调用父类的函数还是子类的函数,比如父类的某个函数里调用了父类的另一个函数而且这个被调用的函数在子类中也存在,且子类中的函数会对数据进行关键操作,本来想调用父类的方法时却调用了子类的方法,在调用时没有警告,可能会引起巨大的问题。(低级格式化、高级格式化问题),c#中会强制要求使用override或new来避免问题。
拷贝
深拷贝:指的是拷贝一个对象时,不仅仅把对象的引用进行复制,还把该对象引用的值也一起拷贝。这样进行深拷贝后的拷贝对象就和源对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响。例如:值类型的拷贝
浅拷贝:指的是拷贝一个对象时,仅仅拷贝对象的引用进行拷贝,但是拷贝对象和源对象还是引用同一份实体。此时,其中一个对象的改变都会影响到另一个对象。例如:引用类型的拷贝
子类的构造函数
子类构造时会先调用父类的缺省构造函数,再执行子类的构造函数,如果父类没有无参的构造函数,会报错,解决方法:给父类定义无参构造函数;2.在子类构造函数执行时先显式定义父类构造函数,可以这样写
class B:A{ public B(int x) :base(x){}} 调用了A的有参构造函数A(int x){ }
抽象
abstract方法 在声明时不实现,隐式virtual(不能再显示定义),有抽象方法的类需声明为抽象类
sealed关键字 不允许再被继承
接口
可以有属性(get/set)、方法、索引器、事件,不能有字段、析构,构造,运算符重载、嵌套类型
接口里的属性的get/set方法都不能实现,都要在非抽象类的子类中实现
成员不能是静态
隐式已经是abstract(同样也隐式是virtual)
实现接口的方法时,不能用override关键字,不是重写,如果实现的方法允许实现接口的类的子类重写,要再声明为virtual
接口不能实例化,但是可以用静态类型声明接口对象,只要动态类型不是接口类的对象(普通类或null都可以)就可以,同样可以通过is关键字判断对象是否属于接口类
如果一个类实现的多个接口中出现了同名函数,在实现时要写明:
接口名.函数名(),调用时通过不同的静态类型的接口对象调用
注意:这种情况不能把同名函数写成Public,会报错,是CLR的问题
类和接口的异同:
Class |
Interface |
可以实例化 |
不能实例化 |
可以包含字段、构造、析构、运算符重载、嵌套类型等 |
不能包含字段、构造、析构、运算符重载、嵌套类型等 |
包含方法的实现 |
不包含方法的实现 |
一个类只能继承一个其他的类 |
一个类可以实现多个接口 |
一个类可以继承多个接口 |
一个接口可以继承多个接口 |
类可以包含事件、索引器、方法、属性 |
接口可以包含事件、索引器、方法、属性 |
委托和事件
委托
返回类型 + 函数名 + (参数列表) 返回类型和参数组成方法的签名
delegate + 返回类型 + 委托名 + (参数列表) 声明委托类型
委托名 委托变量名 ;//声明一个委托类型的委托变量
方法签名和委托类型里的签名(返回类型和参数类型)一致
把委托变量赋值为方法名(也可以new一个委托类型的变量new A(函数名)),则委托变量相当于一个函数指针了,可以调用函数
例:
给委托分配方法也可以直接greetings = SayHello(如果不是静态方法的话需要写明对象加点或者放在类的方法里)一个委托变量可以是空的,没有分配方法,这时这个委托变量不能被调用。委托变量可以存在数据结构里,可以作为参数传递。
在类外使用委托,如果是静态方法需要类名加点,动态方法需要对象加点
多播委托
"委托变量 += 方法"可以再绑定一个方法,所以在执行委托时会执行两个函数
可以"委托变量 -= 方法"解绑方法,解绑时先从最后绑定的方法开始找,找到第一个同名的解绑。
如果绑定的方法是函数,即有返回值,则最后委托变量的返回值是最后一个函数的返回值,如果参数有ref,后边的函数传入的参数可能已经被前边的函数修改了
事件
特殊的委托
把方法的调用和修改隔离开,只有声明方法的类可以触发event域,外部只能通过+=或者-=来改变event域,不能使用=改变event域,可以让委托变量不会被外部随意修改
还需要在Model声明event之前在外边声明一个delegate,名为Notifier,参数为string
注意delegate的可见性不能比event小,即类型的可见性不能比变量小
判断不为空(null)才能调用
异常
必须来源于System.Exception
stacktrace 堆栈跟踪,检查异常的位置
层层调用的方法中抛出了异常,会一层一层往外抛,直到被某一层catch或者最终抛给操作系统。更方便但是鲁棒性不好。抛出异常的每一个函数都会终止,不会继续执行。
委托的异常和普通的函数调用异常是一样的。
如果try块后边没有任何的catch,此时必须有finally块
名字空间和程序集
名字空间
命名空间 编译时构造,容器,里边放的是各种类型,包含类、接口、结构体、枚举类型、委托……
可以嵌套
使用其他名字空间里的类型,可以导入(using Util),也可以在限定名中指定(Util.Color)
和java的包的区别:
c#中每个文件里可以包含多个名字空间,而java的文件里每个只能有一个包;
c#中的名字空间和类不映射到目录和文件,java的包和类映射到目录和文件;
c#中导入名字空间可以把名字空间中所有的公有类型全部导入,java是选择性的导入,用.*表示导入全部的公有类型;
c#的可见性有internal,java的可见性有package;
程序集
被编译到同一个dll文件或者exe中的程序属于一个程序集
运行时构造,可能包含来自不同名字空间的类型
编译指令举例:
csc A.cs => A.exe
csc A.cs B.cs C.cs => B.exe (if B.cs contains the Main method) csc /out:X.exe A.cs B.cs => X.exe
csc /t:library A.cs => A.dll
csc /t:library A.cs B.cs => A.dll
csc /t:library /out:X.dll A.cs B.cs => X.dll
csc /r:X.dll A.cs B.cs => A.exe (where A or B reference types in X.dll)
csc /addmodule:Y.netmodule A.cs => A.exe (Y is added to this assembly; but Y.netmodule remains as a separate file)
.dll文件在调用时加载,编译出的.exe文件中存有需要的dll文件版本号,加载时会加载对应版本号的dll
公有/私有程序集
private assembly只能被一个应用程序使用、保存在应用程序目录中、不要求强命名、无法签名;
public assembly可以被所用应用程序使用、保存在全局程序集中、必须有一个强命名、可以签名
私有程序集只能被一个程序使用,没有强名称
公有程序集可以被所有的程序使用,有强名称
强名称
包含四部分:程序集名称、程序集版本号、程序集的文化、程序集公钥。可以使用sn.exe生成私钥文件。
程序集用私钥签名,公钥存在程序集里
可见性修饰符 internal
程序集内可见,在不同的程序集里即使是同一个namespace也不可见,在同一个程序集里即使不在同一个namespace,通过using所在的namespace也可见
特性
用户定义的元信息,程序中各种元素(比如类、方法、结构、枚举、组件等)的行为信息的声明性标签,如编译器指令和注释、描述、方法、类等其他信息。用中括号框起来,可以有多个特性
通式:
[attribute(positional_parameters, name_parameter = value, ...)]
positional_parameters 规定必需的信息(特性的构造),name_parameter 规定可选的信息(特性的属性)。
Obsolete特性
用于表示过时。这个预定义特性标记了不应被使用的程序实体。它可以让您通知编译器丢弃某个特定的目标元素。例如,当一个新方法被用在一个类中,但是您仍然想要保持类中的旧方法,您可以通过显示一个应该使用新方法,而不是旧方法的消息,来把它标记为 obsolete(过时的)。
[Obsolete(message,iserror)] ,也可以只传入message
参数 message,是一个字符串,描述项目为什么过时的原因以及该替代使用什么。
参数 iserror,是一个布尔值。如果该值为 true,编译器应把该项目的使用当作一个错误。默认值是 false(编译器生成一个警告)。
Conditional特性
标记了一个条件方法,其执行依赖于指定的预处理标识符。
它会引起方法调用的条件编译,取决于指定的值,比如 Debug 或 Trace。例如,当调试代码时显示变量的值。
[Conditional(conditionalSymbol)]会在定义了conditionalSymbol的时候编译这个特性附属的内容,如果没有定义conditionalSymbol就不会编译特性附属的内容,也不会执行这些内容调用的地方。
Serializable特性
可串行化/序列化,存储和获取磁盘文件、内存或其他地方中的对象。适用于类,拥有此特性的类自动序列化。
NonSerialized不可串行化,适用于字段,被排除在序列化之外。引用了自身的类就需要把引用的自身的字段设置为不可串行化之后这个类才可以串行化。
AttributeUsage特性
描述了如何使用一个自定义特性类。它规定了特性可应用到的项目的类型。
[AttributeUsage(validon, AllowMultiple=allowmultiple, Inherited=inherited)]
参数 validon 规定特性可被放置的语言元素。它是枚举器 AttributeTargets 的值的组合。默认值是 AttributeTargets.All。
参数 allowmultiple(可选的)为该特性的 AllowMultiple 属性(property)提供一个布尔值。如果为 true,则该特性是多用的。默认值是 false(单用的)。里边写get/set
参数 inherited(可选的)为该特性的 Inherited 属性(property)提供一个布尔值。如果为 true,则该特性可被派生类继承。默认值是 false(不被继承)。里边写get/set
线程
System.Threading
并行过程,抢占式,共享地址空间
可以通过Thread t = new Thread(M) 来开启一个执行M的线程,其中M是方法名
调度的最小单位是进程,资源分配的最小单位是线程
抢占式意味着如果当前占用cpu的线程不交出cpu的使用权,则其他线程就无法执行
一旦调用了sleep,即使是0也会交出cpu的使用权
与java中线程的区别
c#中不需要写Thread的子类去把要执行的线程写入类中(java需要或者实现runnable接口)
任何一个没有返回值的方法都可以作为线程启动(java线程的活动必须写在方法run里)
中止方法可以使用,可以抛出ThreadAbortException异常,捕获后在catch子句末尾重新抛出(java不能使用stop方法)
线程的状态
开始之前 unstarted
调用start 开始 running
调用suspend 挂起 suspended
调用resume 重新恢复 running
调用abort 退出 stopped
线程.join方法 主线程等待子线程结束再继续执行
abort方法
层层抛出异常,一直抛到最后,执行完finally块里的东西
线程对象调用abort方法时开始抛出
互斥
确保当一个线程位于代码的临界区时,另一个线程不进入临界区。如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。
lock是语法糖,自动在使用时加锁,使用后释放
实际上使用的是monitor类
Monitor.Enter(object)方法是获取锁,Monitor.Exit(object)方法是释放锁
使用了lock的类就起到monitor的作用
wait和pulse
Monitor.Wait 释放对象的锁并进入对象的等待队列,对象的就绪队列中的下一个线程(如果有)获取锁并拥有对对象的独占使用。Wait()就是交出锁的使用权,使线程处于阻塞状态,直到再次获得锁的使用权。让当前使用对象的线程等待,释放锁
Monitor.Pulse 当前线程调用此方法以便向队列中的下一个线程发出锁的信号。接收到脉冲后,等待线程就被移动到就绪队列中。在调用 Pulse 的线程释放锁后,就绪队列中的下一个线程(不一定是接收到脉冲的线程)将获得该锁。pulse()并不会使当前线程释放锁。
两个方法必须在lock块中使用,wait常用于等待一个条件为真,注意一个线程从等待中唤醒以后可能就再也不能使条件为真了,所以wait应该被放在循环里并检查条件是否为假
PulseAll(v)可以让所有等待使用v的线程唤醒,使v被其中一个加锁
同步缓冲区的可视化,使用的就是wait和pulse
C#2.0简介
泛型
为了能够使编写的类或方法对所有的类型都可用,需要引入泛型。
采用把类型声明为object类的方式也可以实现,但是就涉及到了装箱和拆箱,需要更多的时间消耗,而且可能会导致类型的使用混乱,不同数据类型混用导致出错
有尖括号的类是泛型类,可以用于类、结构体、接口、泛型方法,Element叫占位符,可以被替代
class Buffer<Element>{
private Element[] data;
public Buffer(int size) {...}
public void Put(Element x) {...}
public Element Get() {...}
}
使用的时候实例化,Buffer<int>a = new Buffer<int>(100)
还可以对参数添加限制,比如参数可以进行比较(比如实现IComparable接口)
根据优先级插值排序的方法,Priority:IComparable要求参数Priority的类型必须实现了IComparable接口(是其子类即可),使这个类型之间可以比较
通过编译时类型检查来使用指定的数据类型
还可以带多个参数
class Buffer <Element, Priority>,两个参数可以是不同类型,如上图
在where 后可以限定类型必须有某种形式的构造函数
要求E必须有无参构造函数
抛出E类型的异常
应用:
泛型的父类和子类问题
泛型子类可以继承自泛型类,也可以继承自已经实例化的泛型(其实已经不是泛型了),也可以继承自普通类
子类和父类的泛型可以不同(参数个数)
不允许父类是泛型类而子类不是泛型类
泛型类必须包含泛型方法
三种继承示例
泛型的重写
父类的虚函数的参数可以是Element(占位符),
子类里重写的时候
如果子类是普通类,必须把泛型实例化,不能再用占位符
如果子类是泛型类,可以保留占位符
不能出现子类是普通类但是还在函数里使用占位符的情况
泛型方法
可以使用任意数据类型的方法,上图的方法为任何实现了IComparable接口的类型都可以使用,排序数组
调用方法,字典序排列
编译器会根据方法的参数类型推断要代替占位符类型的具体类型
所以方法的调用可以是
泛型类型的字段要初始化成default(类型)
泛型类的编译
泛型类只在实例化为新的类型(此处指引用、值等,一次引用编译可以让以后都不必再编译任何的引用类型,一次int编译可以让以后不需要再编译int类型)的才会编译,否则只在第一次出现的时候编译,之后都不再重新编译
c#3.0简介
LINQ
语言集成查询
注意输出的要求,ToUpper,转换成大写
Lambda表达式
为委托添加的语法糖
格式:(参数...)=>{方法主体}
参数和返回值的对应位置:
右侧可以是返回结果的块(如果委托类型为void右侧就不需要有返回结果)
右侧可以对外部的变量进行操作
示例:(委托是System.Linq名字空间里定义的泛型委托)