C#基础笔记
动态链接库(DLL,Dynamic Link Library)和程序都属于程序集(Assembly)。
C#编译器只是将C#代码转换成公共中间语言(Common Intermediate Language, CIL),VES(Virtual Execution System 虚拟执行系统,也称运行时)将CIL编译成机器码,这个过程称为即时编译(JT编译,Just-in-Time)。这些代码称为托管代码(Managed Code)。
在数值范围内,decimal数字表示的十进制数都是完全准确的,因为其基数是十进制的。浮点数是基于二进制的,所以有误差。可以在数值最后加上m,表示decimal类型,加f,d分别表示float, double.
直接将一个值放到源代码中的行为称为硬编码(hardcoding)。若是修改数值,必须重新编译代码。为了避免这种情况,可以考虑从外部来源获取数值,如配置文件(如公司系统的config.xml)。
round-trip格式说明符返回的是一个完整的字符串,假如将该字符串转换回数值,那么将肯定得到原始值。
不能将换行符直接插入到一个不以@开头的字符串中(想想在C#里取数SQL基本都是以@开头)。
string类型的关键特征在于它是不可变的(immutable),没有机制(自带方法)可供修改一个字符串的内容。可以将方法将其处理后的返回值重新赋值给它。
string.System.Text.StringBuilder同样不可变,但System.Text.StringBuilder类型则可通过Append(),Insert(),Remove(),Replace()等进行修改。
所有类型都可以划分为两类:值类型和引用类型。值类型储存的是数值,在栈上。引用类型储存的是内存地址,同样在栈上。但通过内存地址在堆上找到数值。所以,修改值类型,只会对该变量影响。修改引用类型,所有指到该内存地址的值都会被修改。
在checked块内,如果在运行时发生一次溢出的赋值,就会引发一个异常。在unchecked块内,它会将数据截断,不引发异常。C#默认是后者。
在C#2.0开始,所有的基本数值类型都包含一个静态TryParse()方法。该方法与Parse类似,只是其需要两个参数,返回布尔值。在转换失败情况下,不会报错,只是返回false。
多维数组结构必须一致,交错数组则要求对数组中的数组进行实例化。两者声明不同,多维数组是int[,] ,交错数组是int[][] 。
属性Length返回数组中元素的总数。如果是多维数组,则返回所有子元素的个数,如二维数组,2行3列,则6个。如果是交错数组,由于Length返回的是外部数组的元素数,所以Length只作用于外部数组,只统计有多少个数组,不管各自内部有多少个元素。
System.Array.BinarySearch()传入数组必须升序,否则出错(注意不仅是排序,是升序)。如果以降序,或者搜索元素不存在,那么返回值为负值。使用取反运算符~可以返回大于搜索值的第一个索引(暂时不知有鬼用)。
System.Array.Reverse()反转数组
System.Array.Clear()不删除数组元素,而且不将长度设为零,因为数组大小是固定的,不能修改。所以Clear只是将每个元素设为默认值,引用类型Null,数值0,bool false, char '\0'。
string类型的ToCharArray()方法,返回一个char数组。
DOS/Windows系统采用CRLF(即回车+换行)表示下一行
Linux/UNIX系统采用LF表示下一行
MAC系统采用CR表示下一行
注:CR的ASCII是十进制数的13,十六进制的0x0D,LF为10和0x0A
多数的计算机语言中,CR表示为字符或者字符串就是"\r",LF为"\n"
为了在多个平台都可运行,可用System.Environment.NewLine属性来输出当前环境的换行符。
2013.1.11
从C#2.0开始,所有异常都派生自System.Exception类,所以所有的异常都可以用catch(System.Exception exception)块进行处理。然而,一个更好的做法是编写专门的catch块来处理更具体的派生类型(如System.FormatException),从而获取有关异常的具体信息,有的放矢地进行处理,并避免使用大量条件逻辑来判断具体发生了什么类型的异常。
这也是C#规定catch块必须从“最具体”到“最不具体”排列的原因,例如用于捕捉System.FormatException的catch语句不能出现在System.Exception之后,因为System.FormatException 较 System.Exception更具体。
命名空间可以是嵌套的,这意味着假如一个方法在一个更具体的命名空间中,那么即使存在using System这样的指令,也不能在使用更具体的命名空间时省略System字样,比如要使用System.Text中的方法,则必须指定using System.Text命名空间。换句话说,就是必须准确指明命名空间。
C#中的二元运算符要求进行一次赋值或者调用,它们总是返回一个新的结果,而且两个操作数都不能修改。在C++中像1+1;这样不进行赋值的语句通过编译,但在C#中,只有赋值,调用,递增,递减和new对象表达式才允许作为仅有运算符的语句使用。
2013.1.14
作为一个良好的编程习惯,我们应该只在属性实现的内部,访问为属性提供支持的字段。换言之,我们使用的应该一直是属性,而不要直接调用字段。许多时候,即使是在包容属性的那个类中,也不应该从属性实现的外部,访问它所支持的字段。这样一来,在为属性添加了验证逻辑或者其他额外的逻辑之后,整个类就可以马上利用这些逻辑。
C#允许属性像字段一样使用,只是不允许将它们作为ref或者out参数值来传递,因为ref和out参数值在内部实现时,需要将内存地址传给目标方法,但属性可能是没有支持字段的虚字段。
一旦为一个类显式添加了构造器,C#编译器就不再自动提供默认构造器。没有必要依赖由编译器定义的默认构造器,程序员可以显式地定义一个默认构造器,这也许是一种能将某些字段初始化成特定值的构造器。、
对象初始化器。在创建对象的构造器调用之后,现在增加一对大括号,并在其中添加一个成员初始化列表,赋值等号左边是字段属性,右边是值。如:
public Student (string id, string firstname, string lastname){ Sex = '1', Score = 60}
使用this在一个构造器中调用另一个构造器,可以避免输入重复的代码。这称为构造器链(constructor chaining),它是用对象初始化器来实现的。如:
public Employee(int id, string firstName, string lastName):this(firstName, lastName)此处this表示构造器
静态构造器。不是显式调用的,运行时会在首次访问类时自动调用静态构造器。
【关于const和readonly】
首先先解释下什么是静态常量以及什么是动态常量。静态常量是指编译器在编译时候会对常量进行解析,并将常量的值替换成初始化的那个值。而动态常量的值则是在运行的那一刻才获得的,编译器编译期间将其标示为只读常量,而不用常量的值代替,这样动态常量不必在声明的时候就初始化,而可以延迟到构造函数中初始化。
当你大致了解上面的两个概念的时候,那么就可以来说明const与readonly了。const修饰的常量是上述中的第一种,即静态常量;而readonly则是第二种,即动态常量。那么区别可以通过静态常量与动态常量的特性来说明:
1)const修饰的常量在声明的时候必须初始化;readonly修饰的常量则可以延迟到构造函数初始化
2)const修饰的常量在编译期间就被解析,即常量值被替换成初始化的值;readonly修饰的常量则延迟到运行的时候
此外const常量既可以声明在类中也可以在函数体内,但是static readonly常量只能声明在类中。
http://www.cnblogs.com/royenhome/archive/2010/05/22/1741592.html
2013.1.22
解决单一继承的问题,可以用聚合来解决,即在一个类中聚合另一个类,使后者成为前者的一个成员。
重写一个成员,会造成运行时调用最底层或者说派生得最远的实现。
假如有如下继承链:a<-b<-c<-d,在a中有虚方法public virtual do();且b,c,d均重写了do方法。
那么a A = new c(); A.do(); 从a开始往下寻找,将调用c的do方法,因为从a到c,c派生到最远。同理如果a A = new d(); A.do();将调用d的do方法。b亦同理。
如果重写一个方法没有使用override关键字,编译器会报告一条警告消息。
new修饰符在基类面前隐藏了派生类重新声明的成员。在这种情况下,不是调用派生得最远的成员,相反,基类的成员会搜索继承链,找到使用了new修饰符的那个成员之前的成员,然后调用该成员。
假如有如下继承链:a<-b<-c<-d,在a中有非虚方法public do(); 在b中有虚方法public virtual do(); c继承并重写了do(),d用new声明了一个do()方法。
那么分别实现A,B,C,D四个实例,分别对应a,b,c,d四个类。将D赋值给A,B,C。下面分析四者调用do方法时不同的结果:
D.do();调用d的方法,这个没什么好解析的。
C.do();从c到d,虽然构成一个继承链,但do方法在d是new修饰的,不在重写范围,所以调用的是c的方法。
B.do();从b到d,同样构成一个继承链,但到c是do方法重写的最后一站,所以与C.do();一样,调用的是c方法。
A.do();从a到d,a的do方法没有重新声明任何基类成员,而且是非virtual的,所以,它会被直接调用,调用a方法。
接口。一旦某个类声明自己要实现一个接口,则该接口的所有成员都必须实现。
接口的一个重要特征在于,它们永远不能被实例化,不能使用new来创建一个接口。也正是这个原因,接口不能有构造器或终结器。接口也不能包含static成员,因为接口的一个重要目的就是实现多态性,而假如没有实现它的一个类型的实例,多态性是没有什么价值的。
显式实现接口成员,只有通过接口本身来调用它,而隐式实现则可以直接调用。
比如a显式实现接口iCompare,那么a的实现A调用Compare()方法,必须先将其转为iCompare类型,再调用,否则出错。即必须这样调用:((iCompare)A).Compare();而隐式实现的话,直接调用A.Compare();但同时a中的Compare()方法必须是public的(默认是private)。
更改或者删除一个特定接口成员的签名,明显会造成现有代码的中断,因为除非进行修改,否则对那个成员的任何调用都不再能够编译。更改一个类的public或者protected成员也同样如此。然而跟类不同的是,增加一个接口成员,同样也不能编译。因为要实现接口的所有成员,因此要进行修改才可以。
在开发阶段更改接口显然是可以接受的,虽然开发者可能要为此付出相当多的劳动,然而一旦接口对外发布,就不应该更改它,相反,你应该创建第二个接口,该接口可以从原始接口派生。
2013.1.27