ONE PIECE ------ C#

43 篇文章 0 订阅
35 篇文章 0 订阅
  1. C#区分大小写。
  2. 单行注释以两个斜杠字符开头(//) ,多行注释以一条斜杠和一个星号(/*)开头,以一个星号和一条斜杠(*/)结尾。注释是为了让人更好地理解程序,它们并不是程序的一部分。
  3. namespace 关键字声明了应与类相关的名称空间。其后花括号中的所有代码都被认为是在这个名称空间中。
  4. 编译器在using语句指定的名称空间中查找没有在当前名称空间中定义但在代码中引用的类,使用using 指令的原因是:要使用类System.Console。using System 语句允许把这个类简写为Console(System 名称空间中的其他类也与此类似)。如果没有using 语句,就必须完全限定对Console.WriteLine()方法的调用,system.Console.WriteLine("Hello");
  5. C#中做的所有工作都依赖于.NET 基类。
  6. 所有的C#代码都必须包含在一个类中。类的声明包括c1ass 关键字,其后是类名和一对花括号,与类相关的所有代码都应放在这对花括号中。
  7. 声明一个类MyFirstClassOP。假设该类位于OP 名称空间中,其完整的名称是OP.MyFirstClassOP。
  8. 每个C#可执行文件(如控制台应用程序、Windows 应用程序和Windows服务)都必须有一个入口点——Main()方法(注意M大写)。在程序启动时会调用这个方法。该方法要么没有返回值(void),要么返回一个整数(int)。
  9. 在 C#中声明变量:datatype identifier;声明之后,就可以使用赋值运算符(=)给它赋值。
    int i;
    i = 8;
    string s = "LY";
  10. 编译器不允许在表达式中使用未初始化的变量。编译器需要用某个初始值对变量进行初始化,之后才能在操作中引用该变量。大多数现代编译器把没有初始化标记为警告,但C#编译器把它当作错误来看待。
  11. 如果在一条语句中声明和初始化了多个变量,那么所有的变量都具有相同的数据类型:
    int x = 10, y = 20;
  12. 要声明不同类型的变量,需要使用单独的语句。在多个变量的声明中,不能指定不同的数据类型:
    int x = 10;
    bool y = true;
    int x = 10, bool y = true;
  13. 在C#中实例化一个引用对象需要使用new 关键字
  14. 类型推断(type inference)使用var 关键字。编译器可以根据变量的初始化值“推断”变量的类型。例如:
    int someNumber = 0;
    就变成:
    var someNumber = 0;
    即使someNumber 从来没有声明为int,编译器也可以确定,只要someNumber 在其作用域内,就是一个int。编译后,上面两个语句是等价的。
    声明了变量,推断出了类型后,就不能改变变量类型了。变量的类型确定后,就遵循其他变量类型遵循的强类型化规则。
  15. 变量的作用域是可以访问该变量的代码区域。一般情况下,确定作用域遵循以下规则:
    只要类在某个作用域内,其字段(也称为成员变量)也在该作用域内。
    a.局部变量存在于表示声明该变量的块语句或方法结束的右花括号之前的作用域内。
    b.在 for、while 或类似语句中声明的局部变量存在于该循环体内。
  16. 同名的局部变量不能在同一作用域内声明两次
  17. 常量是其值在使用过程中不会发生变化的变量。在声明和初始化变量时,在变量的前面加上关键字const,就可以把该变量指定为一个常量:
    const int a = 100;
    常量具有如下特点:
    a.常量必须在声明时初始化。指定了其值后,就不能再改写了。
    b.常量的值必须能在编译时用于计算。因此,不能用从一个变量中提取的值来初始化常量。如需要这么做,应用只读字段。
    c.常量总是静态的。但注意,不必(实际上,是不允许)在常量声明中包含修饰符static。
  18. C#把数据类型分为两种:值类型和引用类型
    区别是值类型直接存储其值,而引用类型存储对值的引用。这两种类型存储在内存的不同地方:值类型存储在堆栈中,而引用类型存储在托管堆上。注意区分某个类型是值类型还是引用类型,因为这种存储位置的不同会有不同的影响。
  19. 如果变量是一个引用类型,就可以把其值设置为null,表示它不引用任何对象:
    y = null;
    如果将引用类型设置为null,显然就不可能对它调用任何非静态的成员函数或字段,这么做会在运行期间抛出一个异常。
  20. 我们自己声明的类都是引用类型
  21. 如果要把自己的类型定义为值类型,就应把它声明为一个结构。
  22. C#中数据类型都以与平台无关的方式定义。
  23. C#认可的基本预定义类型并没有内置于C#语言中,而是内置于.NET Framework中。
  24. C#有15 个预定义类型,其中13 个是值类型,两个是引用类型(string 和object)。
  25. 整型(值类型):C#支持8 个预定义整数类型,所有整数类型的变量都能被赋予十进制或十六进制的值,后者需要0x 前缀
    如果对一个整数是int、uint、long 或是ulong 没有任何显式的声明,则该变量默认为int 类型。
    为了把输入的值指定为其他整数类型,可以在数字后面加上如下字符:
    uint ui = 1234U;
    1ong l = 1234L;
    ulong ul = 1234UL;
    也可以使用小写字母u 和l,但后者会与整数1 混淆。
  26. 浮点类型(值类型):如果在代码中没有对某个非整数值(如12.3)硬编码,则编译器一般假定该变量是double。如果想指定该值为float,可以在其后加上字符F(或f):
    float f = 12.3F;
  27. decima类型(值类型):decimal 类型表示精度更高的浮点数,C#一个重要的优点是提供了一种专用类型进行财务计算,这就是decimal 类型,注意,decimal 类型不是基本类型,所以在计算时使用该类型会有性能损失。要把数字指定为decimal 类型,而不是double、float 或整型,可以在数字的后面加上字符M(或m),如下所示。
    decimal d = 12.30M;
  28. bool类型(值类型):包含布尔值true 或false,bool 值和整数值不能相互隐式转换。如果变量(或函数的返回类型)声明为bool 类型,就只能使用值true 或false。如果试图使用0 表示false,非0 值表示true,就会出错。
  29. 字符类型(值类型):为了保存单个字符的值,C#支持char 数据类型。char 类型的字面量是用单引号括起来的,如'A'。如果把字符放在双引号中,编译器会把它看作字符串,从而产生错误。
    除了把char 表示为字符字面量之外,还可以用4 位十六进制的Unicode 值(如'\u0041')、带有数据类型转换的整数值(如(chaar)65)或十六进制数('\x0041')表示它们。
  30. object类型(引用类型):在C#中,object 类型就是最终的父类型,所有内置类型和用户定义的类型都从它派生而来。
    object 类型就可以用于两个目的:
    a. 可以使用object 引用绑定任何子类型的对象。例如,使用object 类型把堆栈中的一个值对象装箱,再移动到堆中。object 引用也可以用于反射,此时必须有代码来处理类型未知的对象。
    b. object 类型执行许多一般用途的基本方法,包括Equals()、GetHashCode()、GetTypc()和ToString()。用户定义的类需要使用一种面向对象技术——重写,提供其中一些方法的替代执行代码。例如,重写ToString()时,要给类提供一个方法,给出类本身的字符串表示。如果类中没有提供这些方法的实现代码,编译器就会使用object 类型中的实现代码,它们在类中的执行不一定正确。
  31. string类型(引用类型):C#有string 关键字,在编译为.NET 类时,它就是System.String。有了它,像字符串连接和字符串复制这样的操作就很简单了:
    string str1 = "Hello ";
    string str2 = "World";
    string str3 = str1 + str2;
    尽管这是一个值类型的赋值,但string 是一个引用类型。string 对象被分配在堆上,而不是栈上。因此,当把一个字符串变量赋予另一个字符串时,会得到对内存中同一个字符串的两个引用。但是,string 与引用类型在常见的操作上有一些区别。例如,字符串是不可改变的。修改其中一个字符串,就会创建一个全新的string 对象,而另一个字符串不发生任何变化。(这实际上是运算符重载的结果)
    字符串字面量放在双引号中("…");如果试图把字符串放在单引号中,编译器就会把它当作char,从而引发错误。C#字符串和char 一样,可以包含Unicode 和十六进制数转义序列。因为这些转义序列以一个反斜杠开头,所以不能在字符串中使用这个非转义的反斜杠字符,而需要用两个反斜杠字符(\\)来表示它:
    string filepath = "C:\\A\\b.cs";
    即使用户相信自己可以在任何情况下都记住要这么做,但输入两个反斜杠字符会令人迷惑。幸好,C#提供了另一种替代方式。可以在字符串字面量的前面加上字符@,在这个字符后的所有字符都看作是其原来的含义——它们不会解释为转义字符:
    string filepath = @"C:\A\b.cs";
    甚至允许在字符串字面量中包含换行符:
    string strAB= @"'A
    b.";
  32. 转义字符
  33. if语句:测试特定条件是否满足
    if (condition)
       statement(s)
    e1se
       statement(s)
    如果在条件中要执行多个语句,就需要用花括号({…})把这些语句组合为一个块。可以单独使用if 语句,不加最后的else 语句。也可以合并else if 子句,测试多个条件。
    对于if,要注意的一点是如果条件分支中只有一条语句,就无需使用花括号。
    在C#中,if 子句中的表达式必须等于布尔值。
    特别注意,C#使用“==”对变量进行等于比较。此时不要使用“=”,一个“=”用于赋值。
  34. switch语句:适合于从一组互斥的分支中选择一个执行分支。其形式是switch 参数的后面跟一组case 子句。如果switch 参数中表达式的值等于某个case 子句旁边的某个值,就执行该case 子句中的代码。此时不需要使用花括号把语句组合到块中;只需使用break 语句标记每段case 代码的结尾即可。也可以在switch 语句中包含一条default 子句,如果表达式不等于任何case 子句的值,就执行default 子句的代码。如果激活了块中靠前的一条case 子句,后面的case 子句就不会被激活。
    switch (integerA)
    {
       case 1:
          Conso1e.WriteLine("integerA =1");
          break;
       case 2:
          Console.WriteLine("integerA =2");
          break;
       case 3:
          Console.WriteLine("integerA =3");
          break;
       default:
          Console.WriteLine("integerA is not 1, 2, or 3");
          break;
    }
    注意case 的值必须是常量表达式;不允许使用变量。
    如果一条case 子句为空,就可以从这个case 跳到下一条case 上,这样就可以用相同的方式处理两条或多条case 子句了(不需要goto 语句)。
    switch(country)
    {
       case "au":
       case "uk":
       case "us":
          language = "Eng1ish";
          break;
       case "at":
       case "de":
          1anguage = "German";
          break;
    }
    在C#中,switch 语句的一个有趣的地方是case 子句的排放顺序是无关紧要的,甚至可以把default子句放在最前面!因此,任何两条case 都不能相同。这包括值相同的不同常量。
  35. for循环:C#的for 循环提供的迭代循环机制是在执行下一次迭代前,测试是否满足某个条件,其语法如下:
    for (initializer; condition; iterator)
       statement(s)
    其中:
    a. initializer 是指在执行第一次循环前要计算的表达式(通常把一个局部变量初始化为循环计
    数器);
    b. condition 是在每次迭代执行新循环前要测试的表达式(它必须等于true,才能执行下一次迭代);
    c. iterator 是每次迭代完要计算的表达式(通常是递增循环计数器)。
    当condition 等于false 时,迭代停止。
    for 循环是所谓的预测试循环,因为循环条件是在执行循环语句前计算的,如果循环条件为假,循环语句就根本不会执行。
    for 循环非常适合于一个语句或语句块重复执行预定的次数。
  36. while循环:与 for 循环一样,while 也是一个预测试循环。其语法是类似的,但while 循环只有一个表达式:
    while(condition)
       statement(s);
    与for 循环不同的是,while 循环最常用于以下情况:在循环开始前,不知道重复执行一个语句或语句块的次数。通常,在某次迭代中,while 循环体中的语句把布尔标志设置为false,结束循环。
  37. do...while循环:do...while 循环是while 循环的后测试版本。该循环的测试条件要在执行完循环体之后执行。因此do...while 循环适用于至少要将循环体执行一次的情况:
    do
    {
       ……
    } while (condition);
  38. foreach循环:foreach 循环可以迭代集合中的每一项。集合是一种包含一系列对象的对象即可。从技术上看,要使用集合对象,就必须支持IEnumerable接口。集合的例子有C#数组、System.Collection 名称空间中的集合类,以及用户定义的集合类。从下面的代码中可以了解foreach 循环的语法,其中假定arrayOfInts 是一个整型数组:
    foreach (int temp in arrayofInts)
    {
       Console.WriteLine(temp);
    }
    其中,foreach 循环每次迭代数组中的一个元素。它把每个元素的值放在int 型的变量temp 中,然后执行一次循环迭代。
    这里也可以使用类型推断功能。此时,foreach 循环变成:
    foreach (var temp in arrayOfInts)
    ...
    注意,foreach 循环不能改变集合中各项(上面的temp)的值,所以下面的代码不会编译:
    foreach (int temp in arrayOfInts)
    {
       temp++;
       Console.WriteLine(temp);
    }
    如果需要迭代集合中的各项,并改变它们的值,就应使用for 循环。
  39. goto语句:goto 语句可以直接跳转到程序中用标签指定的另一行(标签是一个标识符,后跟一个冒号):
    goto Label1;
    Console.WriteLine("This won't be executed");
    Label1:
    Console.WriteLine("Continuing execution from here");
    goto 语句有两个限制。不能跳转到像for 循环这样的代码块中,也不能跳出类的范围,不能退出try...catch 块后面的finally。
  40. break语句:在switch 语句中使用它退出某个case 语句。实际上,break 也可以用于退出for、foreach、while 或do...while 循环,该语句会使控制流执行循环后面的语句。如果该语句放在嵌套的循环中,就执行最内部循环后面的语句。如果break放在switch 语句或循环外部,就会产生编译错误。
  41. continue语句:continue 语句类似于break,也必须在for、foreach、while 或do...while 循环中使用。但它只退出循环的当前迭代,开始执行循环的下一次迭代,而不是退出循环。
  42. return语句:return 语句用于退出类的方法,把控制权返回方法的调用者。如果方法有返回类型,return 语句必须返回这个类型的值;如果方法返回void,应使用没有表达式的return 语句。
  43. 枚举:枚举是用户定义的整数类型。在声明一个枚举时,要指定该枚举的实例可以包含的一组可接受的值。不仅如此,还可以给值指定易于记忆的名称。如果在代码的某个地方,要试图把一个不在可接受范围内的值赋予枚举的一个实例,编译器就会报告一个错误。
    可以定义如下的枚举:
    public enum TimeOfDay
    {
       Morning = 0,
       Afternoon = 1,
       Evening = 2
    }
    在C#中,枚举的真正强大之处是它们在后台会实例化为派生于基类System.Enum 的结构。这表示可以对它们调用方法,执行有用的任务。注意因为.NET FrameWork 的执行方式,在语法上把枚举当做结构是不会造成性能损失。实际上,一旦代码编译好,枚举就成为基本类型,与int 和float类似。
    可以获取枚举的字符串表示,例如使用前面的TimeOfDay 枚举:
    TimeOfDay time = TimeOfDay.Afternoon;
    Conso1e.WriteLine(time.ToString());//会返回字符串Afternoon。
    另外,还可以从字符串中获取枚举值:
    TimeOfDay time2 = (TimeOfDay) Enum.Parse(typeof(TimeOfDay), "afternoon", true);
    Console.WriteLine((int)time2);
    这段代码说明了如何从字符串获取枚举值,并转换为整数。要从字符串中转换,需要使用静态的Enum.Parse()方法,这个方法带3 个参数。第1 个参数是要使用的枚举类型,其语法是关键字typeof后跟放在括号中的枚举类名。第2 个参数是要转换的字符串,第3 个参数是一个bool,指定在进行转换时是否忽略大小写。最后,注意Enum.Parse()方法实际上返回一个对象引用——我们需要把这个字符串显式转换为需要的枚举类型(这是一个拆箱操作的例子)。对于上面的代码,将返回1,作为一个对象,对应于TimeOfDay.Afternoon 的枚举值。在显式转换为int 时,会再次生成1。
  44. 名称空间:名称空间提供了一种组织相关类和其他类型的方式。与文件或组件不同,名称空间是一种逻辑组合,而不是物理组合。把一个类型放在名称空间中,可以有效地给这个类型指定一个较长的名称,该名称包括类型的名称空间,名称之间用句点分隔开,最后是类名。名称空间与程序集无关。同一个程序集中可以有不同的名称空间,也可以在不同的程序集中定义同一个名称空间中的类型。
  45. using语句:C#允许简写类的全名。为此,要在文件的顶部列出类的名称空间,前面加上using 关键字。在文件的其他地方,就可以使用其类型名称来引用名称空间中的类型了:
    using System;
    几乎所有的C#源代码都以语句using System;开头,这仅是因为Microsoft 提供的许多有用的类都包含在System 名称空间中。
    using 关键字的另一个用途是给类和名称空间指定别名。其语法如下:
    using alias = NamespaceName;
    注意名称空间别名的修饰符是“::”。
  46. Main()方法:C#是从方法Main()开始执行的。这个方法必须是类或结构的静态方法,并且其返回类型必须是int 或void。虽然显式指定public 修饰符是很常见的,因为按照定义,必须在程序外部调用该方法,但我们给该入口点方法指定什么访问级别并不重要,即使把该方法标记为private,它也可以运行。
    在编译C#控制台或Windows 应用程序时,默认情况下,编译器会在类中查找与上述签名匹配的Main()方法,并使这个类方法成为程序的入口点。如果有多个Main()方法,编译器就会返回一个错误消息。
    给Main()方法传递参数,可以让CLR 包含一个参数,这个参数是一个字符串数组,传统上称为args(但C#可以接受任何名称)。在启动程序时,程序可以使用这个数组。
  47. XML格式的文档说明:根据特定的注释自动创建XML格式的文档说明。这些注释都是单行注释,但都以3条斜杠(///)开头,而不是通常的两条斜杠。在这些注释中,可以把包含类型和类型成员的文档说明的XML 标记放在代码中。
  48. C#预处理器指令:这些命令从来不会转化为可执行代码中的命令,但会影响编译过程的各个方面。例如,使用预处理器指令可以禁止编译器编译代码的某一部分。
    预处理器指令的开头都有符号#。
    #define和#undef:
    #define 的用法如下所示:
    #define DEBUG
    它告诉编译器存在给定名称的符号,在本例中是DEBUG。这有点类似于声明一个变量,但这个变量并没有真正的值,只是存在而己。这个符号不是实际代码的一部分,而只在编译器编译代码时存在。在C#代码中它没有任何意义。
    #undef 正好相反——它删除符号的定义:
    #undef DEBUG
    如果符号不存在,#undef 就没有任何作用。同样,如果符号已经存在,则#define 也不起作用。必须把#define 和#undef 命令放在C#源文件的开头位置,在声明要编译的任何对象的代码之前。
    #if, #elif, #else和#endif:这些指令告诉编译器是否要编译某个代码块。
    int DoSomeWork(double x)
    {
    // do something
    #if DEBUG
    Console.WriteLine("x is " + x);
    #endif
    }
    这段代码会像往常那样编译,但Console.WriteLine()命令包含在#if 子句内。这行代码只有在前面的#define 命令定义了符号DEBUG 后才执行。当编译器遇到#if 语句后,将先检查相关的符号是否存在,如果符号存在,就编译#if 子句中的代码。否则,编译器会忽略所有的代码,直到遇到匹配的#endif 指令为止。#if 和#elif 还支持一组逻辑运算符“!”、“==”、“!=”和“||”。如果符号存在,就被认为是true,否则为false。
    #warning和#error:当编译器遇到它们时,会分别产生警告或错误。如果编译器遇到#warning 指令,会给用户显示#warning指令后面的文本,之后编译继续进行。如果编译器遇到#error 指令,就会给用户显示后面的文本,作为一条编译错误消息,然后会立即退出编译,不会生成IL 代码。
    #region和#endregion:#region 和#endregion 指令用于把一段代码标记为有给定名称的一个块。
    #line:#line 指令可以用于改变编译器在警告和错误信息中显示的文件名和行号信息。
    #pragma:#pragma 指令可以抑制或还原指定的编译警告。与命令行选项不同,#pragma 指令可以在类或方法级别执行,对抑制警告的内容和抑制的时间进行更精细的控制。
  49. 标识符:标识符是给变量、用户定义的类型(如类和结构)和这些类型的成员指定的名称。标识符区分大小写,所以interestRate 和InterestRate 是不同的变量。确定在C#中可以使用什么标识符有两条规则:
    a. 尽管可以包含数字字符,但它们必须以字母或下划线开头。
    b. 不能把C#关键字用作标识符。
    如果需要把某一保留字用作标识符(例如,访问一个用另一种语言编写的类),那么可以在标识符的前面加上前缀符号@,告知编译器其后的内容是一个标识符,而不是C#关键字(所以abstract 不是有效的标识符,@abstract 才是)。最后,标识符也可以包含Unicode 字符,用语法\uXXXX 来指定,其中XXXX 是Unicode 字符的4 位十六进制编码。
  50. 关键字
  51. Hungarian 表示法:变量名用带有前缀字母来表示某种数据类型,这种约定称为Hungarian 表示法。这样,其他阅读该代码的开发人员就可以立即从变量名中了解它代表什么数据类型。
  52. Pascal 表示法:在许多情况下,名称都应使用Pascal 大小写形式。Pascal 大小写形式指名称中单词的首字母大写,如EmployeeSalary、ConfirmationDialog、PlainTextEncoding。注意,名称空间和类,以及基类中的成员等的名称都应遵循该规则,最好不要使用带有下划线字符的单词,即名称不应是employee_salary。
  53. camel 表示法:这种形式类似于Pascal 大小写形式,但名称中第一个单词的首字母不大写,如employeeSalary、confirmationDialog、plainTextEncoding。
    有3 种情况可以使用camel 大小写形式。
    a. 类型中所有私有成员字段的名称都应是camel 大小写形式
    b. 传递给方法的所有参数的名称都应是camel 大小写形式
    c. camel 大小写形式也可以用于区分同名的两个对象——比较常见的情况是属性封装一个字段:
    private string employeeName;
    public string EmployeeName
    {
    get
    {
    return employeeName;
    }
    }
    如果这么做,则私有成员总是使用camel 大小写形式,而公有的或受保护的成员总是使用Pascal
    大小写形式,这样使用这段代码的其他类就只能使用Pascal 大小写形式的名称了(除了参数名以外)。
  54. 类和结构实际上都是创建对象的模板,每个对象都包含数据,并提供了处理和访问数据的方法。类定义了类的每个对象(称为实例)可以包含什么数据和功能。结构与类的区别是它们在内存中的存储方式、访问方式(类是存储在堆(heap)上的引用类型,而结构是存储在栈(stack)上的值类型)和它们的一些特征(如结构不支持继承)。较小的数据类型使用结构可提高性能。但在语法上,结构与类非常相似,主要的区别是使用关键字struct 代替class 来声明结构。对于类和结构,都使用关键字new 来声明实例:这个关键字创建对象并对其进行初始化。
  55. 类:类中的数据和函数称为类的成员。正式术语对数据成员和函数成员进行了区分。除了这些成员外,类还可以包含嵌套的类型(如其他类)。成员的可访问性可以是public、protected、internal、protected、private 或internal。
    数据成员是包含类的数据——字段、常量和事件的成员。数据成员可以是静态数据。类成员总是实例成员,除非用static 进行显式的声明。
    字段是与类相关的变量。一旦实例化类的对象,就可以使用语法Object.FieldName 来访问字段。
    常量与类的关联方式同变量与类的关联方式。使用const 关键字来声明常量。如果把它声明为public,就可以在类的外部访问它。
    事件是类的成员,在发生某些行为(如改变类的字段或属性,或者进行了某种形式的用户交互操作)时,它可以让对象通知调用方。
    函数成员提供了操作类中数据的某些功能,包括方法、属性、构造函数和终结器(finalizer)、运算符以及索引器。
    a.方法的声明:
    在 C#中,方法的定义包括任意方法修饰符(如方法的可访问性)、返回值的类型,然后依次是方
    法名和输入参数的列表(用圆括号括起来)和方法体(用花括号括起来)。
    [modifiers] return_type MethodName([parameters])
    {
    // Method body
    }
    每个参数都包括参数的类型名和在方法体中的引用名称。但如果方法有返回值,return 语句就必须与返回值一起使用,以指定出口点。
    如果方法没有返回值,就把返回类型指定为void,因为不能省略返回类型。如果方法不带参数,仍需要在方法名的后面包含一对空的圆括号()。此时return 语句就是可选的——当到达右花括号时,方法会自动返回。注意方法可以包含任意多条return 语句。
    b.调用方法:
    对象实例名.方法名
    c.传递参数:
    参数可以通过引用或通过值传递给方法。在变量通过引用传递给方法时(如数组或其他引用类型(如类)),被调用的方法得到的就是这个变量,所以在方法内部对变量进行的任何改变在方法退出后仍旧有效。而如果变量通过值传送给方法(如int),被调用的方法得到的是变量的一个相同副本,也就是说,在方法退出后,对变量进行的修改会丢失。对于复杂的数据类型,按引用传递的效率更高,因为在按值传递时,必须复制大量的数据。
    在C#中,除非特别说明,所有的参数都通过值来传递。但是,在理解引用类型的含义时需要注意。因为引用类型的变量只包含对象的引用,将要复制的正是这个引用,而不是对象本身,所以对底层对象的修改会保留下来。相反,值类型的对象包含的是实际数据,所以传递给方法的是数据本身的副本。
    注意字符串的行为方式有所不同,因为字符串是不可变的(如果改变字符串的值,就会创建一个全新的字符串),所以字符串无法采用一般引用类型的行为方式。在方法调用中,对字符串所做的任何改变都不会影响原始字符串。
    d.ref 参数:可以迫使值参数通过引用传送给方法。为此,要使用ref 关键字。如果把一个参数传递给方法,且这个方法的输入参数前带有ref 关键字,则该方法对变量所做的任何改变都会影响原始对象的值。
    e.out 参数:在方法的输入参数前面加上out 前缀时,传递给该方法的变量可以不初始化。该变量通过引用传递,所以在从被调用的方法中返回时,对应方法对该变量进行的任何改变都会保留下来。在调用该方法时,还需要使用out 关键字。
    C#要求对传递给方法的参数进行初始化,在传递给方法之前,无论是按值传递,还是按引用传递,任何变量都必须初始化。
    f.命名参数:参数一般需要按定义的顺序传送给方法。命名参数允许按任意顺序传递。
    string FullName(string firstName, string lastName)
    {
       return firstName + " " + lastName;
    }
    FullName("John", "Doe");
    FullName(lastName: "Doe", firstName: "John");
    上面两种调用方式返回相同的值。
    如果方法有几个参数,就可以在同一个调用中混合使用位置参数和命名参数。
    g.可选参数:参数也可以是可选的。必须为可选参数提供默认值。可选参数还必须是方法定义的最后一个参数。
    h.方法的重载:C#支持方法的重载——方法的几个版本有不同的签名(即,方法名相同,但参数的个数和/或类型不同)。为了重载方法,只需声明同名但参数个数或类型不同的方法即可。(1.两个方法不能仅在返回类型上有区别。
    2. 两个方法不能仅根据参数是声明为ref 还是out 来区分。)
  56. 属性:
    在C#中定义属性,可以使用下面的语法:
    public string SomeProperty
    {
       get
       {
          return "This is the property value.";
       }
       set
       {
          // do whatever needs to be done to set the property.
       }
    }
    get 访问器不带任何参数,且必须返回属性声明的类型。也不应为set 访问器指定任何显式参数,但编译器假定它带一个参数,其类型也与属性相同,并表示为value。例如,下面的代码包含一个属性Age,它设置了一个字段age。在这个例子中,age 表示属性Age 的后备变量。
    private int age;
    public int Age
    {
       get
       {
          return age;
       }
       set
       {
          age = value;
       }
    }
    注意这里所用的命名约定。我们采用C#的区分大小写模式,使用相同的名称,但公有属性采用Pascal 大小写形式命名,并且如果存在一个等价的私有字段则它采用camel 大小写形式命名。这会为识别字段提供极大的便利。
    a.只读和只写属性:
    在属性定义中省略set 访问器,就可以创建只读属性。同样,在属性定义中省略get 访问器,就可以创建只写属性。
    b.属性的访问修饰符:
    C#允许给属性的get 和set 访问器设置不同的访问修饰符,所以属性可以有公有的get 访问器和私有或受保护的set 访问器。这有助于控制属性的设置方式或时间。注意set访问器或get 访问器如果没有任何访问修饰符,就表示该 访问器具有属性的访问级别。在get 和set 访问器中,必须有一个具备属性的访问级别。
    c.自动实现的属性:必须有两个访问器,但是,每个访问器的访问级别可以不同。
    如果属性的set 和get 访问器中没有任何逻辑,就可以使用自动实现的属性。这种属性会自动实现后备成员变量。前面Age 示例的代码如:public int Age { get; set; }不需要声明private int Age。编译器会自动创建它。
  57. 构造函数:
    声明基本构造函数的语法就是声明一个与包含的类同名的方法,但该方法没有返回类型:
    public class MyClass
    {
       public MyClass()
       {
       }
    }
    一般情况下,如果没有提供任何构造函数,编译器会在后台创建一个默认的构造函数。这是一个非常基本的构造函数,它只能把所有的成员字段初始化为标准的默认值(例如,引用类型为空引用,数值数据类型为0,bool为false)。这通常就足够了,否则就需要编写自己的构造函数。可以为构造函数提供任意多的重载,只要它们的签名有明显的区别即可。但注意,如果提供了带参数的构造函数,编译器就不会自动提供默认的构造函数。只有在没有定义任何构造函数时,编译器才会自动提供默认的构造函数。在构造函数中一般使用this 关键字区分成员字段和同名的参数。
    public class MyNumber
    {
       private int number;
       private MyNumber(int number)
       {
          this.number = number;
       }
    }
    a.静态构造函数:
    C#的一个新特征是也可以给类编写无参数的静态构造函数。这种构造函数只执行一次,而前面的构造函数是实例构造函数,只要创建类的对象,就会执行它。
    class MyClass
    {
       static MyClass()
       {
          // initialization code
       }
       // rest of class definition
    }
    编写静态构造函数的一个原因是,类有一些静态字段或属性,需要在第一次使用类之前,从外部源中初始化这些静态字段和属性。.NET 运行库没有确保什么时候执行静态构造函数,所以不应把要求在某个特定时刻(例如,加载程序集时)执行的代码放在静态构造函数中。也不能预计不同类的静态构造函数按照什么顺序执行。但是,可以确保静态构造函数至多运行一次,即在代码引用类之前调用它。在C#中,通常在第一次调用类的任何成员之前执行静态构造函数。注意,静态构造函数没有访问修饰符,其他C#代码从来不调用它,但在加载类时,总是由.NET运行库调用它,所以像public 或private 这样的访问修饰符就没有任何意义。出于同样原因,静态构造函数不能带任何参数,一个类也只能有一个静态构造函数。很显然,静态构造函数只能访问类的静态成员,不能访问类的实例成员。注意,无参数的实例构造函数与静态构造函数可以在同一个类中同时定义。尽管参数列表相同,但这并不矛盾,因为在加载类时执行静态构造函数,而在创建实例时执行实例构造函数,所以何时执行哪个构造函数不会有冲突。如果多个类都有静态构造函数,先执行哪个静态构造函数就不确定。此时静态构造函数中的代码不应依赖于其他静态构造函数的执行清况。另一方面,如果任何静态字段有默认值,就在调用静态构造函数之前指定它们。
    b.从构造函数中调用其他构造函数:
    有时,在一个类中有几个构造函数,以容纳某些可选参数,这些构造函数包含一些共同的代码。
    例如,下面的情况:
    class Car
    {
       private string description;
       private uint nWheels;
       public Car(string description, uint nWheels)
       {
          this.description = description;
          this.nWheels = nWheels;
       }
       public Car(string description)
       {
          this.description = description;
          this.nWheels = 4;
       }
    }
    这两个构造函数初始化了相同的字段,显然,最好把所有的代码放在一个地方。C#有一个特殊的语法,称为构造函数初始化器,可以实现此目的:
    class Car
    {
       private string description;
       private uint nWheels;
       public Car(string description, uint nWheels)
       {
          this.description = description;
          this.nWheels = nWheels;
       }
       public Car(string description): this(description, 4)
       {
       }
    }
    这里,this 关键字仅调用参数最匹配的那个构造函数。注意,构造函数初始化器在构造函数的函数体之前执行。
    C#构造函数初始化器可以包含对同一个类的另一个构造函数的调用,也可以包含对直接基类的构造函数的调用(使用相同的语法,但应使用base 关键字代替this)。初始化器中不能有多个调用。
  58. 只读字段:
    常量的概念就是一个包含不能修改的值的变量,常量是C#与大多数编程语言共有的。但是,常量不必满足所有的要求。有时可能需要一些变量,其值不应改变,但在运行之前其值是未知的。C#为这种情形提供了另一种类型的变量:只读字段。readonly 关键字比const 灵活得多,允许把一个字段设置为常量,但还需要执行一些计算,以确定它的初始值。其规则是可以在构造函数中给只读字段赋值,但不能在其他地方赋值。只读字段还可以是一个实例字段,而不是静态字段,类的每个实例可以有不同的值。与const 字段不同,如果要把只读字段设置为静态,就必须显式声明它。
    在构造函数中如果没有给只读字段赋值。它的值就是其特定数据类型的默认值,或者在声明时给它初始化的值。
  59. 匿名类型:
    var 关键字,它用于表示隐式类型化的变量。var 与new 关键字一起使用时,可以创建匿名类型。匿名类型只是一个继承自Object 且没有名称的类。该类的定义从初始化器中推断,类似于隐式类型化的变量。
    如果需要一个对象包含某个人的姓氏、中间名和名字,则声明如下:
    var captain = new {FirstName = "James", MiddleName = "T", LastName = "Kirk"};
    如果所设置的值来自于另一个对象,就可以简化初始化器。如果已经有一个包含FirstName、MiddleName 和LastName 属性的类,且有该类的一个实例(person),captain 对象就可以初始化为:
    var captain = new {person.FirstName, person.MiddleName, person.LastName};
    person 对象的属性名应投射到新对象名captain。所以captain 对象应有FirstName、MiddleName和LastName 属性。
    这些新对象的类型名未知。编译器为类型“伪造”了一个名称,但只有编译器才能使用它。
  60. 结构:需要一个小的数据结构而类提供的功能多于需要的功能,由于性能原因,最好使用结构。
    用关键字struct 代替class,定义一个结构而不是类。
    为结构定义函数与为类定义函数完全相同。(为结构定义构造函数的方式与为类定义构造函数的方式相同,但不允许定义无参数的构造函数。)
    结构是值类型,不是引用类型。它们存储在栈中或存储为内联(inline)(如果它们是存储在堆中的另一个对象的一部分),其生存期的限制与简单的数据类型一样。
    a. 结构不支持继承。
    b. 对于结构构造函数的工作方式有一些区别。尤其是编译器总是提供一个无参数的默认构造函数,它是不允许替换的。
    c. 使用结构,可以指定字段如何在内存中的布局
    struct Dimensions
    {
       public double Length;
       public double Width;
    }
    虽然结构是值类型,但在语法上常常可以把它们当作类来处理。例如,在上面的Dimensions 类的定义中,可以编写下面的代码:
    Dimensions point = new Dimensions();
    point.Length = 3;
    point.Width = 6;
    注意,因为结构是值类型,所以new 运算符与类和其他引用类型的工作方式不同。new 运算符并不分配堆中的内存,而是只调用相应的构造函数,根据传送给它的参数,初始化所有的字段。对于结构,可以编写下述完全合法的代码:
    Dimensions point;
    point.Length = 3;
    point.Width = 6;
    结构遵循其他数据类型都遵循的规则:在使用前所有的元素都必须进行初始化。在结构上调用new 运算符,或者给所有的字段分别赋值,结构就完全初始化了。当然,如果结构定义为类的成员字段,在初始化包含的对象时,该结构会自动初始化为0。
  61. 部分类:partial 关键字允许把类、结构或接口放在多个文件中。一般情况下,一个类全部驻留在单个文件中。但有时,多个开发人员需要访问同一个类,或者某种类型的代码生成器生成了一个类的某部分,所以把类放在多个文件中是有益的。
    partial 关键字的用法是:把partial 放在class、struct 或interface 关键字的前面。
    //BigClassPart1.cs
    partial class TheBigClass
    {
       public void MethodOne()
       {
       }
    }
    //BigClassPart2.cs
    partial class TheBigClass
    {
       public void MethodTwo()
       {
       }
    }
    编译包含这两个源文件的项目时,会创建一个TheBigClass 类,它有两个方法MethodOne()和MethodTwo()。
    如果声明类时使用了下面的关键字,这些关键字就必须应用于同一个类的所有部分:
    a. public
    b. private
    c. protected
    d. internal
    e. abstract
    f.  sealed
    g. new
    h. 一般约束
    在嵌套的类型中,只要private 关键字位于class 关键字的前面,就可以嵌套部分类。在把部分类编译到类型中时,属性、XML 注释、接口、泛型类型的参数属性和成员会合并。
  62. 静态类:如果类只包含静态的方法和属性,该类就是静态的。
    不能创建静态类的实例。
    使用static 关键字来声明静态类。
    使用 类型名.方法名 即调用静态类中的方法。
  63. Object类:所有的.NET 类都派生自System.Object。实际上,如果在定义类时没有指定基类,编译器就会自动假定这个类派生自Object。其实际意义在于,除了自己定义的方法和属性等外,还可以访问为Object 定义的许多公有的和受保护的成员方法。
  64. 扩展方法:静态类中的静态方法。
    它允许改变一个类,但不需要该类的源代码。
    扩展方法是静态方法,它是类的一部分,但实际上没有放在类的源代码中。
    对于扩展方法,第一个参数是要扩展的类型,它放在this 关键字的后面。这告诉编译器,这个方法是扩展类型的一部分。在在扩展方法中,可以访问所扩展类型的所有公有方法和属性。
  65. 在面向对象的编程中,有两种截然不同的继承类型:实现继承和接口继承。
    a. 实现继承:表示一个类型派生于一个基类型,它拥有该基类型的所有成员字段和函数。在实现继承中,派生类型采用基类型的每个函数的实现代码,除非在派生类型的定义中指定重写某个函数的实现代码。在需要给现有的类型添加功能,或许多相关的类型共享一组重要的公共功能时,这种类型的继承非常有用。
    b. 接口继承:表示一个类型只继承了函数的签名,没有继承任何实现代码。在需要指定该类型具有某些可用的特性时,最好使用这种类型的继承。
    C#支持实现继承和接口继承。它们都内置于语言和架构中,因此可以根据应用程序的体系结构选择合适的继承。
  66. 每个C#类(除了Object 类之外)都可以有一个基类和任意多个基接口。结构并不支持实现继承,但支持接口继承。
  67. 实现继承:
    如果要声明派生自另一个类的一个类,就可以使用下面的语法:
    class MyDerivedClass: MyBaseClass
    {

    }
    如果类(或结构)也派生自接口,则用逗号分隔列表中的基类和接口:
    public class MyDerivedClass: MyBaseClass, IInterface1, IInterface2
    {

    }
  68. 虚方法:
    把一个基类函数声明为virtual,就可以在任何派生类中重写该函数:
    class MyBaseClass
    {
       public virtual string VirtualMethod()
       {
         
       }
    }
    C#要求在派生类的函数重写另一个函数时,要使用override 关键字显式声明:
    class MyDerivedClass: MyBaseClass
    {
       public override string VirtualMethod()
       {
         
       }
    }
  69. 隐藏方法:
    如果签名相同的方法在基类和派生类中都进行了声明,但该方法没有分别声明为virtual 和override,派生类方法就会隐藏基类方法。在C#中,要隐藏一个方法应使用new 关键字声明,如下所示:
    class MyDerivedClass: HisBaseClass
    {
       public new int MyGroovyMethod()
       {
         
       }
    }
  70. 调用函数的基类版本:使用base.<MethodName>()语法调用基类中的任何方法。
  71. 抽象类和抽象函数:C#允许把类和函数声明为abstract。抽象类不能实例化,而抽象函数不能直接实现,必须在非抽象的派生类中重写。显然,抽象函数本身也是虚拟的(尽管也不需要提供virtual 关键字,实际上,如果提供了该关键字,就会产生一个语法错误)。如果类包含抽象函数,则该类也是抽象的,也必须声明为抽象的:
    abstract class Building
    {
       public abstract decimal CalculateHeatingCost(); // abstract method
    }
  72. 密封类和密封方法:C#允许把类和方法声明为sealed。对于类,这表示不能继承该类:对于方法,这表示不能重写该方法。
  73. 修饰符:

  74. 接口:
    public interface IDisposable
    {
       void Dispose();
    }
    声明接口在语法上与声明抽象类完全相同,但不允许提供接口中任何成员的实现方式。一般情况下,接口只能包含方法、属性、索引器和事件的声明。
    不能实例化接口,它只能包含其成员的签名。接口既不能有构造函数也不能有字段。接口定义也不允许包含运算符重载,在接口定义中还不允许声明关于成员的修饰符。接口成员总是公有的,不能声明为虚拟或静态。
    实现(派生自)接口 的任何类都必须实现接口中的所有方法,没有实现所有这些方法就会产生一个编译错误。
  75. 数组:如果需要使用同一类型的多个对象,就可以使用数组。数组是一种数据结构,它可以包含同一类型的多个元素。
    在声明数组时,应先定义数组中元素的类型,其后是一对空方括号和一个变量名。声明了一个包含整型元素的数组:
    int[] myArray;
    声明了数组后,就必须为数组分配内存,以保存数组的所有元素。数组是引用类型,所以必须给它分配堆上的内存。为此,应使用new 运算符,指定数组中元素的类型和数量来初始化数组的变量。下面指定了数组的大小。
    myArray = new int[4];
    在一个语句中声明和初始化数组:int[] myArray = new int[4];
    使用数组初始化器为数组的每个元素赋值。数组初始化器只能在声明数组变量时使用,不能在声明数组之后使用。
    int[] myArray = new int[4] { 4, 7, 11, 2 };
    用花括号初始化数组,则还可以不指定数组的大小,因为编译器会自动统计元素的个数:
    int[] myArray = new int[] { 4, 7, 11, 2 };
    使用C#编译器还有一种更简化的形式。使用花括号可以同时声明和初始化数组:
    int[] myArray = {4, 7, 11, 2};
    在声明和初始化数组后,就可以使用索引器访问其中的元素了。数组只支持有整型参数的索引器。通过索引器传递元素编号,就可以访问数组。索引器总是以0 开头,表示第一个元素。可以传递给索引器的最大值是元素个数减1,因为索引从0 开始。
    Length 属性可以知道数组中的元素个数。
    for (int i = 0; i < myArray.Length;i++)
    {
       Console.WriteLine(myArray[i]);
    }
    使用foreach 语句迭代数组中的所有元素:
    foreach (var val in myArray)
    {
       Console.WriteLine(val);
    }
  76. 多维数组:多维数组用两个或多个整数来索引。
    在C#中声明这个二维数组,需要在方括号中加上一个逗号。数组在初始化时应指定每一维的大小(也称为阶)。如
    int[,] twodim = new int[3, 3];
  77. 锯齿数组:在锯齿数组中,每一行都可以有不同的大小。
    在声明锯齿数组时,要依次放置左右括号。在初始化锯齿数组时,只在第1 对方括号中设置该数组包含的行数。定义各行中元素个数的第2 个方括号设置为空,因为这类数组的每一行包含不同的元素个数。之后,为每一行指定行中的元素个数。
    int[][] jagged = new int[3][];
    jagged[0] = new int[2] { 1, 2 };
    jagged[1] = new int[6] { 3, 4, 5, 6,7, 8 };
    jagged[2] = new int[3] { 9, 10, 11 };
    迭代锯齿数组中所有元素的代码可以放在嵌套的for 循环中。在外层的for 循环中迭代每一行,在内层的for 循环中迭代一行中的每个元素:
    for (int row = 0; row < jagged.Leng;row++)
    {
       for (int element = 0; element < jagged[row].Length; element++)
       {
          Console.WriteLine("row: {0}, element: {1}, value: {2}", row, element, jagged[row][element]);
       }
    }
  78. 复制数组:因为数组是引用类型,所以将一个数组变量赋予另一个数组变量,就会得到两个引用同一数组的变量。而复制数组,会使数组实现ICloneable 接口。这个接口定义的Clone()方法会创建数组的浅表副本。
  79. foreach语句:
    foreach (var p in persons)
    {
       Console.WriteLine(p);
    }
    foreach 语句使用IEnumerator 接口的方法和属性,迭代集合中的所有元素。IEnumerator定义了Current 属性,来返回光标所在的元素,该接口的MoveNext()方法移动到集合的下一个元素上,如果有这个元素,该方法就返回true。如果集合不再有更多的元素,该方法就返回false。
    foreach 语句会解析为下面的代码段。首先,调用GetEnumerator()方法,获得数组的一个枚举器。
    在while 循环中——只要MoveNext()返回true——就用Current 属性访问数组中的元素:
    IEnumerator < Person > enumerator = persons.GetEnumerator();
    while (enumerator.MoveNext())
    {
       Person p = enumerator.Current;
       Console.WriteLine(p);
    }
  80. ……
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值