来源:www.cncfan.com | 2006-8-30 | (有2217人读过)
引用类型是类型安全的指针,它们的内存是分配在堆(保存指针地址)上的。 String、数组、类、接口和委托都是引用类型。 强制类型转换与as类型转换的区别:当类型转换非法时,强制类型转换将抛出一个System.InvalidCastException异常, 而as不会抛出异常,它返回一个null值。 用using创建别名:using console = System.Console; 访问限定符: public 该成员可以被其他任何类访问 protected 该成员只能被其派生类访问 private 该成员只能被本类的其他成员访问 internal 该成员只能在当前编译单元的其他成员访问 带参数列表和返回值的Main方法: class Test { public static int Main(string[] args) { foreach (string arg in args) { ... } } } 构造函数(constructor)包括实例构造函数和静态构造函数。 构造函数与类名相同,且不能有返回值。例: class TestClass { TestClass() //实例构造函数:可以访问静态成员和实例成员,用于初始化实例成员 { ... }
static TestClass() //静态构造函数:只能访问静态成员,用于初始化静态成员 { ... } } 类的静态成员属于类所有,不必生成实例就可以访问,它是在载入包含类的应用程序时创建的, 但静态方法不能访问类的实例变量和方法。通常,静态变量是在定义时就赋初始值的。 类的实例成员属于类的实例所有,不创建实例对象就无法对其进行访问,实例成员可以访问类的 静态成员和其它实例成员。 调用基类的析构函数: class A { public A() { ... } } class B { public B(): base() //调用基类的析构函数 { ... } } 常量:其值是在编译时设定的,必须是数值文字。默认状态下常量是静态的。例: class A { public const double pi = 3.1415; } 常量是编译时就确定的值,只读字段是在运行才能确定的值。比如运行时才能确定的屏幕分辨率。 只读字段只能在类的析构函数中赋值。 静态只读字段: class A { public static readonly int ScreenWidth; //静态只读字段 static A() //静态析构函数 { ScreenWidth = 1024; //在静态析构函数中初始化 } } 在类的继承中,类的析构函数是不会被继承的。 一个派生类只能从一个基类继承,不能同时从多个基类继承,但可以通过继承多个接口来 达到相同目的。实现多继承的唯一方法就是使用接口。例: class MyFancyGrid: Control, ISerializable, IDataBound { ... } 密封类是不能继承的类,抽象类不能被定义为密封类,且密封类的私有成员不能用protected修饰, 只能用private。例: sealed class A { ... } 关键字ref和out用于指定用引用方式传递方法的参数。 它们的区别是:ref参数必须初始化,而out参数不需要初始化。所以在方法处理代码依赖参数的 初始化值时使用ref,不依赖初始化值时使用out。 对out参数即使在传递前对其进行了初始化,其值也不会传递到方法处理函数内部。传递时系统 会将其设为未初始化。所以在方法内部必须对out参数进行初始化。 方法重载时,必须参数数目和参数类型其中之一不同,返回值不同不能作为重载。 C#不支持方法的默认值,只能通过方法重载来实现。例: class A { int Method(int a) { ... }
void Method(int a, int b) //参数数目不同 { //返回值不同不能作为重载 ... } } params参数用于一个不定数目参数的方法,一般后面跟一个数组。例: class A { public void Method(params int[] i) { ... } } 方法的覆盖:指派生类覆盖基类的同名方法,有二种方法 1)第一种是在派生类要覆盖的方法前面加new修饰,而基类不需要作任何改动。 这种方法的缺点是不能实现多态。例: class A { public void Method() //无需任何修饰 { ... } }
class B: A //从基类继承 { new public void Method() //覆盖基类的同名方法 { ... } }
class TestClass { A Instance = new B(); Instance.Method(); //这时将调用类A的Method方法,而不是类B的Method方法 }
2)第二种是在派生类要覆盖的方法前面加override修饰,而基类的同名方法前面加virtual修饰。 这样就能实现多态,例:
class A { virtual public void Method() //基类定义虚方法 { //虚拟方法不能定义为private,因为private成员对派生类是无法访问的 ... } }
class B: A //从基类继承 { override public void Method() //派生类覆盖基类的同名虚方法 { ... } }
class TestClass { protected void Test() { A Instance = new B(); //定义一个实例,类型为基类,从派生类创建 //派生类总是能够向上转换为其基类 Instance.Method(); //将调用派生类B的Method方法,而不是基类的,这就是多态 } }
说明:new修饰的方法覆盖不能实现多态的原因,是因为使用new时编译器只会实现早期绑定(early binding)。 即调用的方法在编译时就决定了:编译器看到Instance.Method()而Instance的类是A,就会调用类A的Method()方法。 override修饰的方法覆盖可以实现多态的原因,是因为实现了后期绑定(late binding)。 使用override时强制编译器在运行时根据类的真正类型正确调用相应的方法,而不是在编译时。 而基类的同名方法必须加virtual修饰。 类的静态方法可能通过 类名.静态方法名 这种格式来调用,不能使用 实例名.静态方法名 这种方法调用。 因为类的静态方法为类所有(是属于类本身的),而非实例所有(不是属于类的实例的)。 类的静态方法可以访问类的任何静态成员,但不能访问类的实例成员。 C#中类的变量称为字段。类的public变量称为类的公共字段。 类的属性由一个protected(也可以是private)字段和getter和setter方法构成: class Address { protected string zipCode; //protected字段,注意大小写 public string ZipCode { get //getter方法 { return zipCode; } set //setter方法 { zipCode = value; //被传递的值自动被在这个value变量中 } }; } 只读属性是指省略setter方法的属性,只读属性只能读取,不能设置。 属性也可以用限定符virtual,override和abstract修饰,功能同其他类的方法。 属性有一个用处称为懒惰的初始化(lazy initialization)。即在需要类成员时才对它们进行 初始化。如果类中包含了很少被引用的成员,而这些成员的初始化又会花费大量的时候和系统 资源的话,懒惰的初始化就很有用了。 C#中数组对象共同的基类是System.Array。 将数组声明为类的一个成员时,声明数组与实例化数组必须分开,这是因为只能在运行时创建了 类的实例对象之后,才能实例化数组元素值。 声明: int[] intArray; //一维数组 int[,,] int3Array; //三维数组 初始化: intArray = new int[3] {1,2,3}; int[,] int2Array = new int[2,3] {{1,2,3},{4,5,6}}; //声明时可以初始化 遍历: 1)一维数组 for (int i = 0; i < intArray.Length; i++); //Array.Length返回数组所有元素的个数 foreach (int i in intArray); for (int i = 0; i < intArray.GetLength(0); i++);//Array.GetLength(0)返回数组第一维的个数 2)多维数组 for (int i = 0; i < int3Array.GetLength(0); i++) //遍历三维数组 for (int j = 0; j < int3Array.GetLength(1); j++) for (int k = 0; k < int3Array.GetLength(2); k++) { ... } 数组的维数就是该数组的秩(Rank)。Array.Rank可以返回数据的秩。 锯齿数组(jagged Array)是元素为数组的数组,例: int[][] jaggedArray = new int[2][]; //包含二个元素,每个元素是个数组 jaggedArray[0] = new int[2]; //每个元素必须初始化 jaggedArray[1] = new int[3]; for (int i = 0; i < jaggedArray.Length; i++) //遍历锯齿数组 for (int j = 0; j < jaggedArray[i].Length; j++) { ... } 类的属性称为智能字段,类的索引器称为智能数组。由于类本身作数组使用,所以用 this作索引器的名称,索引器有索引参数值。例: using System; using System.Collections; class MyListBox { protected ArrayList data = new ArrayList(); public object this[int idx] //this作索引器名称,idx是索引参数 { get { if (idx > -1 && idx < data.Count) { return data[idx]; } else { return null; } } set { if (idx > -1 && idx < data.Count) { data[idx] = value; } else if (idx = data.Count) { data.Add(value); } else { //抛出一个异常 } } } } 接口是二段不同代码之间约定,通过约定实现彼此之间的相互访问。 C#并不支持多继承,但通过接口可实现相同功能。 当在接口中指定了实现这个接口的类时,我们就称这个类“实现了该接口”或“从接口继承”。 一个接口基本上就是一个抽象类,这个抽象类中除了声明C#类的其他成员类型——例如属性、 事件和索引器之外,只声明了纯虚拟方法。 接口中可以包含方法、属性、索引器和事件——其中任何一种都不是在接口自身中来实现的。例: interface IExampleInterface { //property declaration int testProperty { get; }
//event declaration event testEvevnt Changed;
//mothed declaration function void testMothed();
//indexer declaration string this[int index] { get; set; } } 说明:定义接口时,在方法、属性、事件和索引器所有这些接口成员都不能用public之类的访问限定符, 因为所有接口成员都是public类型的。 因为接口定义了一个约定,任何实现一个接口的类都必须定义那个接口中每一个成员,否则将编译失败。例: using System; public class FancyControl { protected string data; public string Data { get {return this.data;} set {data = value;} } }
interface IValidate { bool Validate(); //接口方法 }
public class MyControl: FancyControl, IValidate { public MyControl() { data = "my control data"; }
public bool Validate() //实现接口 { if (data == "my control data") return true; else return false; } } class InterfaceApp { MyControl myControl = new MyControl(); IValidate val = (IValidate)myControl; //可以将一个实现某接口的类,转换成该接口 bool success = val.Validate(); //然后可调用该接口的方法 } 也可以用: bool success = myControl.Validate(); 这种方法来调用Validate方法,因为Validate在类MyControl中是被定义成public的,如果去除public,Validate方法被隐藏, 就不能用这种方法调用了,这样隐藏接口方法称为名字隐藏(name hiding)。 可以用:类实例 is 接口名 来判断某个类是否实现了某接口,例: myControl is IValidate //MyControl类的实例myControl是否实现了IValidate接口 当然,也可用as来作转换,根据转换结果是否为null来判断某个类是否实现了某接口,例: IValidate val = myControl as IValidate; if (null == val) { ... //没有实现IValidate接口 } else { ... //实现了IValidate接口 }
如果一个类从多个接口继承,而这些接口中如果定义的同名的方法,则实现接口的方法时,必须加接口名来区别, 写成 接口名.方法名。假设Test类从IDataStore和ISerializable二个接口继承,而这二个接口都有SaveData()方法, 实现SaveData()方法时必须写成: class Test: ISerializable, IDataStore { void ISerializable.SaveData() { ... }
void IDataStore.SaveData() { ... } }
如果一个类从多个接口继承,为了方便可以定义一个新的接口,这个接口继续多个接口,然后类直接从这个接口继承就 可以了,这个叫合并接口。例: interface ISaveData: ISerializable, IDataStore { //不需要定义任何方法或成员,只是用作合并 } class Test: ISaveData //只要继承ISaveData就可以了 { ... } 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对象。例: Type t1 = typeof(Apple); //Apple是一个类名 Apple apple = new Apple(); //apple是Apple类的一个实例 Type t2 = apple.GetType(); //t1与t2是相同的 通过反射得到一个类的所有成员和方法: Type t = typeof(Apple); string className = t.ToString(); //得到类名 MethodInfo[] methods = t.GetMethods(); //得到所有方法 foreach (MethodInfo method in methods) { //用method.ToString()得到方法名 } MemberInfo[] members = t.GetMembers(); //得到所有成员 foreach (MemberInfo member in members) { //用member.ToString()得到成员名 } sizeof()操作符用来计算值类型变量在内存中占用的字节数(Bytes),并且它只能在unsafe(非安全) 代码中使用。例: static unsafe public void ShowSizes() { int i, j; j = sizeof(short); j = sizeof(i); } 尽可能使用复合赋值操作符,它比不用复合赋值操作符的效率高。 for语句的语法为: for (initialization; Boolean-expression; step) embedded-statement 在initialization和step部份还可以使用逗号操作符,例: for (int i = '0', j = 1; i <= '\xFF'; i++, j++) for (int i = 1, j = 1; i < 1000; i += j, j = i - j) //输出斐波那契数列 Console.Write("{0} ", i); 在switch语句中执行一个分支的代码后还想执行另一个分支的代码,可以用: goto case 分支; 操作符重载是为了让程序更加自然,容易理解。想要为一个类重新定义一个操作符,使用以下语法: public static 返回值 operator 操作符 (操作对象1[,操作对象2]) 说明: 1)所有重载的操作符方法都必须定义为public和static 2)从技术上说返回值可以是任何类型,但通常是返回所定义方法使用的类型 3)操作对象的数目取决于重载是一元操作符还是二元操作符,一元操作符只要一个操作对象,二元操作符则需要二个。 4)不管重载是一元操作符还是二元操作符,第一个操作对象的类型都必须与返回值的类型一致;而对于二元操作符的第二个 操作对象的类型则可以是任何类型。 5)只有下列操作符可以被重载: 一元:+ - ! ~ ++ -- true false 二元:+ - * / % & | ^ << >> == != > < >= <= 赋值操作符(+=,-=,*-,/=,%=等等)无法被重载。 []和()操作符也无法被重载。 6)操作符的优先级是无法改变的,运算优先级的规则是静态的。
|