C#基础知识回顾

今天在一个网站看到了一篇关于C#基础知识的文章,篇幅很长,浏览了一下,确实是基础知识,虽然顺序凌乱,没有章法,但是对于我们经常写代码的人确实应该尽量多的记住一些基础知识。 掌握牢固的基础知识编程才能得心应手,最基本的东西也应该注重,因为细节决定成败。

 

引用类型是类型安全的指针,它们的内存是分配在堆(保存指针地址)上的。
string、数组、类、接口和委托都是引用类型。


强制类型转换与as类型转换的区别:当类型转换非法时,强制类型转换将抛出一个system.invalidcastexception异常,
而as不会抛出异常,它返回一个null值。

用using创建别名:

访问限定符:
public  该成员可以被其他任何类访问
protected 该成员只能被其派生类访问
private  该成员只能被本类的其他成员访问
internal 该成员只能在当前编译单元的其他成员访问

带参数列表和返回值的main方法:

构造函数(constructor)包括实例构造函数和静态构造函数。
构造函数与类名相同,且不能有返回值。例:

类的静态成员属于类所有,不必生成实例就可以访问,它是在载入包含类的应用程序时创建的,
但静态方法不能访问类的实例变量和方法。通常,静态变量是在定义时就赋初始值的。
类的实例成员属于类的实例所有,不创建实例对象就无法对其进行访问,实例成员可以访问类的
静态成员和其它实例成员。
调用基类的析构函数:

常量:其值是在编译时设定的,必须是数值文字。默认状态下常量是静态的。例:

常量是编译时就确定的值,只读字段是在运行才能确定的值。比如运行时才能确定的屏幕分辨率。
只读字段只能在类的析构函数中赋值。

静态只读字段:

在类的继承中,类的析构函数是不会被继承的。
一个派生类只能从一个基类继承,不能同时从多个基类继承,但可以通过继承多个接口来
达到相同目的。实现多继承的唯一方法就是使用接口。例:

密封类是不能继承的类,抽象类不能被定义为密封类,且密封类的私有成员不能用protected修饰,
只能用private。例:

关键字ref和out用于指定用引用方式传递方法的参数。
它们的区别是:ref参数必须初始化,而out参数不需要初始化。所以在方法处理代码依赖参数的
初始化值时使用ref,不依赖初始化值时使用out。
对out参数即使在传递前对其进行了初始化,其值也不会传递到方法处理函数内部。传递时系统
会将其设为未初始化。所以在方法内部必须对out参数进行初始化。


方法重载时,必须参数数目和参数类型其中之一不同,返回值不同不能作为重载。
c#不支持方法的默认值,只能通过方法重载来实现。例:

方法的覆盖:指派生类覆盖基类的同名方法,有二种方法
1)第一种是在派生类要覆盖的方法前面加new修饰,而基类不需要作任何改动。
这种方法的缺点是不能实现多态。例:

2)第二种是在派生类要覆盖的方法前面加override修饰,而基类的同名方法前面加virtual修饰。
这样就能实现多态,例:

说明:new修饰的方法覆盖不能实现多态的原因,是因为使用new时编译器只会实现早期绑定(early binding)。
即调用的方法在编译时就决定了:编译器看到instance.method()而instance的类是a,就会调用类a的method()方法。
override修饰的方法覆盖可以实现多态的原因,是因为实现了后期绑定(late binding)。
使用override时强制编译器在运行时根据类的真正类型正确调用相应的方法,而不是在编译时。
而基类的同名方法必须加virtual修饰。


类的静态方法可能通过 类名.静态方法名 这种格式来调用,不能使用 实例名.静态方法名 这种方法调用。
因为类的静态方法为类所有(是属于类本身的),而非实例所有(不是属于类的实例的)。
类的静态方法可以访问类的任何静态成员,但不能访问类的实例成员。


c#中类的变量称为字段。类的public变量称为类的公共字段。
类的属性由一个protected(也可以是private)字段和getter和setter方法构成:

只读属性是指省略setter方法的属性,只读属性只能读取,不能设置。
属性也可以用限定符virtual,override和abstract修饰,功能同其他类的方法。

属性有一个用处称为懒惰的初始化(lazy initialization)。即在需要类成员时才对它们进行
初始化。如果类中包含了很少被引用的成员,而这些成员的初始化又会花费大量的时候和系统
资源的话,懒惰的初始化就很有用了。


c#中数组对象共同的基类是system.array。
将数组声明为类的一个成员时,声明数组与实例化数组必须分开,这是因为只能在运行时创建了
类的实例对象之后,才能实例化数组元素值。
声明:

初始化:

遍历:
1)一维数组

2)多维数组

数组的维数就是该数组的秩(rank)。array.rank可以返回数据的秩。
锯齿数组(jagged array)是元素为数组的数组,例:

类的属性称为智能字段,类的索引器称为智能数组。由于类本身作数组使用,所以用
this作索引器的名称,索引器有索引参数值。例:

接口是二段不同代码之间约定,通过约定实现彼此之间的相互访问。
c#并不支持多继承,但通过接口可实现相同功能。
当在接口中指定了实现这个接口的类时,我们就称这个类“实现了该接口”或“从接口继承”。
一个接口基本上就是一个抽象类,这个抽象类中除了声明c#类的其他成员类型??例如属性、
事件和索引器之外,只声明了纯虚拟方法。
接口中可以包含方法、属性、索引器和事件??其中任何一种都不是在接口自身中来实现的。例:

说明:定义接口时,在方法、属性、事件和索引器所有这些接口成员都不能用public之类的访问限定符,
因为所有接口成员都是public类型的。


因为接口定义了一个约定,任何实现一个接口的类都必须定义那个接口中每一个成员,否则将编译失败。例:

也可以用:

这种方法来调用validate方法,因为validate在类mycontrol中是被定义成public的,如果去除public,validate方法被隐藏,
就不能用这种方法调用了,这样隐藏接口方法称为名字隐藏(name hiding)。

可以用:类实例 is 接口名 来判断某个类是否实现了某接口,例:
mycontrol is ivalidate  //mycontrol类的实例mycontrol是否实现了ivalidate接口
当然,也可用as来作转换,根据转换结果是否为null来判断某个类是否实现了某接口,例:

如果一个类从多个接口继承,而这些接口中如果定义的同名的方法,则实现接口的方法时,必须加接口名来区别,
写成 接口名.方法名。假设test类从idatastore和iserializable二个接口继承,而这二个接口都有savedata()方法,
实现savedata()方法时必须写成:

如果一个类从多个接口继承,为了方便可以定义一个新的接口,这个接口继续多个接口,然后类直接从这个接口继承就
可以了,这个叫合并接口。例:

c# 操作符优先级(从高到低)
初级操作符 () x.y f(x) a[x] x++ x-- new typeof sizeof checked unchecked
一元操作符 + - | ~ ++x --x (t)x
乘除操作符 * / %
加减操作符 + -
位移操作符 << >>
关系操作符 < > <= >= is
等于操作符 ==
逻辑与  &
逻辑异或 ^
逻辑或  |
条件与  &&
条件或  ||
条件操作符 ?:
赋值操作符 = *= /= %= += -= <<= >>= &= ^= |=


所有的二元操作符除赋值符外都是左联合的,即从左到右计算。

typeof()运算符可以从一个类名得到一个system.type对象,而从system.object对象继承来的gettype()方法
则可从一个类实例来得到一个system.type对象。例:

通过反射得到一个类的所有成员和方法:

sizeof()操作符用来计算值类型变量在内存中占用的字节数(bytes),并且它只能在unsafe(非安全)
代码中使用。例:

尽可能使用复合赋值操作符,它比不用复合赋值操作符的效率高。

for语句的语法为:

在initialization和step部份还可以使用逗号操作符,例:

在switch语句中执行一个分支的代码后还想执行另一个分支的代码,可以用:
goto case 分支;


操作符重载是为了让程序更加自然,容易理解。想要为一个类重新定义一个操作符,使用以下语法:
public static 返回值 operator 操作符 (操作对象1[,操作对象2])
说明:
1)所有重载的操作符方法都必须定义为public和static
2)从技术上说返回值可以是任何类型,但通常是返回所定义方法使用的类型
3)操作对象的数目取决于重载是一元操作符还是二元操作符,一元操作符只要一个操作对象,二元操作符则需要二个。
4)不管重载是一元操作符还是二元操作符,第一个操作对象的类型都必须与返回值的类型一致;而对于二元操作符的第二个
操作对象的类型则可以是任何类型。
5)只有下列操作符可以被重载:
一元:+ - ! ~ ++ -- true false
二元:+ - * / % & | ^ << >> == != > < >= <=
赋值操作符(+=,-=,*-,/=,%=等等)无法被重载。
[]和()操作符也无法被重载。
6)操作符的优先级是无法改变的,运算优先级的规则是静态的。

例:假设一个invoice发票类由多个invoicedetailline类(成员只有一个double类型的amount金额属性)组成,
我们重载+操作符,使之可以将invoicedetailline类的内容(注意不是金额合计)加在一起。

自定义类型转换可以编写代码实际二个不同的类、结构体之间的转换。
语法:public static implicite/explicite operator 输出类型 (输入类型)
说明:
1)转换方法必须是静态的。
2)implicite表示隐式转换,explicite表示显式转换。
3)输入类型和输出类型其中之一必须与包含转换的类或结构体类型。即转换必须与本类相关。
例:

代表的(delegate)目的与c++中的函数指针相同,代表不是在编译时被定义的,而是在运行时被定义的。
代表主要有二个用途:回调(callback)和事件处理(event)
回调通常用于异步处理和自定义处理。例:

//调用

//使用静态代表,上面的调用改为

//在需要时才创建代表,上面的调用改为

可以将多个代表整合成单个代表,例:

可以根据需要将多个代表自由地组合成单个代表,例:

c#的事件遵循“发布??预订”的设计模式。在这种模式中,一个类公布能够出现的所有事件,
然后任何的类都可以预订这些事件。一旦事件产生,运行环境就负责通知每个订户事件已经发生了。
当代表作为事件的处理结果时(或者说定义具有代表的事件),定义的代表必须指向二个参数的方法:
一个参数是引发事件的对象(发布者),另一个是事件信息对象(这个对象必须从eventargs类中派生)。
例:

microsoft windows nt和ibm os/2等操作系统都支持占先型多任务。在占先型多任务执行中,处理器负责
给每个线程分配一定量的运行时间??一个时间片(timeslice)。处理器接着在不同的线程之间进行切换,
执行相应的处理。在单处理器的计算机上,并不能真正实现多个线程的同时运行,除非运行在多个处理器
的计算机上。操作系统调度的多线程只是根据分配给每个线程时间片进行切换执行,感觉上就像同时执行。

上下文切换(context switching)是线程运行的一部分,处理器使用一个硬件时间来判断一个指定线程的时间片
何时结束。当这个硬件计时器给出中断信号时,处理器把当前运行的线程所用的所有寄存器(registers)数据
存储到堆栈中。然后,处理器把堆栈里那些相同的寄存器信息存放到一种被称为“上下文结构”的数据结构中。
当处理器要切换回原来执行的线程时,它反向执行这个过程,利用与该线程相关的上下文结构,在寄存器里
重新恢复与这一线程相关的信息。这样的一个完整过程称为“上下文切换”。

多线程允许应用程序把任务分割为多个线程,它们彼此之间可以独立地工作,最大限度地利用了处理器时间。

可以通过两种方式来得到一个thread对象:一种是通过创建一个新线程来得到,如上例;另一种在正在执行的线程调用
静态的thread.currentthread方法。
静态方法thread.sleep(int ms)可以让当前线程(它自动调用thread.currentthread)暂停指定毫秒的时间。
如果使用thread.sleep(0)那么当前线程将一直处于等待中,直到另一个线程调用这个线程的实例方法thread.interrupt方法,
等待才会结束。
使用thread.suspend方法也能挂起线程,thread.suspend方法可以被当前线程或其他线程调用,而thread.sleep(0)
只能由当前线程在执行体中调用。当线程用thread.suspend挂起时,必须用thread.resume方法恢复。不论thread.suspend
方法调用了多少次,只要调用thread.resume方法一次就可以线程恢复执行。用thread.suspend方法并不会阻塞线程,
调用立即返回。而thread.sleep(0)则会阻塞线程。所以确切地说thread.sleep(0)暂停线程,而不是挂起线程。
使用thread.abort方法可以终止正在执行的线程。当thread.abort方法被调用时,线程不会立即终止执行。运行环境将会
等待,直到线程到达文档中所描述的“安全点”。如果要确保线程已经完全停止,可以使用thread.join方法。这是一个同步
调用,同步调用意味着直到线程完全停止,调用才会返回。
thread.priority属性用于设置的线程的优先级。其值是thread.threadpriority枚举值,可以设为highest, abovenormal,
normal, belownormal, lowest。缺省值是thread.threadpriority.normal。

线程的同步是为了解决多个线程同时使用同一对象产生的一些问题。通过同步,可以指定代码的临界区(critical section),
一次只有一个线程可以进入临界区。

使用system.monitor类(锁定与信号量)进行线程同步:

说明:当执行monitor.enter方法时。这个方法会试图获取对象上的monitor锁,如果另一个线程已经拥有了
这个锁,这个方法将会阻塞(block),直到这个锁被释放。
也可用c#的lock语句来获得和释放一个monitor锁。上面同步写成:

也可以使用system.threading名称空间的mutex类(互斥类)进行线程同步。与monitor锁一样,一次只有一个线程
能获得一个给定的互斥。但mutex要慢得多,但它增加了灵活性。例:

mutex类重载了三个构造函数:
mutex()       //创建并使创建类立即获得互斥
mutex(bool initiallyowned)    //创建时可指定是否要立即获得互斥
mutex(bool initiallyowned, string mutername)  //还可以指定互斥的名称

mutex.waitone方法也重载了三次:
mutex.waitone()      //一直等待
mutex.waitone(timespan time, bool exitcontext)  //等待timespan指定的时间
mutex.waitone(int milliseconds, bool exitcontext) //等待指定的毫秒

线程的用法:
1)并发操作:比如一个程序监视多个com口,当每个com接到信息时执行一段处理时。
2)复杂长时间操作:一个长时间的复杂操作可能会使界面停滞,停止用户响应,如果还允许用户停止它,
或者显示进度条、显示操作执行进程信息时。

 

反射(reflection)就是能够在运行时查找类型信息,这是因为.net编译的可执行(pe)文件中包括msil和元数据(metadata)。
反射的中心是类system.type。system.type是一个抽象类,代表公用类型系统(common type system, cts)中的一种类型。

通过assembly类可以得到已经编译.net framework程序的中所有类型,例:

一个应用程序可以包括多个代码模块。若要将一个cs文件编译一个模块,只要执行下面的命令:
csc /target:module 要编译的模块.cs  //csc是c sharp compiler(c#编译器)
然后在应用程序中using编译的模块.cs中的namespace即可应用了。
要反射应用程序中所有代码模块(module),只要:

后期绑定(latebinding),例:

//带参数的例子

动态生成代码和动态调用的完整例子:
//动态生成代码的部分

//动态调用的部分

调用dll

例2,使用回调:

关键字unsafe指定标记块在非控环境中运行。该关键字可以用于所有的方法,包括构造函数和属性,
甚至还有方法中的代码块。关键字fixed负责受控对象的固定(pinning)。pinning是一种动作,向
垃圾收集器(garbage collector, gc)指定一些不能被移动的对象。为了不在内存中产生碎片,.net
运行环境把对象四处移动,以便于最有效地利用内存。使用fixed后指定对象将不会被移动,所以就
可以用指针来访问它。
c#中只能得到值类型、数组和字符串的指针。在数组的情况下,第一个元素必须是值类型,因为c#
实际上是返回一个指向数组第一个元素的指针,而不是返回数组自身。
& 取一个变量的内存地址(即指向该变量的指针)
* 取指针所指变量的值
-> 取成员
例:

fixed语法为:fixed(type* ptr = expression) statements
其中type也可以为非控类型,也可是void;expression是任何产生一个type指针的表达式;
statements是应用的代码块。例:

传统的com组件可以通过互操作层(com interop)与.net运行环境交互。互操作层处理在托管运行环境和非托管区域
中的com组件操作之间传递所有的消息。
要使com组件能在.net环境中使用,必须为com组件生成元数据。.net运行环境用元数据层业判断类型信息。在运行时刻
使用类型信息,以便生成rcw(runtime callable wrapper,运行时可调用包装)。当.net应用程序与com对象交互时,
rcw处理对com对象的装载和调用。rcw还完成许多其他的工作,如管理对象标识、对象生存周期以及接口缓冲区。
对象生存周期管理十分关键,因为.net gc把对象到处移动,并且当对象不再使用时,自动处理这些对象。rcw服务告诉
.net,应用程序正与托管.net组件交互,同时又使非托管com组件“觉得”com对象是被传统的com客户端调用的。

为了为com组件生成元数据包装,必须使用tlbimp.exe(typelib importer)工具:
tlbimp some_com.tlb /out:som_com.dll

 

你还可能感兴趣:十三个代码注释的小技巧

posted on 2011-12-28 09:40  c语言源码  阅读(172)  评论(0编辑  收藏  举报

导航