关闭

当优美的C#与Unity3D结合时开发游戏变的超Esey(一)必须掌握的C#100个基本点。

标签: Unity
854人阅读 评论(0) 收藏 举报
分类:

Unity3D提供支持三种脚本语言,但是Jscript并非一我们所熟知的那个Jscript,有着很大的不同,所以建议使用优美的C#语言来写脚本,下面是必须掌握的100个C#基本点。

1.C#中使用//(双斜杠)来表明本行的剩余部分代表注释。

2.C#中语句是按顺序执行的,每条语句以分号结尾。

3.C#中的函数执行一系列语句的行为,称为语句块---一对大括号中包含0条或多条语句。

4.一个函数可以通过定义参数来从调用者处接受输入数据,也可以通过定义返回类型来输出数据给调用者。

5.Main函数定义为执行程序的默认入口点。Main函数可以不返回任何值,也可以返回一个整数给执行程序环境。Main函数也可以不定义任何参数,或者定义一个string数组作为参数(该参数将由传递给执行程序的参数填充)。

6.Using指令用于使用命名空间。

7.C#中的标识符以字母或下划线开头,并且大小写敏感。

8.关键字是由编译器保留的名字,不能用作标识符,下面是C#的关键字列表:

9.如果想将关键字作为标识符,那么可以加上前缀@以使它合法,例如:class @class。注意@符号并不构成标识符的一部分,所以@myVariable和myVariable是相同的。

10.一些关键字是上下文相关的,它们不用使用符号@就能够被用作标识符。这些关键字如下:

11.C#中所有的值都是一个特定类型的实例,一个值的含义,以及一个变量可以拥有的所有可能的值,是由它的类型决定的。

12.预定义类型(也叫内建类型)是被编译器特别支持的类型,如int、float、bool等。

13.自定义类型可以包含成员数据和成员函数。需要使用new运算符来创建一个新的自定义类型的实例,当new运算符实例化一个对象后,该对象的构造函数将被调用。构造函数的定义类似于普通函数定义,除了函数名必须和类名相同以及去除了返回类型。

14.在类型的实例上操作的数据成员和函数成员称为实例成员。而不在类型的实例上,但在类型本身上操作的数据成员和函数成员,必须被标记为static。

15.public关键字用于显露成员给其他类使用。

16.C#能够在两个相兼容的类型之间进行转换,转换总是根据一个已存在的值而创建一个新值。转换可以是隐式的或者显式的,隐式的转换自动发生,如int x = 5; long y = x。而显式的转换需要一个映射,如int x = 5; short y = (short)x。一般来说,当编译器能够保证在两个类型之间转换总是成功的,且不丢失任何信息,那么就允许隐式转换,否则的话,就必须使用显式转换。

17.C#中的类型可以分为值类型和引用类型。值类型包括了大多数内置类型(所有的数值类型(int, float等),char类型,bool类型以及struct与enum类型),引用类型包括了所有的class,array,delegate与interface类型。

18.一个值类型的变量或常量包含的内容仅仅是一个值,对一个值类型的实例进行赋值总是会拷贝实际数据到该实例。引用类型的变量或常量包含的内容是对一个拥有实际数据的对象的引用,对一个引用类型的实例进行赋值总是会拷贝引用,而不会拷贝拥有实际数据的对象实例,这就允许多个变量引用同一个对象。

19.一个引用类型的实例可以被设置为null,以表明没有引用任何对象,使用一个为null的实例的成员将产生一个运行时错误。相比之下,一个值类型的实例不能被设置为null。

20.C#中的预定义类型分类:

21.C#预定义的数字类型如下:

22.整型可以使用10进制或16进制标记法,16进制标记法需要加0x前缀,如0x7F。实数可以用10进制或指数标记法,如1e06。

23.浮点数转换为整数将丢弃小数部分,如果想要进行四舍五入的转换,则可以使用System.Convert类。

24.需要注意从1个很大的整数转换为浮点数时可能会丢失精度,如int x = 100000001; float f = x; // 此时f = 100000000。

25.算数运算符(+,-,*,/,%)可以用于所有的数值类型(除了8位和16位的整数类型)。

26.自增,自减运算符用于使数值类型的变量加1或减1。运算符可以放在变量前(++x)或者变量后(x++),取决于想在表达式计算之前还是之后更新变量的值。

27.整数除法总是会丢弃余数。如果除以一个值为0的变量将产生一个运行时错误(DivideByZeroException),除以字面值0将产生编译错误。

28.在运行时,整数类型进行算术运算可能会造成溢出,默认情况下,不会有异常被抛出。C#文档表明对于溢出结果是不可预料的,但是Common Language Runtime(CLR)总是产生一个wraparound行为,也就是说,将最小的int值减1的结果是最大的int值,如int x = int.MinValue; x--; // 此时x = int.MaxValue。

29.checked运算符通知运行时当溢出时抛出一个OverflowException异常,checked运算符可以用于++, --, -(一元), +, -, *, /以及整数类型之间的显示转换。checked运算符可以用于一个表达式或者一个语句块,如:

复制代码
1 int a = 1000000;
2 int b = 1000000;
3 int c = checked( a * b );   // 用于表达式
4 
5 checked
6 {
7     c = a * b;  // 用于语句块
8 }
复制代码

可以使用/checked[+/-]编译选项来检测/不检测程序中所有发生的溢出,如果使用了/checked+来检测程序中所有发生的溢出,而对于某些特定的表达式或语句块又不想使用检测功能,那么可以像checked运算符一样使用unchecked运算符来关闭检测。

30.C#提供了下面这些位运算符:

31.8位和16位的整型包括了byte, sbyte, short和ushort,这些类型自身没有算数运算符,所以C#在需要时会将它们转换为更大的类型,这样就会导致如果接收运算结果的是一个较小的整型时,会发生编译错误,如short x = 1, y = 1; short z = x + y; // 此处编译错误。在这种情况下,为了使加法能够执行,x, y会被隐式的转换为int,这意味着结果也是int,而int不能被隐式的转换为short(因为会导致数据丢失),所以,为了使编译通过,我们需要加上显式转换,short z = (short)(x + y);

32.float和double类有一些常量用于NaN(Not a Number), +∞, -∞, MaxValue, MinValue和Epsilon。当发生除0时将导致结果为无穷值,如:

。当发生0除0或者无穷减无穷时将导致结果为NaN,如

。当使用==时,一个NaN值永远不会等于另一个值,即使是另一个NaN值,所以如果要测试一个值是否为NaN,必须使用float.IsNaN或者double.IsNaN函数。当使用Object.Equals函数时,也可以判断两个NaN值是否相等,如object.Equals (0.0/0.0, double.NaN) // true。

33.float类型适用于科学计算,而decimal类型适用于金融计算或者表示那些人为的值,下面是double类型与decimal类型的区别:

34.由于float和double类型的数值在内部是以2为基表示的,所以许多以10为基的小数部分字面值无法被精确的表示,如

,这就是为什么float和double类型不适用于金融计算的原因。相比之下,由于decimal类型的数值是以10为基表示的,所以这种情况不会出现。

35.C#的bool类型是一个可以被指定为true或者false的逻辑值。尽管一个bool类型仅需要1位的存储空间,但是运行时将使用1个字节的存储空间,因为这是运行时和处理器可以有效工作的最小单位。所以为了避免空间上的浪费,C#提供了一个BitArray类,该类位于System.Collections命名空间下,旨在只使用1位存储空间表示每个bool类型的值。

36.对于引用类型,是否相等,默认情况下是取决于所引用的对象是否相同,而不是对象内部的实际数据。因此,一个对象的两个拥有相同数据的实例被认为是不相等的,除非该对象所属的类型重载了==运算符以达到使他们相等的效果。

37.相等和比较运算符,==, !=, <, >, <=, >=,可以用于所有的数值类型,但是用于实数时需要谨慎小心(参见34)。这些运算符也可以用于enum(枚举)类型成员,比较的是他们代表的整型数值。

38.&&和||运算符用于测试条件与和条件或,他们是短路运算符,也就是说,当前一个表达式不满足时,后续表达式将不再计算,如if ( a && b ),如果表达式a为假,那么表达式b将不会计算,这是十分有用的,例如:if (sb != null && sb.Length > 0),可以避免抛出NullReferenceException异常。&和|运算符也可用于测试条件与和条件或,但是他们不是短路运算符,所以,一般很少用于条件测试。还有一个三目运算符,形式如q ? a : b,当q为真时,a将被计算,否则b将被计算。

39.C#的char类型代表一个Unicode字符,占用2个字节。一个char的字面表示在一个单引号中,如char c = 'A';转义字符用来表示那些不能显示的字符,一个转义字符是一个反斜杠紧跟一个字符,具有特定的含义,转义字符表如下:

。\u(或\x)转义字符允许你通过使用4位16进制来指定任意的Unicode字符,如char newLine = '\u000A';

40.当一个数值类型可以容纳一个unsigned short类型的值时,从char到该数值类型就可以隐式转换。否则的话,就需要显示转换。

41.string类型表示一个不可变的Unicode字符序列。一个string的字面表示在一个双引号中,如string s = "hello";尽管string是引用类型,而不是值类型,但是它的==运算符却遵循值类型的比较语义,如string a = "test", b = "test"; Console.Write (a == b); // True。转义字符同样也可以用在string类型中,如string a = "\\\\server\\fileshare";C#还提供逐字字符串字面值,以@开头并且不再解析转义字符,如string b = @"\\server\fileshare";和上面的字符串a是等价的。

42.+运算符用于连接两个字符串,如string a = "s" + "w";可能某个操作数不是string类型,那么该操作数类型的ToString函数将被调用,如string a = "s" + 5;等价于string a = "s" + 5.ToString();因为string类型是不可变的,所以多次的使用+运算符构建一个新的string是十分低效的。取而代之的,可以使用System.Text.StringBuilder类型,这代表了一个可变的字符串,以及拥有可以高效的添加,删除,插入,替换子串的方法。

43.string类型不支持<和>运算符来进行比较操作,必须使用string类型的CompareTo函数。

44.字符串的索引返回特定位置上的字符,如Console.Write ("word"[2]); // r。

45.切记:string类型代表的字符串是不可变的,所有操作字符串的函数将返回一个新的字符串,原来的字符串不会被改变。

46.一个数组(Array)代表一个特定类型的固定数量的元素。一旦数组被创建,它的长度就不能被改变了。数组元素总是被存储在连续的内存块中,以供高效的存取。一个数组以元素类型紧跟方括号来表示,如char[] vowels = new char[5]; 方括号也用于数组索引,存取一个特定位置的元素,如vowels[0] = 'a'; 在运行时所有的数组索引都会进行范围检测,如果使用了一个无效的索引,那么将抛出一个IndexOutOfRangeException异常。

47.数组的初始化表达式可以方便的同时声明和赋值一个数组,如char[] vowels = new char[] {'a','e','i','o','u'}; 或者更简单的方式char[] vowels = {'a','e','i','o','u'}; 可以使用for循环来遍历一个数组中的所有元素,同时,由于数组总是实现了IEnumerable<T>,所以也能够使用foreach来枚举数组成员:

复制代码
 1 char[] vowels = {'a','e','i','o','u'};
 2 
 3 for (int i = 0; i < vowels.Length; i++)
 4 {
 5     Console.Write (vowels[i]); 
 6 }
 7 
 8 foreach (char c in vowels)
 9 {
10     Console.Write (c);
11 }
复制代码

48.创建一个数组总是会使用默认值来预初始化每个元素,一个类型的默认值就是在内存中每一位都是0,对于值类型来说,就是0,对于引用类型来说,就是null。无论元素类型是什么,数组自身总是引用类型的。

49.多维数组有两种形式:矩形数组(Rectangular arrays)和不规则数组(Jagged arrays)。矩形数组声明时使用逗号来分割每一维,如int[,] matrix = new int [3, 3]; 一个矩形数组可以像下面这样初始化:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int[,] matrix = new int[,]
{
{0,1,2},
{3,4,5},
{6,7,8}
};
// 更简洁的方式
int[,] matrix =
{
{0,1,2},
{3,4,5},
{6,7,8}
};

不规则数组使用连续的方括号来代表每一维,如声明一个2维数组,最外维的大小为3:int[][] matrix = new int[3][]; 可以发现,声明时并没有指定内维的大小,与矩形数组不同的是,每个内维数组可以使任意大小的。因为内维数组被隐式初始化为null而不是一个空数组,所以内维数组必须手动创建:

?
1
2
3
4
5
6
7
int[][] matrix = new int[3][];
for  (int i = 0; i < matrix.Length; i++)
{
matrix[i] = new int[3]; // 创建内维数组
for(int  j = 0; j < matrix[i].Length; j++)
matrix[i][j] = i * 3 + j;
}

不规则数组可以像下面这样初始化:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int[][] matrix = new int[][]
{
newint[] {0,1,2},
newint[] {3,4,5},
newint[] {6,7,8}
};
// 更简洁的方式
int[][] matrix =
{
newint[] {0,1,2},
newint[] {3,4,5},
newint[] {6,7,8}
};

50.还有另一个简洁的数组初始化表达式,它省略了new关键字之后的类型名,让编译器去推断数组类型。当传递数组类型的参数时,这是一个十分有用的缩写:

?
1
2
void  Foo (char[] data) { ... }
Foo ( new[]{'a','e','i','o','u'} );

51.栈和堆是存放变量与常量的地方,它们有截然不同的生命期语义。

52.栈是用来存放局部变量和参数的内存块,当一个函数被调用和退出时,栈就会逻辑增长和减小,考虑下面的函数:

复制代码
1 static int Factorial( int n )
2 {
3     if ( 0 == n )
4     {
5         return 1;
6     }
7 
8     return ( n * Factorial(n-1) );
9 }
复制代码

这是一个递归函数,也就说它会自己调用自己。每次函数被调用时,就会有一个新的int参数被分配在栈上,而当函数退出时,int参数被释放。

53.堆是用来存放对象(也就是引用类型的实例)的地方。无论何时当一个对象被创建时,它被分配在堆上,并且一个对该对象的引用将被返回。运行时有一个垃圾收集器,会定期的释放在堆上的对象,所以你的计算机不会耗尽内存。只要一个对象没有对它自身的任何引用,那么该对象就可以被垃圾收集器释放。堆也可以存放静态字段和常量,与在堆上分配的对象不同的是,他们不会被垃圾收集器释放,而是直到应用程序结束才会销毁。

54.注意:不能像C++那样显示的释放对象,一个未被引用的对象最终会被垃圾收集器释放。

55.C#执行明确的分配政策。在实践中,这意味着除了一个不安全的上下文以外,不可能访问到未初始化的内存。明确的分配政策有下面3个含义:

1.局部变量在读取前必须指定一个值。

2.当一个函数被调用时,必须提供函数需要的参数。(除非为默认参数)

3.所有的其他变量(如数组元素)在运行时会自动初始化。

56.所有类型的实例都有一个默认值,预定义类型的默认值就是内存中每一位都是0,引用类型的默认值是null,数值类型和枚举类型的默认值是0,char类型的默认值是'\0',bool类型的默认值是false。可以使用default关键字来获取任意类型的默认值。

57.可以使用ref和out修饰符来控制如何传递参数,如下:

58.默认情况下,C#中参数的传递方式是值传递。也就是说将值类型的实例传递给函数时,将创建一份值的副本。将引用类型的实例传递给函数时,将创建一份引用的副本,而不是所引用的对象的副本。举例如下:

复制代码
 1 static void Foo( int n )
 2 {
 3     ++n;
 4     Console.WriteLine( n );
 5 }
 6 
 7 static void Main(string[] args)
 8 {
 9     int x = 10;
10     Foo( x );   // 创建一份x的副本
11     Console.WriteLine( x ); // x仍旧是10,因为Foo函数改变的是副本
12 }
13 ////////////////////////////////////////////////////////////////////////////
14 
15 static void Foo( StringBuilder strBuilder )
16 {
17     // strBuilder与sb引用同一个对象
18     strBuilder.Append( "Reference Copy" );
19             
20     // strBuilder是引用的副本,所以改变它不会影响实参
21     strBuilder = null;
22 }
23 
24 static void Main(string[] args)
25 {
26     StringBuilder sb = new StringBuilder();
27     Foo( sb );
28     Console.WriteLine( sb.ToString() ); // Reference Copy
29 }
复制代码

59.C#提供了ref修饰符来进行引用传递,例如:

复制代码
 1 static void Foo( ref int n )
 2 {
 3     ++n;
 4     Console.WriteLine( n );
 5 }
 6 
 7 static void Main(string[] args)
 8 {
 9     int x = 10;
10     Foo( ref x );  // 引用传递,也就是说形参n与实参x现在引用同一块内存
11     Console.WriteLine( x ); // x现在是11
12 }
复制代码

60.out修饰符与ref修饰符相似,除了:

1.传递到函数之前无需指定值。

2.函数返回之前必须被指定值。

out修饰符通常用于从函数获取多返回值的情形。

61.params修饰符可以用于一个函数的最后一个参数上,以表明该函数接受任意数量的特定类型的参数,但必须声明为数组形式,如:

复制代码
1 static int Sum (params int[] ints)
2 {
3     int sum = 0;
4     for (int i = 0; i < ints.Length; i++) sum += ints[i];
5     return sum;
6 }
7 
8 // 可以这样调用Sum函数
9 Console.WriteLine ( Sum (1, 2, 3, 4) );    // 10
复制代码

62.从C#4.0开始,提供了默认参数,当一个参数在声明时被指定了一个默认值时,它就是默认参数,如void Foo (int x = 23){ ... }。当调用函数时,默认参数可以被省略,如Foo();此时声明时指定的默认值将被传递给函数。

63.除了使用位置指定参数,还可以使用名称指定参数,如void Foo (int x, int y){ ... }; Foo(x:1, y:2); 以名称指定参数可以使用任意顺序,如Foo(y:2, x:1)和Foo(x:1, y:2)是等价的。也可以混合使用位置和名称指定参数,但是以名称指定参数必须出现在最后,如Foo(1, y:2); 以名称指定参数与默认参数一起使用时显得特别有用,如void Bar (int a=0, int b=0, int c=0, int d=0) { ... } 就可以使用以下方式只提供一个值给d:Bar( d:3 );

64.通常有在声明变量的同时初始化的情况,此时,如果编译器能够从初始化表达式中推断出类型的话,那么就可以使用var来代替类型声明,如var x = 5;等价于int x = 5; 这称为隐式类型变量,它是静态类型,如下面的语句将产生编译错误:var x = 5; x = "string"; // 编译错误,x是int类型

65.一个赋值表达式使用=运算符将一个表达式的结果赋值给一个变量,如int x = x * 5; 赋值表达式能够和其他表达式组合,如y = 5 * (x = 2); 初始化多个变量时,这种方式特别有用:a = b = c = d = 0; 复合赋值运算符是组合赋值运算符与另一个运算符的语法简写,如x *= 2;等价于x = x * 2;

66.当一个表达式包含多个运算符时,优先级和结合律决定了计算顺序。高优先级的运算符将在低优先级的运算符之前计算。当运算符的优先级相同时,运算符的结合律决定了运算顺序。如1+2*3的计算顺序为1+(2*3),因为*运算符的优先级高于+运算符。结合律分为左结合与右结合,左结合就是计算时从左至右,如8/4/2的计算顺序为(8/4)/2,因为/运算符是左结合。右结合就是计算时从右至左,如x=y=3,先将3赋值给y,然后将表达式y=3的结果(即3)赋值给x。

67.下表以优先级从高至低的顺序列出了C#中的运算符,相同副标题下的运算符具有相同的优先级:

68.一条声明语句声明了一个新的变量,在声明变量时可以选择是否使用表达式初始化该变量,声明语句以分号结尾。可以在一条声明语句中声明多个同类型的变量,当中以逗号分隔,如int a = 5, b = 6; 一个常量的声明与变量的声明类似,除了常量在声明后不能被改变,以及必须在声明时进行初始化。

69.一个局部变量的生命期为当前语句块,不能在当前语句块或任何嵌套的语句块中声明另一个同名局部变量。

70.表达式语句意味着表达式"做了"某些事情,如下:

赋值或修改一个变量

实例化一个对象

调用一个函数

一个没有做上面任何事情的表达式是非法的语句,如string s = "foo"; s.Length; // 非法的语句。 当调用一个构造函数或者一个有返回值的函数的时候,并不是必须使用该结果的,如new StringBuilder();是合法的语句。

71.在switch语句中只能使用能够被静态求值的表达式,类型被限制在内建的整型,string类型和枚举类型。在每一个case从句的最后,必须使用一些跳转语句类型明确说明接下去该如何执行,可选的跳转语句如下:

break - 跳转到switch语句的结尾

goto case x - 跳转到另一个case从句

goto default - 跳转到default从句

任何其他的跳转语句 - 也就是return,throw,continue或goto标签

当多个值需要执行相同的代码时,可以依次列出:

复制代码
 1 switch (cardNumber)
 2 {
 3 case 13:
 4 case 12:
 5 case 11:
 6     Console.WriteLine ("Face card");
 7     break;
 8 default:
 9     Console.WriteLine ("Plain card");
10     break;
11 }
复制代码

72.foreach语句迭代一个可枚举对象中的每一个元素,C#中大多数表示元素集合或元素列表的类型都是可枚举的,比如数组和字符串,如:

1 foreach ( char c in "foreach" )
2 {
3     Console.Write( c );
4 }

73.跳转语句包括break, continue, goto, return以及throw。break语句结束一个迭代语句或switch语句的执行。continue语句放弃循环中剩余的语句,立即开始下一次迭代。goto语句跳转到一个标签(使用冒号后缀表示)执行。return语句结束函数的执行,如果函数具有返回类型,那么必须返回一个该类型的表达式。

74.命名空间是一个域,在该域中的类型名称必须是唯一的。命名空间不受成员访问权限(private, internal, public等等)的影响。没有定义在任何命名空间的类型就说处于全局命名空间。

75.可以使用完整合格名称引用一个类型,也就是包括该类型定义所在的所有的命名空间。如:System.Security.Cryptography.RSA rsa = System.Security.Cryptography.RSA.Create(); 使用using指令导入命名空间后,就可以不需要使用完整合格名称来引用一个类型,如:using System.Security.Cryptography; RSA rsa = RSA.Create();

76.在外层命名空间声明的名称可以在内层命名空间中直接使用,而不需要使用完整合格名称,如

复制代码
 1 namespace Outer
 2 {
 3     namespace Middle
 4     {
 5         class Class1 {}
 6         
 7         namespace Inner
 8         {
 9             class Class2 : Class1 {}
10         }
11     }
12 }
复制代码

如果想要使用同一命名空间层级下不同分支里的一个类型,只需要使用完整合格名称的一部分即可:

复制代码
 1 namespace MyTradingCompany
 2 {
 3     namespace Common
 4     {
 5         class ReportBase {}
 6     }
 7 
 8     namespace ManagementReporting
 9     {
10         class SalesReport : Common.ReportBase {}
11     }
12 }
复制代码

77.如果相同的类型名称同时出现在内层和外层的命名空间中,那么内层的命名空间中的类型将替代外层的命名空间中的类型。如果想要使用外层的命名空间中的类型,需要使用包括命名空间的合格名称。

78.导入命名空间可能会导致类型名称冲突。相比导入整个命名空间,可以只导入需要的类型,并给类型一个别名,如

1 using PropertyInfo2 = System.Reflection.PropertyInfo;
2 class Program { PropertyInfo2 p; }

命名空间也可以给定别名,如

1 using R = System.Reflection;
2 class Program { R.PropertyInfo p; }

79.一个字段是一个类或结构体中的成员变量。一个字段可以使用readonly修饰符来防止它在构造后被修改,一个只读字段只能在声明时或构造函数中赋值。字段的初始化是可选的,未初始化的字段有默认值(0, \0, null, false),字段的初始化在构造函数之前,以他们声明出现的顺序执行。

80.一个类型可以重载方法(有多个相同名称的方法),只要参数类型不同即可,如

1 void Foo (int x);
2 void Foo (double x);
3 void Foo (int x, float y);
4 void Foo (float x, int y);

81.一个类或结构体可以重载构造函数,通过使用this关键字,一个重载的构造函数可以调用另外一个重载的构造函数,如

1 public class Wine
2 {
3     public Wine (decimal price) {...}
4     public Wine (decimal price, int year)
5                  : this (price) {...}
6 }

当一个构造函数调用另外一个构造函数时,被调用的构造函数先执行。

82.对于类来说,当且仅当没有定义任何构造函数时,编译器会自动生成一个无参的构造函数。对于结构体来说,无参的构造函数是结构体的内部结构,因此不能自己定义。结构体的无参的构造函数负责使用默认值初始化结构体中的每一个字段。

83.为了简化对象的初始化,对象的可访问的字段或属性可以在构造后的单条语句中初始化,如

复制代码
 1 public class Bunny
 2 {
 3   public string Name;
 4   public bool LikesCarrots, LikesHumans;
 5   public Bunny () {}
 6   public Bunny (string n) { Name = n; }
 7 }
 8 
 9 // 可以像下面这样初始化
10 Bunny b1 = new Bunny {
11                        Name="Bo",
12                        LikesCarrots = true,
13                        LikesHumans = false
14                      };
15 
16 Bunny b2 = new Bunny ("Bo") {
17                               LikesCarrots = true,
18                               LikesHumans = false
19                             };
复制代码

84.属性从外部看像字段,但在内部他们包含逻辑,像函数一样。一个属性像字段那样声明,但是附加了get/set块,如

复制代码
1 public class Stock
2 {
3     decimal currentPrice;
4     public decimal CurrentPrice    // 属性
5     {
6          get { return currentPrice; }
7          set { currentPrice = value; }
8     }
9 }
复制代码

get和set表示属性的访问器,当属性被读取的时候,get访问器就会运行,它必须返回与属性类型相同的一个值。当属性被赋值的时候,set访问器就会被运行,它包含一个名称为value的与属性类型相同的隐式参数。一个属性如果只定义了get访问器,那么它就是只读的。如果只定义了set访问器,那么它就是只写的。

85.大多数属性的实现仅仅只是读与写一个与属性同类型的私有字段,一个自动属性声明可以告诉编译器提供这种实现,自动属性声明如下:

1 public class Stock
2 {
3   public decimal CurrentPrice { get; set; }
4 }

此时编译器就会自动生成一个私有字段供属性读和写,该字段的名称是编译器生成的,所以不能引用。

86.索引器提供了一种自然的语法来访问一个类或结构体中的元素。写一个索引器需要定义一个名称为this的属性,然后在方括号中指定参数:

复制代码
1 class Sentence
2 {
3   string[] words = "The quick brown fox".Split();
4   public string this [int wordNum]      // 索引器
5   {
6     get { return words [wordNum];  }
7     set { words [wordNum] = value; }
8   }
9 }
复制代码

一个索引器可以使用多个参数。每个类型可以定义多个索引器,只要它们的参数类型不同即可。如果省略set访问器,那么索引器就是只读的。

87.一个常量是一个无法改变其值的字段。一个常量在编译时期被静态求值,无论何时使用它,编译器将用字面值替换它。一个常量使用关键字const进行声明,声明时必须被初始化:

1 public class Test
2 {
3   public const string Message = "Hello World";
4 }

88.静态构造函数在每个类型上执行一次,而不像普通构造函数那样在每个实例上执行一次。一个类型只能定义一个静态构造函数,并且必须无参。当一个类型被使用之前,运行时会自动调用静态构造函数,有2件事会触发这种行为:实例化一个类型的对象以及访问类型中的静态成员。有个需要注意的地方:如果静态构造函数引发一个未处理的异常,那么在应用程序的生命期内,该类型都无法使用。

89.一个类可以被标记为静态的,表明它必须完全由静态成员组成。

90.终结器(Finalizers)是只有类拥有的函数,该函数在垃圾收集器回收一个未被引用的对象的内存时被调用。终结器的语法是类名加上前缀~:

1 class Class1
2 {
3   ~Class1() { ... }
4 }

91.一个类只能有一个基类,但它自身可以作为许多类的基类。

92.引用是多态的,也就是说一个类型的变量可以引用它的子类对象。多态的实现基于这样一个事实:子类拥有其基类的所有功能。

93.一个对象的引用能够:

隐式的向上转型到基类的引用

显式的向下转型到子类的引用

在兼容的引用类型之间向上转型或向下转型将执行引用转换,引用转换将创建一个指向同对象的引用。向上转型总是成功的,而向下转型是否成功取决于对象的类型是否合适。

94.一个向上转型操作从子类引用创建一个基类引用。一个向下转型操作从基类引用创建一个子类引用,一个向下转型需要显式指定因为可能会失败,如果失败,将抛出InvalidCastException异常:

House h = new House();
Asset a = h;          // 向上转型总是成功
Stock s = (Stock)a;   // 向下转型失败: a不是Stock类型

95.as运算符执行向下转型,如果转型失败,会返回null,而不是抛出异常。as运算符不能用于数值转换。

96.is运算符用于测试是否一个引用转换将会成功,换句话说,也就是是否一个对象派生自一个特定类(或实现了一个接口)。

97.函数,属性,索引器以及事件都可以被声明为virtual,被标记为virtual就能够被想要提供特殊实现的子类覆盖(overridden),子类通过使用override修饰符来实现覆盖:

复制代码
 1 public class Asset
 2 {
 3   public string Name;
 4   public virtual decimal Liability { get { return 0; } }
 5 }
 6 
 7 public class House : Asset
 8 {
 9   public decimal Mortgage;
10   public override decimal Liability
11     { get { return Mortgage; } } // 覆盖基类的属性,提供特殊实现。使用override修饰符
12 }
复制代码

虚方法和覆盖方法的签名,返回类型以及访问权限必须一致。如果一个覆盖方法想要调用基类的实现,那么可以使用base关键字。

98.一个被声明为抽象(abstract)的类不能被实例化。抽象类可以定义抽象成员,抽象成员类似于虚成员,但他们不需要提供一个默认实现。子类必须提供抽象成员的实现,除非子类也被声明为抽象类。

99.一个基类和一个子类可以定义相同的成员,例如:

1 public class A      { public int Counter = 1; }
2 public class B : A  { public int Counter = 2; }

此时可以说B类中的Counter字段隐藏了A类中的Counter字段。编译器会生成警告并且按如下的方法解决二义性:A类的引用对象绑定A.Counter,B类的引用对象绑定B.Counter。但是有时候,可能是故意想要隐藏成员,在这种情况下,可以使用new修饰符:

public class A     { public     int Counter = 1; }
public class B : A { public new int Counter = 2; }

new修饰符其实只是使得编译器不再会生成警告,并且可以告诉其他程序员,这是故意的。

100.一个覆盖的方法可以使用sealed关键字来密封它的实现,从而防止被其子类覆盖。也可以密封类,从而隐式的密封所有的虚函数。

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:5435次
    • 积分:46
    • 等级:
    • 排名:千里之外
    • 原创:6篇
    • 转载:11篇
    • 译文:0篇
    • 评论:2条
    文章分类
    最新评论