分享错误,学习生活

c#入门经典 C# 入门经典

1 C#简介

.NET Framework是Microsoft最新的开发平台,目前版本是4。它包括一个公共类型系统(CTS)和一个公共语言运行时(CLR)。

用.NET Framework编写的应用程序首先编译为CIL(以前叫MSIL)。在执行应用程序时,JIT把CIL编译为本机代码。应用程序编译后,把不同的部分链接到包含CIL的程序集中。

3 变量和表达式

以#开头的任意关键字实际上都是一个预处理指令,严格地说并不是C#关键字。

简单类型:

整型:sbyte, byte, short, ushort, int, uint, long, ulong。分别占用1,1,2,2,4,4,8,8字节。

浮点数:float, double, decimal。分别占用4,8,?字节。

还有3中简单类型:bool, string, char。char保存unicode字符,所以占用2个字节。

字符串是引用类型,而其他简单类型都是值类型。所以字符串也可以被赋予null值,即字符串变量不引用字符串。

默认情况下,C#代码包含在全局名称空间中。

变量只有经过声明和初始化后,才能使用

4 流程控制

C#与C++是有区别的,在C++中可以运行完一个case语句后再运行另一个case语句。C#中是不行的。一个case语句处理完后,不能自由进入下一个case语句,但这个规则有一个例外。如果把多个case语句放在一起,其后加一个代码块,实际上市一次检查多个条件。如果满足这些条件中的任何一个,就会执行代码。

5 变量的更多内容

隐式转换:从类型A到类型B的转换可以在所有的情况下进行,执行转换的规则非常简单,可以让编译器执行转换。任何类型A,只要其取值范围完全包含在类型B的取值范围内,就可以隐式转换为B。

显式转换:从类型A到类型B的转换只能在某些情况下进行,转换的规则比较复杂,应进行某种类型的处理。

checked和unchecked称为表达式的溢出检查上下文。

使用数据类型转换语法执行显式转换,其运算符优先级与其他一元运算符一样,都是优先级中最高级,如前缀的++。

枚举使用一个基本类型来存储。枚举类型可以提取的每个值都为该基本类型的一个值,默认情况下该类型为int,在枚举声明中添加类型,就可以指定其他基本类型。方法是在枚举声明时,在枚举类型名称后面加冒号,在冒号后面指定基本类型。枚举指定的基本类型只能用简单整型变量。

定义enum,struct,class时,右大括号不需要加分号。

普通数组定义:

  1. int[] A = {1,2};
  2. int[] A = new int[2];
  3. int[] A = new int[2]{1,2};
  4. int[] A = new int[variable];

foreach循环只能对数组内容进行只读访问,不能改变任何元素的值。

多维数组的定义:

  1. int[,] A = new int[2,2];
  2. int[,] A = {{1,2},{2,3}};

多维数组可以用foreach来访问。

数组的数组的定义:

  1. int [][] A = new int[2][]{new int[]{1,2,3}, new int[]{3,4}};
  2. int [][] A = new int[2][];
  3. A[0] = new int[3]{1,2,3};
  4. A[1] = new int[2]{3,4};

数组的数组不能直接用foreash访问,必须用两个或更多的foreach访问。

8 面向对象编程简介

静态构造函数:使用类中的静态成员时,需要预先初始化这些成员。在声明时,可以给静态成员提供一个初始值,但有时需要执行更复杂的初始化,或者在赋值,执行静态方法之前执行某些操作。使用静态构造函数可以执行此类初始化任务。一个类只能有一个静态构造函数,该构造函数不能有访问修饰符,也不能带任何参数。静态构造函数不会被继承。如果没有写静态构造函数,而类中包含带有初始值设定的静态成员,那么编译器会自动生成默认的静态构造函数。静态构造函数不能直接调用,只能在下书情况下执行:

1 创建包含静态构造函数的类的实例时 2 访问包含静态构造函数的类的静态成员时

这两种情况下,会先调用静态构造函数,之后实例化类或访问静态成员。无论创建多少个类的实例,其静态构造函数都只调用一次。

只包含静态成员,不能用于实例化对象的类叫静态类。静态类只能包含静态成员,不需要实例构造函数,但可以有一个静态构造函数。

支持IDisposable接口的对象必须实现其Dispose()方法,即它们必须提供这个方法的代码。当不再需要某个对象时,就调用这个方法,释放重要的资源,否则该资源会等到垃圾回收时才释放。

用using关键字实现上述行为。

  1. using(classname variable = new classname())
  2. {
  3. }//出了这个大括号会自动调用Dispose()

虚拟成员不能是私有成员。

在C#中,所有的类都派生自同一个类object。

尽管不能像对象那样实例化接口,但可以建立接口类型的变量,然后就可以在支持该接口的对象上使用这个变量访问该接口提供的方法和属性。(不能实例化一个接口,所以对接口不存在用new来实例一个接口对象!!但可以定义接口的变量,然后给它赋值,赋值是可以的,赋值是使用了多态。)

值类型在内存的一个地方存储它们自己它们的内容,引用类型存储指向内存中其他某个位置(堆)的引用,而在另外一个位置存储内容。值类型和引用类型的一个主要区别是,值类型总是包含一个值,而引用类型可以是null,表示他们不包含值。但是可以使用可空类型创建一个值类型。

结构类型和类的重要区别是,结构类型是值类型,类是引用类型。

9 定义类

默认情况下,类声明为内部的,即只有当前项目中的代码才能访问它。可以用internal访问修饰符关键字显式指定。internal的对立面是public,可以由其他项目中的代码来访问。C#的类定义中,只能有一个基类,如果继承了一个抽象类,就必须实现所继承的所有抽象成员(除非派生类也是抽象的)。编译器不允许派生类的可访问性高于基类。也就是说内部类可以继承自一个公共基类,但公共类不能继承自一个内部类。

所有接口成员都必须在支持该接口的类中实现。

类的修饰符包括:internal,public,abstract,sealed。前两个互斥,后两个互斥。

接口的修饰符只有internal和public。没有abstract和sealed。不能用实例化类的方式来实例化接口。

在利用多态性时,object.GetType()是一个有用的方法,允许根据对象的类型执行不同的操作,而不是像通常那样,对所有的对象都执行相同的操作。例如一个函数接受一个object类型的参数,就可以在遇到某些对象时执行额外的任务。联合使用GetType()和typeof()就可以进行比较。比如:

  1. if(myObj.GetType() == typeof(MyClass))
  2. {
  3. ...
  4. }

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

插入一个关于C++默认构造函数的知识点。

在栈上声明一个类对象时,不能加括号,如果加了括号,那就是声明一个函数原型而已。在堆上声明类对象时,括号可加可不加。

  1. int main()
  2. {
  3. //在栈上
  4. ClassName A; //OK
  5. ClassName A();//OK,但这里不是声明类的对象,而是声明一个函数原型
  6. //在堆上
  7. ClassName A = new ClassName; //OK
  8. ClassName A = new ClassName(); //OK
  9. }

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

无论在派生类上使用什么构造函数(默认构造函数或非默认构造函数),除非明确指定,否则就使用基类的默认构造函数。(这个跟第4版的《C#入门经典》说法不一样)

base关键字指定.NET实例化过程中使用基类中指定参数的构造函数。(C++中在初始化区域调用基类的构造函数时,是直接使用基类的名字,而不是用base。因为C++可以同时从多个基类派生,而C#只能从一个基类派生。)

还可以用this关键字调用本类的其他构造函数。

如果没有给构造函数指定构造函数初始化器,编译器会自动添加base()。

抽象类和接口都包含可以由派生类继承的成员。接口和抽象类都不能直接实例化,但可以声明这些类型的变量。如果声明这两种类型的变量,就可以使用多态性把继承这两种类型的对象指定给它们的变量,接着通过这些变量来使用这些类型的成员。它们的区别在于,抽象类可以拥有抽象成员和非抽象成员。接口成员必须都再使用接口的类上实现。另外,接口成员必须都是公共的,抽象类的成员可以是private, protected,internal的。此外,接口不能包含字段,构造函数,析构函数,静态成员和常量

System.Object的MemberwiseClone()提供浅复制。要实现深度复制,可以实现一个ICloneable接口,以标准的方式来进行

10 定义类成员

字段也可以使用关键字readonly修饰,表示这个字段只能在执行构造函数中赋值,或由初始化赋值语句赋值。字段可以使用static关键字声明为静态

可以用来修饰类方法的有:virtual-方法可以重写, abstract-抽象方法,必须在非抽象派生类中重写, override-表明该方法是重写的, extern-方法定义在其他地方,sealed-指定派生类中不能对这个方法作进一步的修改。

基类中用virtual定义一个虚函数,非抽象派生类要用override修饰说明是在重写虚函数。

属性可以使用virtual, override, abstract关键字修饰,但这几个关键字不能用于字段。

自动属性声明如下:

  1. public int MyIntProp
  2. {
  3. get;
  4. set;
  5. }

自动属性的唯一限制是它们必须同时包含get和set存取器,无法使用这种方式定义只读或只写属性。

如果确实要隐藏一个成员,可以使用new关键字显式表明意图。

无论是重写成员(virtual和override)还是隐藏成员(new),都可以在派生类的内部访问基类成员。可以使用base关键字来实现上述行为。this也可以用在类成员内部,且该关键字也引用对象实例。只是this引用的是当前的对象实例。this关键字最常用的功能是把当前对象实例的引用传递给一个方法。(这点跟C++的this有点相似)this关键字的另一个常见用法是限定本地类型的成员。这样用可以一目了然地看出引用的是类成员,而不是局部变量。

所有的接口成员都是公共的接口成员不能包含代码体,不能定义字段,类型定义是禁止的,不能用static,virtual,abstract,sealed修饰。

要隐藏基接口,可以用关键字new来定义它们。跟类隐藏方法一样。接口可以定义只读或只写属性。

实现接口的类必须宝航该接口所有成员的实现代码,并且必须是公共的。可以用virtual或abstract来实现接口成员,但不能使用static或const。可以在基类上实现接口。继承一个实现给定接口的基类,就意味着派生类隐式地支持这个接口。

显式实现接口成员:如果显式实现接口成员,该成员就只能通过接口来访问,不能通过类来访问。

隐式实现接口成员:如果隐式实现接口成员,该成员即可以通过接口变量访问,也可以通过类访问。

  1. public class MyClass : IMyInterface
  2. {
  3. public void IMyInterface.DoSomething() //显式实现接口
  4. {
  5. }
  6. public void DoSomethingElse() //隐式实现接口
  7. {
  8. }
  9. }

部分类定义,就是把类的定义放在多个文件中。为此,只需要在每个包含部分类定义的文件中对类使用partial关键字即可。如

  1. public partial class MyClass
  2. {
  3. }

如果使用部分类定义,partial关键字就必须出现在包含定义部分的每个文件的与此相同的位置。
部分类定义可以在一个部分类定义文件或者多个部分类定义文件中包含基类。但如果基类在多个定义文件中指定,它就必须是同一个基类。因为C#中类只能继承一个基类。

11 集合,比较和转换

C#中的数组实现为System.Array类的实例,它们只是集合类中的一种。

在类中添加索引符:

  1. public class Animals : CollectionBase
  2. {
  3. public Animal this[int animalIndex]
  4. {
  5. get
  6. {
  7. return (Animal)List[animalIndex];
  8. }
  9. set
  10. {
  11. List[animalIndex] = value;
  12. }
  13.  }
  14. }

迭代器:IEnumerable接口负责使用foreach循环。IEnumerable接口有个GetEnumerator方法,返回一个Enumerator的引用。该Enumerator必须实现Current,MoveNext()和Reset()。
迭代器的定义是,它是一个代码块,按顺序提供了要在foreach循环中使用的所有值。一般情况下,这个代码块是一个方法,但也可以使用属性访问器和其他代码作为迭代器。

如果要迭代一个类,可使用方法GetEnumerable(),其返回类型是IEnumerator。

如果要迭代一个类成员,比如一个方法,就要返回IEnumerable。

实例代码:

  1. public static IEnmuerable SimpleList() //迭代一个方法,返回IEnumerable
  2. {
  3. yield return ;
  4. yield return ;
  5. }
  6. public static int Main(string[] args)
  7. {
  8. foreach(string str in SimpleList())
  9. {
  10. ....
  11. }
  12. }
  1. public class MyClass //迭代类的对象
  2. {
  3. public IEnumerator GetEnumerator() //这里不能是static
  4. {
  5. yield return ;
  6. yield return ;
  7. }
  8. }
  9. public static int Main(string[] args)
  10. {
  11. MyClass mc;
  12. foreach(string str in mc)
  13. {
  14. ...
  15. }
  16. }

要实现深复制,应该继承ICloneable接口,实现Clone()方法。
封箱(boxing)是把值类型转换为System.Object类型,或者转换为由值类型实现的接口类型。拆箱(unboxing)是相反的转换过程。以封箱值类型变量而创建的对象,包含值类型变量的一个副本的引用,而不包含源值类型变量的引用。

封箱非常有用,首先它允许在项的类型是object的集合中使用值类型。其次,有一个内部机制允许在值类型上调用object。需要注意的是,在访问值类型内容前,必须进行拆箱。
is运算符并不是说明对象是某种类型的一种方式,而是可以检查对象是否是给定类型,或者是否可以转换为给定类型,如果是,这个运算符就返回true。

应注意不要把签名相同的运算符添加到多个类中。

不能重载赋值运算符(=,+=等)。这点和C++不同,C++允许重载赋值操作符。也不能重载&&和||。C++中可以重载,但不应该重载这两个运算符,因为重载后的行为和内置类型的行为不同。

一些运算符要成对重载,如<和>。

IComparable在要比较的对象的类中实现,可以比较该对象和另一个对象。IComparable提供一个方法CompareTo(),这个方法接受一个对象。

IComparer在一个单独的类中实现,可以比较任意两个对象。ICompare提供一个方法Compare(),这个方法接受两个对象,返回一个整型结果。
ArrayList类本身有Sort()函数可以对集合内的对象进行排序,前提是集合成员类必须实现IComparable接口。

可以重载类型转换操作符。语法如下:

  1. class c1
  2. {
  3. public static implicit operator c2(c1 c)//这是c2转为c1的隐式转化
  4. {
  5. ....返回一个c2对象
  6. }
  7. }
  1. class c2
  2. {
  3. public static explicit operator c1(c2 c)//这是c1转为c2的显式转化
  4. {
  5. ....返回一个c1对象
  6. }
  7. }
  1. public static int Main(string[] args)
  2. {
  3. c1 c = new c1();
  4. c2 cc = c; //ok, c1到c2的隐式转换
  5. c1 ccc = (c2)cc; //ok, c2到c1的显式转换
  6. }

类型转换的语法与C++的差别:C++中不能指定形参。C++中类型转换操作符必须声明为类的成员函数。
as运算符的作用跟C++下面的dynamic_cast类似。如果转换失败,as运算符会返回null。

operand is type:

上述表达式结果如下:

1 如果type是一个类类型,operand也是该类型,或继承了该类型,或可以封装到该类型,结果为true。

2 如果type是一个接口类型,operand也是该类型,或是实现了该接口的类型,结果为true。

3 如果type是一个值类型,operand也是该类型,或者它可以拆箱到该类型,结果为true。

operand as type:

只适用于:1 operand是type类型   2 operand可以隐式转换为type类型   3 operand可以封箱到type类型中。

在后台,.NET运行库允许在需要时动态生成泛型类。

12 泛型
值类型包含一个值,他们可以在声明之后,赋值之前,在未赋值的状态下存在,但不能以任何方式使用。而引用类型可以是null。
??运算符称为空接合运算符,是一个二元运算符。作用跟三元运算符?:一样。它返回第一个操作数的值,如果第一个操作数是null,就返回第二个操作数的值。
System.Collections.Generic名称空间包含用于处理集合的泛型类型。
泛型类,泛型接口,泛型方法,泛型委托。

要创建泛型类,只需要在类定义中包括尖括号:

  1. class MyGenericClass<T1,T2,T3>
  2. {
  3. ...
  4. }

在类模板中,应为不知道T1是引用类型还是值类型,在给T1变量赋值时会出问题。比如给引用类型可以初始化为null,而对于值类型则不行。为解决此问题,可以用default关键字。

T1 t = default(T1);

用where关键字来约束泛型类型参数的可用范围。where必须出现在继承说明符后面,即基类和接口后面。

可以有多个where。一个where可以同时定义多个约束,中间用逗号分开。

  1. class MyGenericClass<T> where T : constraint1, constraint2
  2. {
  3. }
  4. class MyGenericClass<T1,T2> where T1: constraint1 where T2 : constraint2
  5. {
  6. }

struct:类型必须是值类型

class:类型必须是引用类型

base-class:类型必须是base-class或继承自base-class。

interface:类型必须是接口或实现了该接口。

new():类型必须有一个公共的无参数构造函数。 这个约束必须是最后一个约束

结构实际上与类相同,区别是结构是值类型,不是引用类型。可以用与泛型类相同的方式来创建泛型结构。

泛型接口:

定义泛型接口与定义泛型类所用的技术相同。

泛型方法:

在泛型方法中,返回类型和形参由泛型类型参数来确定。在非泛型类中也可以实现泛型方法。泛型方法可以重载,可以根据泛型参数上的区别来重载泛型方法。使用哪个方法取决于调用方法时指定的泛型类型参数的个数。

泛型委托:

定义泛型委托的方法与定义泛型类的方法相同。

变体是协变和抗变的统称。抗变和协变是类似的,但方向相反。协变把泛型接口值放在使用基类类型的变量中,抗变把泛型接口值放在使用派生类类型的变量中。抗变看起来比较奇怪,因为不能通过多态性完成相同的功能。

要把泛型类型参数定义为协变,可以在类型定义中使用out关键字。

  1. public interface IMethaneProducer<out T>
  2. {
  3. }

对于接口定义,协变类型参数只能用作方法的返回值或属性get访问器。(这点很无语)

要把泛型类型参数定义为抗变,可以在类型定义中使用in关键字。

  1. public interface IMethaneProducer<in T>
  2. {
  3. }

对于接口定义,抗变类型参数只能用作方法参数,不能用作返回类型

In general, a covariant type parameter can be used as the return type of a delegate, and contravariant type parameters can be used as parameter types. For an interface, covariant
type parameters can be used as the return types of the interface's methods, and contravariant type parameters can be used as the parameter types of the interface's methods.

13 其他OOP技术

global是顶级根名称空间的别名。

订阅一个事件的含义是提供代码,在事件发生时执行这些代码,这些代码称为事件处理程序。对事件处理程序的唯一限制是,它必须匹配于事件所要求的返回类型和参数。这个限制是事件定义的一部分,由一个委托指定。

在定义一个事件之前,必须先定义一个委托类型,以用于该事件,这个委托类型指定了事件处理方法必须拥有的返回类型和参数。

  1. <code class=>public delegate void MessageHandler(string messageText);  //首先定义一个委托类型,事件处理程序的返回类型和参数必须和这个委托一致。  
  2. public event MessageHandler MessageEvent;                 //用这个委托来定义一个事件。  
  3. public static int Main(string[] args)  
  4. {  
  5.   MessageEvent += new MessageHandler(...);                //订阅该事件  
  6.   if(MessageEvent != null)                                //判断是否有处理程序订阅了该事件  
  7.     MessageEvent();                                //引发事件  
  8. }</code>  
  1. public delegate void MessageHandler(string messageText); //首先定义一个委托类型,事件处理程序的返回类型和参数必须和这个委托一致。
  2. public event MessageHandler MessageEvent; //用这个委托来定义一个事件。
  3. public static int Main(string[] args)
  4. {
  5. MessageEvent += new MessageHandler(...); //订阅该事件
  6. if(MessageEvent != null) //判断是否有处理程序订阅了该事件
  7. MessageEvent(); //引发事件
  8. }


可以用同一个事件处理程序来订阅不同对象引发的几个相同的事件。

.NET提供了两个委托类型:EventHandler和EventHandler<T>,以便于定义事件。它们都是委托,使用标准的事件处理模式。这样就不需要每次定义事件之前都要事先定义一个委托。当然,前提是你要定义的委托的签名正好跟EventHandler和EventHandler<T>一样。

  1. public delegate void EventHandler(Object sender,EventArgs e);
  2. public delegate void EventHandler<TEventArgs>(Object sender,TEventArgs e)where TEventArgs : EventArgs;

如果事件的委托类型有返回值,那么引发事件的语句将返回最后一个订阅该事件的处理程序的返回值。就跟逗号操作符一样。最好是使用void类型的事件处理程序。

匿名方法:

如果不想兴师动众地专门写一个事件处理程序,可以使用匿名方法。语法如下:

  1. public delegate void MessageHandler(string messageText); //首先定义一个委托类型,事件处理程序的返回类型和参数必须和这个委托一致。
  2. public event MessageHandler MessageEvent; //用这个委托来定义一个事件。
  3. public static int Main(string[] args)
  4. {
  5. MessageEvent +=
  6. delegate(arguments){ ...}; //匿名方法
  7.  }


14 C#语言的改进

初始化器:

  1. public class Curry
  2. {
  3. public int A{get;set;};
  4. public int B{get;set;};
  5. }
  6. public static int Main(string[] args)
  7. {
  8. Curry c = new Curry //这里没有小括号,虽然它仍然会先调用Curry的默认构造函数
  9. {
  10. A = 2;
  11. B = 2;
  12. };
  13. }

类型推理:

var v = 3;

让编译器来推理v的类型。使用var时,并不是声明一个没有类型的变量,也不是声明一个类型可以变化的变量。我们只是让编译器来确定变量的类型。如果编译器无法推导出变量的类型,编译就不会通过。因此在用var声明变量时,必须同时初始化该变量。

var并不是C#保留字,所以如果var被使用了,类型推理就无效了。

匿名类型:

高级方法参数:可选参数(跟C++中为参数提供默认参数是一样的东西),命名参数

可选参数的默认值必须是字面值,常量值,新对象实例或默认值类型值,不能是变量。可选参数必须位于方法的参数列表末尾。

扩展方法:

扩展方法可以扩展类型的功能,但无需修改类型本身。甚至可以使用扩展方法扩展不能修改的类型。

Lamdba表达式分为3个部分:

1 参数定义部分。参数可以指定具体的类型(显式),也可以不指定(隐式),但不能混合。

2 =>运算符。它把Lambda表达式的参数与表达式体分开。

3 表达式体。表达式体可以包含多个语句,如果包含多个语句,要用大括号把他们括起来。如果只有一个语句则可以不用大括号。

22 文件系统数据





                

暂无评论

发表评论

电子邮件地址不会被公开。