C#入门(1)

C#入门(1)

基本概念

三个基本概念:
CLR: Common Language Runtime, 就像是一个虚拟机和运行时环境,负责定位管理.Net的对象,还包括低层次的内存管理,线程分配,应用管理和安全检查等等.
CTS: Common Type System, 管理整个系统运行时的所有对象类型和类型间的转换和构建.
CLS: Common Language Specification, 作为.Net平台下所有的语言应共同遵守的规则,

.Net的架构: 外层的Base Class Library提供了所有语言的一些公有库和接口, 而每个语言的CLR和CTS,CLS一层包裹着一层. 这三者是语言特定的.

各种compiler将不同语言转换为IL(Intermediate Language)和MetaData, 通常是exe或者dll这样的程序集, 而在.Net FrameWork下才能进一步执行.所以同其他语言不同的是, 这里应该是.Net FrameWork提供了IL的运行库, 因此所有这些运行库和namespace都可以使用.给出了以System开头的一堆运行库.还有以MicroSoft开头的命名空间.前者是平台无关的,后者是调用Windows系统级别的.

多系统移植和跨平台:
对于C#的跨平台主要仍是实现在该平台上高效的虚拟机, 现在在其他平台上多是Mono, DotGNU两个实现,两者实现了在其他平台上的C#的运行时环境等多项内容.

对于多个依赖项和多个cs文件共同编译的文件, 有类似Makefile的C# response文件, 后缀名是.rsp 在编译时应使用@前缀:

csc @Test.rsp

具体的编译可以参考和csc.exe同目录下的default.rsp文件. 已经添加了多数的运行库, 当然如果需要忽略某些库, 可以使用/noconfig.

程序入口点的Main函数同C++一样可以定义成,void或者是int的,同样可以定义接受参数是string[] args, 不像C++, 还需要定义参数个数和参数数组指针. 在Windows系统中,一个应用程序的返回值存在一个系统环境变量%ERRORLEVEL%中, 如果需要捕捉该值则需要使用static System.Diagnostics.Process.ExitCode这一属性.

C#的命令行参数都是以/开头的,后面跟缩写.获得命令行参数可以用以下方法:Environment.GetCommandLineArgs();其中的第一个参数就是该程序名本身.使用了该种方法就不必在Main方法的参数中加string[]了.

基本API

有关系统的基本变量都在System.Environment这个类中, using System就包含了.
而Console则包含了关于控制台的基本信息,甚至包括改变颜色等等.在WriteLine({x})中,大括号包裹的数字是占位符,当参数少于占位符时报错,多于占位符时会被忽略.但是在打印的时候是按照里面的数字的顺序打印的.

格式化输出数字:
为了方便输出格式化的数字字符串,有多种后缀.

后缀 含义
C or c 货币型 接受一切数值类型,精度为后面紧跟的整型数字,输出自动加上货币符号,有关货币符号的改变可以参见CultureInfo.在外部改,占位符中仅仅包含{x:cy},没有发现其余的可接受参数
D or d 十进制数 仅接受整型,返回结果是整型数字,负号可选,精度为最小位数. 如1234("D") -> 1234, -1234("D6") -> -001234
E or e 指数型 接受一切数值类型,返回结果为指数型计数,精度说明符为小数位数,而默认精度说明符为6
F or f 定点型 接受一切数值,返回结果是整数和小数,负号可选,精度说明符为小数位数,精度大于当前的小数位数直接补零
G or g 常规型 接受一切类型,返回结果是最紧凑的定点表示法或者是科学技术法,精度说明符为有效位数,具体取决于给定数,多余0会去除
N or n 数字 接受一切类型,返回结果是整数和小数,组分隔符和小数分隔符,负号可选.精度说明符是小数位数,默认是小数点后两位,且四舍五入
P or p 百分比 接受一切数值类型,返回乘以100并显示百分比符号的数字,精度说明符表示小数位数
R or r 往返过程 仅接受Single,Double,BigInteger,精度运算符会被忽略,基本上就是返回相同的数字.
X or x 十六进制 仅接受整型,结果为十六进制字符串,精度说明符是结果字符串中的位数.

这些格式化输出在toString()和string的静态方法Format中都可以应用.
所有这些数值类型都是从栈中分配空间,而不是类似new对象是从堆中分配空间.这和C++一样.
本质上int是System.Int32的缩写.

System.DateTime和System.TimeSpan是有关C#时间运算的两个类,前者主要是关注年月日,而后者主要关注时分秒.

BigInteger定义在System.Numerrics中. 同时还有Complex.
需要注意的是BigInteger是immutable的,即一旦赋值,就不可再变.但是由于多态的存在,系统默认的运算符和函数实现其实是新建了一个BigInteger对象,经过计算后再回传过来,所以对于BigInteger类的数学运算加上循环的,运算部分使用可变参数然后在结果上对BigInteger操作可以提高效率.

在字符串前面加@就使其成为了原生字节字符串.同样地,string也是immutable的,有一定的效率风险.
所以在运用中可以使用System.Text来代替string,里面有StringBuilder,可以在内部实现用char代替string,最后才转换为string.

在C#中,可以所有的内置类型都是强类型的,虽然可以使用var来用初始化的方式自动推断出数据类型,但是仍然不是动态变化的,真的动态变化的数据类型需要声明为surprise-dynamic.可以中间换数据类型的.使用var声明变量最有用的地方在LINQ,在LIINQ返回的对象中不是简单的数据类型的组合,而是特定的LINQ对象,此时很难确切定义某些数据结构,因此使用var会减少麻烦.

foreach仅仅是正向遍历集合中的所有值.
if/else仅仅接受boolean值而不接受转换值,如-1或者0.逻辑操作符都是短路的.如果需要全部检查请使用&和|
同样地,正确地使用break;防止贯穿.case中的判断项可以是数字和string,或者是枚举,其实也是数字?

函数的传递参数:
函数的传入值可以是按值传递也可以是按引用传递:并且有修饰类型.具体如下:

修饰字段 含义
(None) 按值传递
out 按引用传递,同时必须由被调用的函数,即这个函数本身内部赋值,如果赋值失败,则会报编译错误,单个使用out修饰参数没有很大的意义,因为返回的时候自动加上了该修饰符,但是当可以返回多个返回值的时候比较方便,不需要给出特殊的返回参数,仅仅给出void就可以.记住这里在传递函数参数时也必须加上out
ref 按引用传递,内部也可以再次赋值,而赋值失败也不会报错
params 可变参数标记,一个函数只能有一个,并且都是在最后出现的.base class中的大部分都用了

特殊地:有关out和ref的一点不同,out修饰的参数不必在传入前初始化,而ref应该这样做,前者在函数内必须初始化,而后者不必要,会有可能造成函数结束依然没有初始化而出错.

params必须在参数列表的最后,一般地应该定义为某个内置类型的数组,这样就既可以接受一个数组作为参数,或者是一列由逗号分割开的该类型的数做参数.

默认参数: 默认参数和python设计的一样,参数在等号后面直接加默认值,但是需要注意的是该值必须是在编译时有效的,而不是运行时有效的,如DataTime.Now,就会报错.

Named Parameters: 用特定的名字参数对调用函数,如foo(A:"Hello"),该中方法仅仅由参数名得到参数,因此不需要遵守参数的顺序,所以在有默认参数的情况下可以简单调用后面的参数,即仅给后面的值赋值.

数组:C#支持大括号初始化的方法.new甚至也是可选的.但是有new的时候如果数组的长度和{}内的初值个数不符合,那么还是会报错.同时也可以使用var.

多维数组:
C#的多维数组有两种方式,一种是真正的多维数组int[,],即在初始化的时候确定了各维的长度,然后就是固定的大小,而另一种被成为交错数组int[][],是其他语言中实现多维数组的方式,即是一维数组的多次嵌套,如二维数组就是一维数组中每一个元素都是一维数组,所以每个数组的元素都不定长.

枚举类型:
首先,枚举类型应该定义在类中或者是类之外,不能是在方法内定义,定义了就非法,所以枚举的本质还是在外部做一个能够引用值的池.
可以使用enum A : TypeName, 但是仅限于byte, int, short, long四种.enum中的值不能再被赋值了.
通过GetType()可以得到一个枚举的类型,从而得到整个枚举的信息.

Struct就是一个轻量版的class,所有的规定基本和class一样.System.ValueType就是保证这些类型的实例从stack分配内存而不是从垃圾回收堆开始.
所以类和结构很重要的区别就是类是从垃圾回收堆上分配的内存,而结构体是从栈上分配的内存,并且结构体都是值类型,而类都是引用类型.
当一个结构体包含一个类类型作为引用时,并没有改变类类型的引用类型的特点,所以对该结构进行copy两个结构体将包含同一个类类型的实例,是浅引用,仅仅是指针.如果是要全部复制,或是深copy则需要实现ICloneable接口.

如果对于一个函数,仅仅传值,那么能改变object的状态,但是不能改变object本身;
但是对于传递引用,不仅可以改变对象的状态,还可以改变指向的对象本身.

值类型和类类型的不同,详细见下表:

问题 值类型 类类型
对象是在哪里分配的 栈上 堆上
变量的表示方法 就是本地的局部变量 指向内存中的实例所开辟的空间
基类或者属性 隐式继承自System.ValueType 可以继承自其他基本任意的类型
是否能被继承 值类型永远不能被继承 可以
默认传递类型 只能传值 传值时传值,传引用的时候传递对象的引用
是否重写了垃圾回收的函数System.Object.Finalize() 不必,因为永远不会由堆来分配内存,因此不需要实现 需要,间接实现了该函数
能否定义构造函数 可以,但是默认的构造函数依然会被保留 当然
该类的变量何时会失效 当离开定义域 当被垃圾回收时

Nullable Type:
在原来数据类型的集合中加上一个null,在和数据库交互的时候很有用.该类型仅对值类型有效,对于类类型无效.用法就是在数据类型后面加上一个?,如:

int? nullableInt = 10;
Nullable<int> nullable = 10;

两者的含义相同.

??代表如果一个nullable对象的值是null就使用??后面的值.可以简化一个if判断是不是null.

int? data = data.GetFromDataBase() ?? 100;

如果没有??就是:

int? data = data.GetFromDataBase();
if (!data.HasValue){
	data = 100;
}

C# oop

C#的this参数跟C++的默认参数很像,先定义一个完整的构造函数,然后其他的更改其中的某些参数以成为不完整的构造参数.

public Motorcycle(string name)
: this(0, name) {}

// This is the 'master' constructor that does all the real work.
public Motorcycle(int intensity, string name)
{
	if (intensity > 10)
	{
		intensity = 10;
	}
	driverIntensity = intensity;
	driverName = name;
}

并且多个构造函数时,很奇怪,都是先传给主构造函数,运行了部分后再转给符合参数的构造函数.

在4.0以上可以使用默认参数代替多个构造函数的构造函数chaining.

OOP的三大特征:封装,继承,多态.
和C++不一样,所谓的抽象函数就是继承之间的接口的抽象函数,也类似钩子,仅在基类定义的时候使用,但是就是需要子类去覆盖取重写的,实现子类自己的功能.但是虚函数是在基类中有default的实现,但是可以被子类重写的.一个是必须重写,一个是可以重写.

可见性:
成员在域中的可见性,详见下表:

关键词 适用对象 解释
public 类型和类型成员 对外一切可见,包括对象中以及继承的类等等,对外部程序集都可见,在编写库函数的时候很有用
private 类型成员或者是嵌套类 仅能在类内部或者是结构内部可见
protected 类型成员或者是嵌套类 可以在内部及子类中可见,但是不能由外部的点运算符取到
internal 类型或者类型成员 仅在当前的程序集内可见,比Java的包高一级的可见性,基本上是在该库中可见的意思
protected internal 类型成员或者嵌套类 该程序集和定义类和继承该类的子类可见

默认的可见性:类型成员是private的,而类都是internal的.最外层的类类型不能被定义为private的.

C#和OC一样,都可以通过getter/setter或者属性的方式访问私有成员变量.
在成员变量上用Ctrl+r,Ctrl+e得到包装好的属性.属性中有自动生成的get,set方法,可以自己修改.所以如果想得到一个只读的属性,就把属性中的set函数省去;自然地,如果要求得到一个只写的属性值,则省去get函数.所以这里的属性和oc中的一样,本身并不代表一个真实的成员变量而是对应了成员变量的get/set方法而已.在只读的内部,仍有用原始的成员变量赋值的能力.

静态变量直接构建出来就是静态属性.

若想自动生成不改变的get/set方法,可以用自动属性,敲prop,tab两次就可以了.特别地,自动属性必须是可读可写的,忽略其中一个都是错误.自动属性已经给其中的参数赋了初值,一般的数值型是0,对象型是null.自动属性中不能有任何其他的检查值等等的操作,只有简单的get/set.

对象的大括号赋值:隐式调用构造函数,C++11才有的特性,给个例子:

Point a = new Point(10,20);	//normal init
Point b = new Point{x=10, y=20};   //invoke the default constructor
<-->
Point b = new Point(){x=10, y=20}

Point c = new Point(10,16){x=10, y=20}    //invoke the custom constructor

而且这样的方式在组合时也可以使用,嵌套的大括号赋初值.

const和其他语言一样,一旦赋值不可更改,并且默认是static的.readonly修饰词和const类似但是不一样的是,readonly稍弱一些,可以在声明时不赋值,但是在构造函数中赋值,但是在任何其他地方都不能再修改其值.另外和const不同,readonly并不是static的,如果需要将其变为类等级的必须显式声明为static.

partial class:由于C#不要求文件名和类名相同,因此可以将一个类分散在多个文件中,只要前面加上partial两部分保持类名不变.在编译的时候还是一样的.感觉真是一般不需要用啊.(貌似只有设计和逻辑分开的时候采用.)

继承:
首先C#也只支持单继承,没有多继承,只能实现多个接口;其次.使用sealed来标识一个类不能再被继承,和java中的final一样.所有的structure都是sealed,不能被继承.

有关继承的构造函数问题:首先,构造函数是不会继承的,public和protected标记的函数和属性,变量等等会被继承,而在定义子类的构造函数时,如果是显式低将父类的属性或者函数直接赋值可以实现,但是这样实现,仍然是先调父类的构造函数,然后后调子类的构造函数,以这种方式写会增加多次的属性赋值和额外的函数调用.所以一般采用直接将参数回传给父类的构造函数的方法:

public SalesPerson(string fullName, int age, int empID,float currPay, string ssn, int numbOfSales): base(fullName, age, empID, currPay, ssn)
{
	// This belongs with us!
	SalesNumber = numberOfSales;
}

即至少有一个子类的构造函数要显式地指向调用父类的构造函数,并传递参数.内部类是理想的处理方式,即只要前面声明的类型对,后面的构造函数一层一层拨开就可以了.

如果可以修改的父类函数,就给出一个virtual标识,只要在子类中使用override表明就可以修改该函数的实现.也可以在这个函数里调父类的函数,用base.函数名就可以了.override还可以和sealed一起使用,即该子类中的函数虽然override了父类的函数,但是却不能够再被该子类的子类override.

和其他语言一样,如果定义了基类而只打算让其成为其他类的父类,作为公共基类而不让它本身实例化,就用abstract修饰该类.但是值得注意的是abstract修饰类并不会让类内部的所有属性和函数变为abstract的,因此如果需要子类必须实现某些方法,还必须显式地将函数声明为abstract的.
而子类不实现父类的abstract函数,那么子类也必须声明为abstract.

Member Shadowing:当子类和父类的属性或者是方法名重复了,这时有两种解决方法,将父类声明为virtual,子类override,但是有时候第三方库,没有源码,所以也可以在子类的该方法前加上new,来表示和父类的方法或属性的不同.额外的一种方式是做强制的继承类型转换.

继承类型转换:隐式低子类朝父类转换永远是安全的.父类显式地朝下转型也不一定正确.

as和is: as可以在强制转型时使用,如果不是或者不能转就会返回null.as其实保证了是可以向下转的类型,而is则是判断是否就是要转的特定类型.

System.Object: override了Equal()就必须复写GetHashCode(),默认的Equal()实现是看内存中的位置是否相同.

原文地址:https://www.cnblogs.com/putuotingchan/p/8628709.html