C#_08_官方文档_语言介绍

C# 官方文档_C#语言介绍篇章

https://docs.microsoft.com/zh-cn/dotnet/csharp/tour-of-csharp/
 

C# 语言介绍

C#(读作“See Sharp”)是一种简单易用的新式编程语言,不仅面向对象,还类型安全。 

C# 源于 C 语言系列,C、C++、Java 和 JavaScript 程序员很快就可以上手使用。

C# 是一种面向对象的语言。不仅如此,C# 还进一步支持面向组件编程。 

当代软件设计越来越依赖采用自描述的独立功能包形式的软件组件。

 此类组件的关键特征包括:

1. 为编程模型提供属性、方法和事件;

2. 包含提供组件声明性信息的特性;

3. 包含自己的文档。 

C# 提供了语言构造来直接支持这些概念,让 C# 成为一种非常自然的语言,可用于创建和使用软件组件。

多项 C# 功能有助于构造可靠耐用的应用程序:

1. 垃圾回收可自动回收无法访问的未使用对象占用的内存;

2. 异常处理提供了一种结构化的可扩展方法来执行错误检测和恢复;

3. C# 语言的类型安全设计禁止读取未初始化的变量、为范围之外的数组编制索引或执行未检查的类型转换。

C# 采用统一的类型系统。 所有 C# 类型(包括 int 和 double 等基元类型)均继承自一个根 object 类型。 

因此,所有类型共用一组通用运算,任何类型的值都可以一致地进行存储、传输和处理。 

此外,C# 还支持用户定义的引用类型和值类型,从而支持对象动态分配以及轻量级结构的内嵌式存储。

为了确保 C# 程序和库能够随着时间的推移以兼容的方式发展,C# 设计更强调版本控制。 

许多编程语言很少关注这个问题,因此,当引入新版依赖库时,用这些语言编写的程序会出现更多不必要的中断现象。 

由于更强调版本控制,直接受影响的 C# 设计方面包括:

单独的 virtual 和 override 修饰符、

关于方法重载决策的规则,

以及对显式接口成员声明的支持

 

Hello world

“Hello, World”程序历来都用于介绍编程语言。 

下面展示了此程序的 C# 代码:

using System;
class Hello
{
    static void Main()
    {
        Console.WriteLine("Hello, Beyond");
    }
}

C# 源文件的文件扩展名通常为 .cs。 

假设“Hello, World”程序存储在文件 hello.cs 中,则可以使用下列命令行编译此程序:

csc hello.cs

这会生成 hello.exe 可执行程序集。 

运行此应用程序生成以下输出:

Hello, Beyond

重要注意事项: 

编译 csc 命令实现的是完整框架,可能并不所有平台都适用。

“Hello, World”程序始于引用 System 命名空间的 using 指令。

 命名空间提供了一种用于组织 C# 程序和库的分层方法。 

命名空间包含类型和其他命名空间。

例如,System 命名空间包含许多类型(如程序中引用的 Console 类)和其他许多命名空间(如 IO 和 Collections)。 

借助引用给定命名空间的 using指令,可以非限定的方式使用作为相应命名空间成员的类型。 

由于使用 using 指令,因此程序可以使用 Console.WriteLine 作为 System.Console.WriteLine 的简写。

“Hello, World”程序声明的 Hello 类只有一个成员,即 Main 方法。 

Main 方法是使用静态修饰符进行声明。 

实例方法可以使用关键字 this 引用特定的封闭对象实例,

而静态方法则可以在不引用特定对象的情况下运行。 

按照约定,Main 静态方法是程序的入口点。

程序的输出是由 System 命名空间中 Console 类的 WriteLine 方法生成。 

此类由标准类库提供。默认情况下,编译器会自动引用标准类库。

关于 C#,要介绍的内容还有很多。 

下面各主题概述了 C# 语言元素。 

通过这些概述,可以了解 C# 语言所有元素的基本信息,并获得深入了解 C# 语言元素所需的信息:

  • 程序结构

    • 了解 C# 语言中的关键组织概念:程序、命名空间、类型、成员和程序集。

  • 类型和变量

    • 了解 C# 语言中的值类型、引用类型和变量。

  • 表达式

    • 表达式是在操作数和运算符的基础之上构造而成。 表达式生成的是值。

  • 语句

    • 语句用于表示程序的操作。

  • 类和对象

    • 类是最基本的 C# 类型。 对象是类实例。 类是使用成员生成的,此主题也对此进行了介绍。

  • 结构

    • 与类不同,结构是属于值类型的数据结构。

  • 数组

    • 数组是一种数据结构,其中包含许多通过计算索引访问的变量。

  • 接口

    • 接口定义了可由类和结构实现的协定。 接口可以包含方法、属性、事件和索引器。 接口不提供所定义的成员的实现代码,仅指定必须由实现接口的类或结构提供的成员。

  • 枚举

    • 枚举类型是包含一组已命名常量的独特值类型。

  • 委托

    • 委托类型表示对具有特定参数列表和返回类型的方法的引用。 通过委托,可以将方法视为可分配给变量并可作为参数传递的实体。 委托类似于其他一些语言中的函数指针概念,但与函数指针不同的是,委托不仅面向对象,还类型安全。

  • 特性

    • 使用特性,程序可以指定关于类型、成员和其他实体的附加声明性信息。

程序结构

C# 中的关键组织结构概念包括程序命名空间类型成员程序集。 

C# 程序由一个或多个源文件组成。 

程序声明类型,而类型则包含成员,并被整理到命名空间中。 

类型示例包括类和接口。 

成员示例包括字段、方法、属性和事件。 

编译完的 C# 程序实际上会打包到程序集中。 

程序集的文件扩展名通常为 .exe 或 .dll,具体取决于实现的是应用程序还是

以下示例在 Acme.Collections 命名空间中声明 Stack 类:

using System;
namespace Acme.Collections
{
    public class Stack
    {
        Entry top;
        public void Push(object data) 
        {
            top = new Entry(top, data);
        }

        public object Pop() 
        {
            if (top == null)
            {
                throw new InvalidOperationException();
            }
            object result = top.data;
            top = top.next;
            return result;
        }
        
        class Entry
        {
            public Entry next;
            public object data;
            public Entry(Entry next, object data)
            {
                this.next = next;
                this.data = data;
            }
        }
    }
}

此类的完全限定的名称为 Acme.Collections.Stack。 

此类包含多个成员:一个 top 字段、两个方法(Push 和 Pop)和一个 Entry 嵌套类。 

Entry 类还包含三个成员:一个 next 字段、一个 data 字段和一个构造函数。 

假定示例的源代码存储在 acme.cs 文件中,以下命令行

csc /t:library acme.cs

将示例编译成库(不含 Main 入口点的代码),并生成 acme.dll 程序集。

重要注意事项: 

上述示例使用 csc 作为命令行 C# 编译器。 

此编译器是 Windows 可执行文件。 

若要在其他平台上使用 C#,应使用 .NET Core 工具。 .

NET Core 生态系统使用 dotnet CLI 来管理命令行生成。 

这包括管理依赖项和调用 C# 编译器。 

有关在 .NET Core 支持的平台上使用这些工具的完整说明,请参阅这篇教程

程序集包含中间语言 (IL) 指令形式的可执行代码和元数据形式的符号信息。 

执行前,程序集中的 IL 代码会被 .NET 公共语言运行时的实时 (JIT) 编译器自动转换成处理器专属代码。

由于程序集是包含代码和元数据的自描述功能单元,因此无需在 C# 中使用 #include 指令和头文件。 

只需在编译程序时引用特定的程序集,即可在 C# 程序中使用此程序集中包含的公共类型和成员。 

例如,此程序使用 acme.dll 程序集中的 Acme.Collections.Stack 类:

using System;
using Acme.Collections;
class Example
{
    static void Main() 
    {
        Stack s = new Stack();
        s.Push(1);
        s.Push(10);
        s.Push(100);
        Console.WriteLine(s.Pop());
        Console.WriteLine(s.Pop());
        Console.WriteLine(s.Pop());
    }
}

如果程序存储在文件 example.cs 中,

那么在 example.cs 编译完后,可以使用编译器的 /r 选项引用 acme.dll 程序集:

csc /r:acme.dll example.cs

这会创建 example.exe 可执行程序集,它将在运行时输出以下内容:

100
10
1

使用 C#,可以将程序的源文本存储在多个源文件中。 

编译多文件 C# 程序时,可以将所有源文件一起处理,并且源文件可以随意相互引用。

从概念上讲,就像是所有源文件在处理前被集中到一个大文件中一样。

在 C# 中,永远都不需要使用前向声明,因为声明顺序无关紧要(除了极少数的例外情况)。 

C# 并不限制源文件只能声明一种公共类型,也不要求源文件的文件名必须与其中声明的类型相匹配。

类型和变量

C# 有两种类型:值类型引用类型。 

值类型的变量直接包含数据,而引用类型的变量则存储对数据(称为“对象”)的引用。 

对于引用类型,两个变量可以引用同一对象;

因此,对一个变量执行的运算可能会影响另一个变量引用的对象。 

借助值类型,每个变量都有自己的数据副本;

因此,对一个变量执行的运算不会影响另一个变量(ref 和 out 参数变量除外)。

C# 值类型又细分为简单类型枚举类型结构类型可以为 null 的值类型。 

C# 引用类型又细分为类类型接口类型数组类型委托类型

下面概述了 C# 的类型系统。

  • 值类型
    • 简单类型
      • 有符号的整型:sbyteshortintlong
      • 无符号的整型:byteushortuintulong
      • Unicode 字符:char
      • IEEE 浮点:floatdouble
      • 高精度小数:decimal
      • 布尔:bool
    • 枚举类型
      • 格式为 enum E {...} 的用户定义类型
    • 结构类型
      • 格式为 struct S {...} 的用户定义类型
    • 可以为 null 的值类型
      • 值为 null 的其他所有值类型的扩展
  • 引用类型
    • 类类型
      • 其他所有类型的最终基类:object
      • Unicode 字符串:string
      • 格式为 class C {...} 的用户定义类型
    • 接口类型
      • 格式为 interface I {...} 的用户定义类型
    • 数组类型
      • 一维和多维,例如 int[] 和 int[,]
    • 委托类型
      • 格式为 delegate int D(...) 的用户定义类型

八个整型类型支持带符号或不带符号格式的 8 位、16 位、32 位和 64 位值。

两个浮点类型(float 和 double)分别使用 32 位单精度和 64 位双精度 IEC-60559 格式表示。

decimal 类型是适用于财务和货币计算的 128 位数据类型。

C# 的 bool 类型用于表示布尔值(true 或 false)。

C# 使用 Unicode 编码处理字符和字符串。 char 类型表示 UTF-16 代码单元,string 类型表示一系列 UTF-16 代码单元。

下面总结了 C# 的数值类型。

  • 有符号的整型
    • sbyte:8 位,介于 -128 到 127 之间
    • short:16 位,介于 -32,768 到 32,767 之间
    • int:32 位,介于 -2,147,483,648 到 2,147,483,647 之间
    • long:64 位,介于 -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 之间
  • 无符号的整型
    • byte:8 位,介于 0 到 255 之间
    • ushort:16 位,介于 0 到 65,535 之间
    • uint:32 位,介于 0 到 4,294,967,295 之间
    • ulong:64 位,介于 0 到 18,446,744,073,709,551,615 之间
  • 浮点
    • float:32 位,介于 1.5 × 10-45 到 3.4 × 1038 之间,7 位精度
    • double:64 位,介于 5.0 × 10-324 到 1.7 × 10308 之间,15 位精度
  • 十进制
    • decimal:128 位,至少介于 -7.9 × 10-28 到 7.9 × 1028 之间,至少为 28 位精度

C# 程序使用类型声明创建新类型。 

类型声明指定新类型的名称和成员。 

用户可定义以下五种 C# 类型:类类型、结构类型、接口类型、枚举类型和委托类型

class 类型定义包含数据成员(字段)和函数成员(方法、属性及其他)的数据结构。 

类类型支持单一继承和多形性,即派生类可以扩展和专门针对基类的机制。

struct 类型定义包含数据成员和函数成员的结构,这一点与类类型相似。 

不过,与类不同的是,结构是值类型,通常不需要进行堆分配。 

结构类型不支持用户指定的继承,并且所有结构类型均隐式继承自类型 object

interface 类型将协定定义为一组已命名的公共函数成员。 

实现 interface 的 class 或 struct 必须提供接口函数成员的实现代码。 

interface 可以继承自多个基接口,class 和 struct 可以实现多个接口。

delegate 类型表示引用包含特定参数列表和返回类型的方法。 

通过委托,可以将方法视为可分配给变量并可作为参数传递的实体。 

委托类同于函数式语言提供的函数类型。 

委托也类似于其他一些语言中的函数指针概念,

但与函数指针不同的是,委托不仅面向对象,还类型安全。

classstructinterface 和 delegate 类型全部都支持泛型,因此可以使用其他类型对它们进行参数化。

enum 类型是一种包含已命名常量的独特类型。 

每个 enum 类型都有一个基础类型(必须是八种整型类型之一)。 

enum 类型的值集与基础类型的值集相同。

C# 支持任意类型的一维和多维数组。 与上述类型不同,数组类型无需先声明即可使用。 

相反,数组类型是通过在类型名称后面添加方括号构造而成。 

例如,int[] 是 int 类型的一维数组,

int[,] 是 int类型的二维数组,

int[][] 是由 int 类型的一维数组构成的一维数组。

可以为 null 的值类型也无需先声明即可使用。 

对于所有不可以为 null 的值类型 T,都有对应的可以为 null 的值类型 T?,后者可以包含附加值 null。 

例如,int? 是可以包含任何 32 位整数或值 null 的类型。

C# 采用统一的类型系统,因此任意类型的值都可视为 object。 

每种 C# 类型都直接或间接地派生自 object 类类型,而 object 是所有类型的最终基类。 

只需将值视为类型 object,即可将引用类型的值视为对象。 

通过执行装箱取消装箱操作,可以将值类型的值视为对象。 

在以下示例中,int 值被转换成 object,然后又恢复成 int

using System;
class BoxingExample
{
    static void Main()
    {
        int i = 123;
        object o = i;    // Boxing
        int j = (int)o;  // Unboxing
    }
}

当值类型的值转换成 object 类型时,将分配 object 实例(亦称为“箱”)来包含值,然后该值会复制到相应的箱中。 

相反,当 object 引用被显式转换成值类型时,将检查引用的 object 是否是具有正确值类型的箱;

如果检查成功,则会将箱中的值复制出来。

C# 的统一类型系统实际上意味着可以“按需”将值类型转换成对象。 

鉴于这种统一性,使用类型 object 的常规用途库可以与引用类型和值类型结合使用。

C# 有多种变量,其中包括字段、数组元素、局部变量和参数。 

变量表示存储位置,每个变量都具有一种类型,用于确定可以在变量中存储哪些值,如下文所述。

  • 不可以为 null 的值类型
    • 具有精确类型的值
  • 可以为 null 的值类型
    • null 值或具有精确类型的值
  • object
    • null 引用、对任意引用类型的对象的引用,或对任意值类型的装箱值的引用
  • 类类型
    • null 引用、对类类型实例的引用,或对派生自类类型的类实例的引用
  • 接口类型
    • null 引用、对实现接口类型的类类型实例的引用,或对实现接口类型的值类型的装箱值的引用
  • 数组类型
    • null 引用、对数组类型实例的引用,或对兼容的数组类型实例的引用
  • 委托类型
    • null 引用或对兼容的委托类型实例的引用

表达式

表达式是在操作数运算符的基础之上构造而成。 

表达式的运算符指明了向操作数应用的运算。 

运算符的示例包括 +-*/ 和 new。 

操作数的示例包括文本、字段、局部变量和表达式。

如果表达式包含多个运算符,那么运算符的优先级决定了各个运算符的计算顺序。 

例如,表达式 x + y * z 相当于计算 x + (y * z),因为 * 运算符的优先级高于 + 运算符。

如果操作数两边的两个运算符的优先级相同,那么运算符的结合性决定了运算的执行顺序:

  • 除了赋值运算符之外,所有二元运算符均为左结合运算符,即从左向右执行运算。 例如,x + y + z将计算为 (x + y) + z
  • 赋值运算符条件运算符 (?:) 右结合运算符,即从右向左执行运算。 例如,x = y = z 将计算为 x = (y = z)

可以使用括号控制优先级和结合性。 

例如,x + y * z 先计算 y 乘 z,并将结果与 x 相加,

而 (x + y) * z 则先计算 x 加 y,然后将结果与 z 相乘。

大多数运算符都可以重载。 

借助运算符重载,可以为一个或两个操作数为用户定义类或结构类型的运算指定用户定义运算符实现代码。

下面总结了 C# 运算符,按优先级从高到低的顺序列出了各类运算符。 

同一类别的运算符的优先级也相同。 

每个类别下均列出了相应类别的表达式,以及对每种表达式类型的说明。

  • 基本
    • x.m:成员访问
    • x(...):方法和委托调用
    • x[...]:数组和索引器访问
    • x++:后置递增
    • x--:后置递减
    • new T(...):创建对象和委托
    • new T(...){...}:使用初始值设定项的对象创建
    • new {...}:匿名对象初始值设定项
    • new T[...]:数组创建
    • typeof(T):获取 T 的 Type 对象
    • checked(x):在已检查的上下文中计算表达式
    • unchecked(x):在未检查的上下文中计算表达式
    • default(T):获取类型为 T 的默认值
    • delegate {...}:匿名函数(匿名方法)
  • 一元
    • +x:标识
    • -x:取反
    • !x:逻辑取反
    • ~x:按位取反
    • ++x:前置递增
    • --x:前置递减
    • (T)x:将 x 显式转换成类型 T
    • await x:异步等待 x 完成
  • 乘法
    • x * y:乘法
    • x / y:除法
    • x % y:求余
  • 加法
    • x + y:加法、字符串串联、委托组合
    • x – y:减法、委托删除
  • Shift
    • x << y:左移位
    • x >> y:右移位
  • 关系和类型测试
    • x < y:小于
    • x > y:大于
    • x <= y:小于或等于
    • x >= y:大于或等于
    • x is T:如果 x 是 T,返回 true;否则,返回 false
    • x as T:返回类型为 T 的 x;如果 x 的类型不是 T,返回 null
  • 相等
    • x == y:等于
    • x != y:不等于
  • 逻辑“与”
    • x & y:整数型位AND,布尔型逻辑 AND
  • 逻辑 XOR
    • x ^ y:整数型位 XOR,布尔型逻辑 XOR
  • 逻辑“或”
    • x | y:整数型位 OR,布尔型逻辑 OR
  • 条件“与”
    • x && y:仅当 x 不是 false 时,才计算 y
  • 条件“或”
    • x || y:仅当 x 不是 true 时,才计算 y
  • null 合并
    • x ?? y:如果 x 为 null,计算结果为 y;否则,计算结果为 x
  • 条件运算
    • x ? y : z:如果 x 为 true,计算 y;如果 x 为 false,计算 z
  • 赋值或匿名函数
    • x = y:赋值
    • x op= y:复合赋值;支持以下运算符
      • *= /= %= += -= <<= >>= &= ^= |=
    • (T x) => y:匿名函数(lambda 表达式)

语句

程序操作使用语句进行表示。 

C# 支持几种不同的语句,其中许多语句是从嵌入语句的角度来定义的。

使用代码块,可以在允许编写一个语句的上下文中编写多个语句。 

代码块是由一系列在分隔符 { 和 }内编写的语句组成。

声明语句用于声明局部变量和常量。

表达式语句用于计算表达式。 

可用作语句的表达式包括方法调用、使用 new 运算符的对象分配、使用 =和复合赋值运算符的赋值、使用 ++ 和 -- 运算符和 await 表达式的递增和递减运算。

选择语句用于根据一些表达式的值从多个可能的语句中选择一个以供执行。 
这一类语句包括 if 和 switch 语句。

迭代语句用于重复执行嵌入语句。 

这一类语句包括 whiledofor 和 foreach 语句。

跳转语句用于转移控制权。 

这一类语句包括 breakcontinuegotothrowreturn 和 yield 语句。

try...catch 语句用于捕获在代码块执行期间发生的异常,try...finally 语句用于指定始终执行的最终代码,无论异常发生与否。

checked 和 unchecked 语句用于控制整型类型算术运算和转换的溢出检查上下文。

lock 语句用于获取给定对象的相互排斥锁定,执行语句,然后解除锁定。

using 语句用于获取资源,执行语句,然后释放资源。

下面列出了可以使用的各种语句,以及每种语句的示例。

  • 局部变量声明:
static void Declarations(string[] args)
{
    int a;
    int b = 2, c = 3;
    a = 1;
    Console.WriteLine(a + b + c);
}
  • 局部常量声明:
static void ConstantDeclarations(string[] args)
{
    const float pi = 3.1415927f;
    const int r = 25;
    Console.WriteLine(pi * r * r);
}
  • 表达式语句:
static void Expressions(string[] args)
{
    int i;
    i = 123;                // Expression statement
    Console.WriteLine(i);   // Expression statement
    i++;                    // Expression statement
    Console.WriteLine(i);   // Expression statement
}
  • if 语句:
static void IfStatement(string[] args)
{
    if (args.Length == 0)
    {
        Console.WriteLine("No arguments");
    }
    else 
    {
        Console.WriteLine("One or more arguments");
    }
}
  • switch 语句:
static void SwitchStatement(string[] args)
{
    int n = args.Length;
    switch (n) 
    {
        case 0:
            Console.WriteLine("No arguments");
            break;
        case 1:
            Console.WriteLine("One argument");
            break;
        default:
            Console.WriteLine($"{n} arguments");
            break;
    }
}
  • while 语句:
static void WhileStatement(string[] args)
{
    int i = 0;
    while (i < args.Length) 
    {
        Console.WriteLine(args[i]);
        i++;
    }
}
  • do 语句:
static void DoStatement(string[] args)
{
    string s;
    do 
    {
        s = Console.ReadLine();
        Console.WriteLine(s);
    } while (!string.IsNullOrEmpty(s));
}

  • for 语句:
static void ForStatement(string[] args)
{
    for (int i = 0; i < args.Length; i++) 
    {
        Console.WriteLine(args[i]);
    }
}
  • foreach 语句:
static void ForEachStatement(string[] args)
{
    foreach (string s in args) 
    {
        Console.WriteLine(s);
    }
}
  • break 语句:
static void BreakStatement(string[] args)
{
    while (true) 
    {
        string s = Console.ReadLine();
        if (string.IsNullOrEmpty(s)) 
            break;
        Console.WriteLine(s);
    }
}
  • continue 语句:
static void ContinueStatement(string[] args)
{
    for (int i = 0; i < args.Length; i++) 
    {
        if (args[i].StartsWith("/")) 
            continue;
        Console.WriteLine(args[i]);
    }
}
  • goto 语句:
static void GoToStatement(string[] args)
{
    int i = 0;
    goto check;
    loop:
    Console.WriteLine(args[i++]);
    check:
    if (i < args.Length) 
        goto loop;
}
  • return 语句:
static int Add(int a, int b) 
{
    return a + b;
}
static void ReturnStatement(string[] args)
{
   Console.WriteLine(Add(1, 2));
   return;
}
  • yield 语句:
static IEnumerable<int> Range(int from, int to) 
{
    for (int i = from; i < to; i++) 
    {
        yield return i;
    }
    yield break;
}
static void YieldStatement(string[] args)
{
    foreach (int i in Range(-10,10)) 
    {
        Console.WriteLine(i);
    }
}
  • throw 和 try 语句:
static double Divide(double x, double y) 
{
    if (y == 0) 
        throw new DivideByZeroException();
    return x / y;
}
static void TryCatch(string[] args) 
{
    try 
    {
        if (args.Length != 2) 
        {
            throw new InvalidOperationException("Two numbers required");
        }
        double x = double.Parse(args[0]);
        double y = double.Parse(args[1]);
        Console.WriteLine(Divide(x, y));
    }
    catch (InvalidOperationException e) 
    {
        Console.WriteLine(e.Message);
    }
    finally 
    {
        Console.WriteLine("Good bye!");
    }
}
  • checked 和 unchecked 语句:
static void CheckedUnchecked(string[] args) 
{
    int x = int.MaxValue;
    unchecked 
    {
        Console.WriteLine(x + 1);  // Overflow
    }
    checked 
    {
        Console.WriteLine(x + 1);  // Exception
    }     
}
  • lock 语句:
class Account
{
    decimal balance;
    private readonly object sync = new object();
    public void Withdraw(decimal amount) 
    {
        lock (sync) 
        {
            if (amount > balance) 
            {
                throw new Exception(
                    "Insufficient funds");
            }
            balance -= amount;
        }
    }
}
  • using 语句:
static void UsingStatement(string[] args) 
{
    using (TextWriter w = File.CreateText("test.txt")) 
    {
        w.WriteLine("Line one");
        w.WriteLine("Line two");
        w.WriteLine("Line three");
    }
}

类和对象

是最基本的 C# 类型。 

类是一种数据结构,可在一个单元中就将状态(字段)和操作(方法和其他函数成员)结合起来。 

类为动态创建的类实例(亦称为“对象”)提供了定义。 

类支持继承多形性,即派生类可以扩展和专门针对基类的机制。

新类使用类声明进行创建。 类声明的开头是标头,指定了类的特性和修饰符、类名、基类(若指定)以及类实现的接口。 

标头后面是类主体,由在分隔符 { 和 } 内编写的成员声明列表组成。

以下是简单类 Point 的声明:

public class Point
{
    public int x, y;
    public Point(int x, int y) 
    {
        this.x = x;
        this.y = y;
    }
}

类实例是使用 new 运算符进行创建,此运算符为新实例分配内存,调用构造函数来初始化实例,并返回对实例的引用。 

以下语句创建两个 Point 对象,并将对这些对象的引用存储在两个变量中:

Point p1 = new Point(0, 0);
Point p2 = new Point(10, 20);

当无法再访问对象时,对象占用的内存会被自动回收。 既没必要,也无法在 C# 中显式解除分配对象。

 

成员

类成员要么是静态成员,要么是实例成员。 静态成员属于类,而实例成员则属于对象(类实例)。

下面概述了类可以包含的成员类型。

  • 常量
    • 与类相关联的常量值
  • 字段
    • 类的常量
  • 方法
    • 类可以执行的计算和操作
  • 属性
    • 与读取和写入类的已命名属性相关联的操作
  • 索引器
    • 与将类实例编入索引(像处理数组一样)相关联的操作
  • 事件
    • 类可以生成的通知
  • 运算符
    • 类支持的转换和表达式运算符
  • 构造函数
    • 初始化类实例或类本身所需的操作
  • 终结器
    • 永久放弃类实例前要执行的操作
  • 类型
    • 类声明的嵌套类型

 

可访问性

每个类成员都有关联的可访问性,用于控制能够访问成员的程序文本区域。 可访问性有六种可能的形式。总结如下。

  • public
    • 访问不受限
  • protected
    • 只能访问此类或派生自此类的类
  • internal
    • 访问限于当前程序集(.exe、.dll 等)
  • protected internal
    • 访问限于包含类、派生自包含类的类或同一程序集中的类
  • private
    • 只能访问此类
  • private protected
    • 访问限于同一程序集中的包含类或派生自包含类的类

 

类型参数

类定义可能会按如下方式指定一组类型参数:在类名后面用尖括号括住类型参数名称列表。 

然后,可以在类声明的主体中使用类型参数来定义类成员。 

在以下示例中,Pair 的类型参数是 TFirst 和 TSecond

public class Pair<TFirst,TSecond>
{
    public TFirst First;
    public TSecond Second;
}

声明为需要使用类型参数的类类型被称为泛型类类型。 

结构、接口和委托类型也可以是泛型。 使用泛型类时,必须为每个类型参数提供类型自变量:

Pair<int,string> pair = new Pair<int,string> { First = 1, Second = "two" };
int i = pair.First;     // TFirst is int
string s = pair.Second; // TSecond is string

包含类型自变量的泛型类型(如上面的 Pair<int,string>)被称为构造泛型类型

 

基类

类声明可能会按如下方式指定基类:在类名和类型参数后面编写冒号和基类名。 省略基类规范与从 object 类型派生相同。 在以下示例中,Point3D 的基类是 PointPoint 的基类是 object

public class Point
{
    public int x, y;
    public Point(int x, int y) 
    {
        this.x = x;
        this.y = y;
    }
}
public class Point3D: Point
{
    public int z;
    public Point3D(int x, int y, int z) : 
        base(x, y) 
    {
        this.z = z;
    }
}

类继承其基类的成员。 继承是指隐式包含其基类的所有成员的类,实例和静态构造函数以及基类的终结器除外。 派生类可以其继承的类添加新成员,但无法删除继承成员的定义。 在上面的示例中,Point3D 从 Point 继承了 x 和 y 字段,每个 Point3D 实例均包含三个字段(xy 和 z)。

可以将类类型隐式转换成其任意基类类型。 因此,类类型的变量可以引用相应类的实例或任意派生类的实例。 例如,类声明如上,Point 类型的变量可以引用 Point 或 Point3D

Point a = new Point(10, 20);
Point b = new Point3D(10, 20, 30);

 

字段

字段是与类或类实例相关联的变量。

使用静态修饰符声明的字段定义的是静态字段。 静态字段只指明一个存储位置。 无论创建多少个类实例,永远只有一个静态字段副本。

不使用静态修饰符声明的字段定义的是实例字段。 每个类实例均包含相应类的所有实例字段的单独副本。

在以下示例中,每个 Color 类实例均包含 rg 和 b 实例字段的单独副本,但分别只包含 BlackWhiteRedGreen 和 Blue 静态字段的一个副本:

public class Color
{
    public static readonly Color Black = new Color(0, 0, 0);
    public static readonly Color White = new Color(255, 255, 255);
    public static readonly Color Red = new Color(255, 0, 0);
    public static readonly Color Green = new Color(0, 255, 0);
    public static readonly Color Blue = new Color(0, 0, 255);
    private byte r, g, b;
    public Color(byte r, byte g, byte b) 
    {
        this.r = r;
        this.g = g;
        this.b = b;
    }
}

如上面的示例所示,可以使用 readonly 修饰符声明只读字段。 只能在字段声明期间或在同一个类的构造函数中向 readonly 字段赋值。

 

方法

方法是实现对象或类可执行的计算或操作的成员。 静态方法是通过类进行访问。 实例方法是通过类实例进行访问。

方法可能具有参数列表,用于表示传递给方法的值或变量引用;并具有返回类型,用于指定方法计算并返回的值的类型。 如果方法未返回值,则其返回类型为 void

方法可能也包含一组类型参数,必须在调用方法时指定类型自变量,这一点与类型一样。 与类型不同的是,通常可以根据方法调用的自变量推断出类型自变量,无需显式指定。

在声明方法的类中,方法的签名必须是唯一的。 方法签名包含方法名称、类型参数数量及其参数的数量、修饰符和类型。 方法签名不包含返回类型。

 

参数

参数用于将值或变量引用传递给方法。 方法参数从调用方法时指定的自变量中获取其实际值。 有四类参数:值参数、引用参数、输出参数和参数数组。

值参数用于传递输入自变量。 值参数对应于局部变量,从为其传递的自变量中获取初始值。 修改值参数不会影响为其传递的自变量。

可以指定默认值,从而省略相应的自变量,这样值参数就是可选的。

引用参数用于按引用传递自变量。 为引用参数传递的自变量必须是具有明确值的变量,并且在方法执行期间,引用参数指明的存储位置与自变量相同。 引用参数使用 ref 修饰符进行声明。 下面的示例展示了如何使用 ref 参数。

using System;
class RefExample
{
    static void Swap(ref int x, ref int y) 
    {
        int temp = x;
        x = y;
        y = temp;
    }
    public static void SwapExample() 
    {
        int i = 1, j = 2;
        Swap(ref i, ref j);
        Console.WriteLine($"{i} {j}");    // Outputs "2 1"
    }
}

输出参数用于按引用传递自变量。 输出参数与引用参数类似,不同之处在于,不要求向调用方提供的自变量显式赋值。 输出参数使用 out 修饰符进行声明。 下面的示例演示如何通过 C# 7 中引入的语法使用 out 参数。

    using System;
    class OutExample
    {
        static void Divide(int x, int y, out int result, out int remainder) 
        {
            result = x / y;
            remainder = x % y;
        }
        public static void OutUsage() 
        {
            Divide(10, 3, out int res, out int rem);
            Console.WriteLine("{0} {1}", res, rem);	// Outputs "3 1"
        }
    }
}

参数数组允许向方法传递数量不定的自变量。 参数数组使用 params 修饰符进行声明。 参数数组只能是方法的最后一个参数,且参数数组的类型必须是一维数组类型。 System.Console 类的 Write 和 WriteLine 方法是参数数组用法的典型示例。 它们的声明方式如下。

public class Console
{
    public static void Write(string fmt, params object[] args) { }
    public static void WriteLine(string fmt, params object[] args) { }
    // ...
}

在使用参数数组的方法中,参数数组的行为与数组类型的常规参数完全相同。 不过,在调用包含参数数组的方法时,要么可以传递参数数组类型的一个自变量,要么可以传递参数数组的元素类型的任意数量自变量。 在后一种情况中,数组实例会自动创建,并初始化为包含给定的自变量。 以下示例:

Console.WriteLine("x={0} y={1} z={2}", x, y, z);

等同于编写以下代码:

string s = "x={0} y={1} z={2}";
object[] args = new object[3];
args[0] = x;
args[1] = y;
args[2] = z;
Console.WriteLine(s, args);

 

方法主体和局部变量

方法主体指定了在调用方法时执行的语句。

方法主体可以声明特定于方法调用的变量。 此类变量称为局部变量。 局部变量声明指定了类型名称、变量名称以及可能的初始值。 下面的示例声明了初始值为零的局部变量 i 和无初始值的局部变量 j

using System;
class Squares
{
    public static void WriteSquares() 
    {
        int i = 0;
        int j;
        while (i < 10) 
        {
            j = i * i;
            Console.WriteLine($"{i} x {i} = {j}");
            i = i + 1;
        }
    }
}

C# 要求必须先明确赋值局部变量,然后才能获取其值。 例如,如果上面的 i 声明未包含初始值,那么编译器会在后面使用 i 时报告错误,因为在后面使用时 i 不会在程序中进行明确赋值。

方法可以使用 return 语句将控制权返回给调用方。 在返回 void 的方法中,return 语句无法指定表达式。 在不返回 void 的方法中,return 语句必须包括用于计算返回值的表达式。

 

静态和实例方法

使用静态修饰符声明的方法是静态方法。 静态方法不对特定的实例起作用,只能直接访问静态成员。

不使用静态修饰符声明的方法是实例方法。 实例方法对特定的实例起作用,并能够访问静态和实例成员。其中调用实例方法的实例可以作为 this 显式访问。 在静态方法中引用 this 会生成错误。

以下 Entity 类包含静态和实例成员。

class Entity
{
    static int nextSerialNo;
    int serialNo;
    public Entity() 
    {
        serialNo = nextSerialNo++;
    }
    public int GetSerialNo() 
    {
        return serialNo;
    }
    public static int GetNextSerialNo() 
    {
        return nextSerialNo;
    }
    public static void SetNextSerialNo(int value) 
    {
        nextSerialNo = value;
    }
}

每个 Entity 实例均有一个序列号(很可能包含此处未显示的其他一些信息)。 Entity 构造函数(类似于实例方法)将新实例初始化为包含下一个可用的序列号。 由于构造函数是实例成员,因此可以访问 serialNo 实例字段和 nextSerialNo 静态字段。

GetNextSerialNo 和 SetNextSerialNo 静态方法可以访问 nextSerialNo 静态字段,但如果直接访问 serialNo 实例字段,则会生成错误。

下面的示例展示了如何使用 Entity 类。

using System;
class EntityExample
{
    public static void Usage() 
    {
        Entity.SetNextSerialNo(1000);
        Entity e1 = new Entity();
        Entity e2 = new Entity();
        Console.WriteLine(e1.GetSerialNo());            // Outputs "1000"
        Console.WriteLine(e2.GetSerialNo());            // Outputs "1001"
        Console.WriteLine(Entity.GetNextSerialNo());    // Outputs "1002"
    }
}

请注意,SetNextSerialNo 和 GetNextSerialNo 静态方法是在类中调用,而 GetSerialNo 实例方法则是在类实例中调用。

 

虚方法、重写方法和抽象方法 (asdf)

如果实例方法声明中有 virtual 修饰符,可以将实例方法称为“虚方法”。 如果没有 virtual 修饰符,可以将实例方法称为“非虚方法”。

调用虚方法时,为其调用方法的实例的运行时类型决定了要调用的实际方法实现代码。 调用非虚方法时,实例的编译时类型是决定性因素。

可以在派生类中重写虚方法。 如果实例方法声明中有 override 修饰符,那么实例方法可以重写签名相同的继承虚方法。 但如果虚方法声明中引入新方法,重写方法声明通过提供相应方法的新实现代码,专门针对现有的继承虚方法。

抽象方法是没有实现代码的虚方法。 抽象方法使用 abstract 修饰符进行声明,只能在同样声明了 abstract 的类中使用。 必须在所有非抽象派生类中重写抽象方法。

下面的示例声明了一个抽象类 Expression,用于表示表达式树节点;还声明了三个派生类(ConstantVariableReference 和 Operation),用于实现常量、变量引用和算术运算的表达式树节点。 (这与表达式树类型相似,但不能与之混淆)。

using System;
using System.Collections.Generic;
public abstract class Expression
{
    public abstract double Evaluate(Dictionary<string,object> vars);
}
public class Constant: Expression
{
    double value;
    public Constant(double value) 
    {
        this.value = value;
    }
    public override double Evaluate(Dictionary<string,object> vars) 
    {
        return value;
    }
}
public class VariableReference: Expression
{
    string name;
    public VariableReference(string name) 
    {
        this.name = name;
    }
    public override double Evaluate(Dictionary<string,object> vars) 
    {
        object value = vars[name];
        if (value == null) 
        {
            throw new Exception("Unknown variable: " + name);
        }
        return Convert.ToDouble(value);
    }
}
public class Operation: Expression
{
    Expression left;
    char op;
    Expression right;
    public Operation(Expression left, char op, Expression right) 
    {
        this.left = left;
        this.op = op;
        this.right = right;
    }
    public override double Evaluate(Dictionary<string,object> vars) 
    {
        double x = left.Evaluate(vars);
        double y = right.Evaluate(vars);
        switch (op) {
            case '+': return x + y;
            case '-': return x - y;
            case '*': return x * y;
            case '/': return x / y;
        }
        throw new Exception("Unknown operator");
    }
}

上面的四个类可用于进行算术表达式建模。 例如,使用这些类的实例,可以按如下方式表示表达式 x + 3

Expression e = new Operation(
    new VariableReference("x"),
    '+',
    new Constant(3));

调用 Expression 实例的 Evaluate 方法可以计算给定的表达式并生成 double 值。 此方法需要使用自变量 Dictionary,其中包含变量名称(作为项键)和值(作为项值)。 因为 Evaluate 是一个抽象方法,因此派生自 Expression 的非抽象类必须替代 Evaluate

Constant 的 Evaluate 实现代码只返回存储的常量。 VariableReference 实现代码查找字典中的变量名称,并返回结果值。 Operation 实现代码先计算左右操作数(以递归方式调用其 Evaluate 方法),然后执行给定的算术运算。

以下程序使用 Expression 类根据不同的 x 和 y 值计算表达式 x * (y + 2)

using System;
using System.Collections.Generic;
class InheritanceExample
{
    public static void ExampleUsage() 
    {
        Expression e = new Operation(
            new VariableReference("x"),
            '*',
            new Operation(
                new VariableReference("y"),
                '+',
                new Constant(2)
            )
        );
        Dictionary<string,object> vars = new Dictionary<string, object>();
        vars["x"] = 3;
        vars["y"] = 5;
        Console.WriteLine(e.Evaluate(vars));		// Outputs "21"
        vars["x"] = 1.5;
        vars["y"] = 9;
        Console.WriteLine(e.Evaluate(vars));		// Outputs "16.5"
    }
}   

 

方法重载

借助方法重载,同一类中可以有多个同名的方法,只要这些方法具有唯一签名即可。 编译如何调用重载的方法时,编译器使用重载决策来确定要调用的特定方法。 重载决策查找与自变量最匹配的方法;如果找不到最佳匹配项,则会报告错误。 下面的示例展示了重载决策的实际工作方式。 UsageExample 方法中每个调用的注释指明了实际调用的方法。

using System;
class OverloadingExample
{
    static void F() 
    {
        Console.WriteLine("F()");
    }
    static void F(object x) 
    {
        Console.WriteLine("F(object)");
    }
    static void F(int x) 
    {
        Console.WriteLine("F(int)");
    }
    static void F(double x) 
    {
        Console.WriteLine("F(double)");
    }
    static void F<T>(T x) 
    {
        Console.WriteLine("F<T>(T)");
    }
    static void F(double x, double y) 
    {
        Console.WriteLine("F(double, double)");
    }
    public static void UsageExample()
    {
        F();            // Invokes F()
        F(1);           // Invokes F(int)
        F(1.0);         // Invokes F(double)
        F("abc");       // Invokes F<string>(string)
        F((double)1);   // Invokes F(double)
        F((object)1);   // Invokes F(object)
        F<int>(1);      // Invokes F<int>(int)
        F(1, 1);        // Invokes F(double, double)
    }
}

如示例所示,可以随时将自变量显式转换成确切的参数类型,并/或显式提供类型自变量,从而选择特定的方法。

 

其他函数成员

包含可执行代码的成员统称为类的函数成员。 上一部分介绍了作为主要函数成员类型的方法。 此部分将介绍 C# 支持的其他类型函数成员:构造函数、属性、索引器、事件、运算符和终结器。

下面的示例展示了 List 泛型类,用于实现对象的可扩充列表。 此类包含最常见类型函数成员的多个示例。

public class List<T>
{
    // Constant
    const int defaultCapacity = 4;

    // Fields
    T[] items;
    int count;

    // Constructor
    public List(int capacity = defaultCapacity) 
    {
        items = new T[capacity];
    }

    // Properties
    public int Count => count; 

    public int Capacity 
    {
        get { return items.Length; }
        set 
        {
            if (value < count) value = count;
            if (value != items.Length) 
            {
                T[] newItems = new T[value];
                Array.Copy(items, 0, newItems, 0, count);
                items = newItems;
            }
        }
    }

    // Indexer
    public T this[int index] 
    {
        get 
        {
            return items[index];
        }
        set 
        {
            items[index] = value;
            OnChanged();
        }
    }
    
    // Methods
    public void Add(T item) 
    {
        if (count == Capacity) Capacity = count * 2;
        items[count] = item;
        count++;
        OnChanged();
    }
    protected virtual void OnChanged() =>
        Changed?.Invoke(this, EventArgs.Empty);

    public override bool Equals(object other) =>
        Equals(this, other as List<T>);

    static bool Equals(List<T> a, List<T> b) 
    {
        if (Object.ReferenceEquals(a, null)) return Object.ReferenceEquals(b, null);
        if (Object.ReferenceEquals(b, null) || a.count != b.count)
            return false;
        for (int i = 0; i < a.count; i++) 
        {
            if (!object.Equals(a.items[i], b.items[i])) 
            {
                return false;
            }
        }
    return true;
    }

    // Event
    public event EventHandler Changed;

    // Operators
    public static bool operator ==(List<T> a, List<T> b) => 
        Equals(a, b);

    public static bool operator !=(List<T> a, List<T> b) => 
        !Equals(a, b);
}

 

构造函数

C# 支持实例和静态构造函数。 实例构造函数是实现初始化类实例所需执行的操作的成员。 静态构造函数是实现在首次加载类时初始化类本身所需执行的操作的成员。

构造函数的声明方式与方法一样,都没有返回类型,且与所含类同名。 如果构造函数声明包含静态修饰符,则声明的是静态构造函数。 否则,声明的是实例构造函数。

实例构造函数可以重载,并能包含可选参数。 例如,List<T> 类声明两个实例构造函数:一个没有参数,另一个需要使用 int 参数。 实例构造函数使用 new 运算符进行调用。 下面的语句使用包含和不包含可选自变量的 List 类构造函数来分配两个 List<string> 实例。

List<string> list1 = new List<string>();
List<string> list2 = new List<string>(10);

与其他成员不同,实例构造函数不能被继承,且类中只能包含实际已声明的实例构造函数。 如果没有为类提供实例构造函数,则会自动提供不含参数的空实例构造函数。

 

属性

属性是字段的自然扩展。 两者都是包含关联类型的已命名成员,用于访问字段和属性的语法也是一样的。不过,与字段不同的是,属性不指明存储位置。 相反,属性包含访问器,用于指定在读取或写入属性值时要执行的语句。

属性的声明方式与字段类似,不同之处在于,属性声明以在分隔符 { 和 } 内写入的 get 访问器和/或 set 访问器结束,而不是以分号结束。 同时包含 get 访问器和 set 访问器的属性是读写属性,仅包含 get 访问器的属性是只读属性,仅包含 set 访问器的属性是只写属性

get 访问器对应于包含属性类型的返回值的无参数方法。 如果在表达式中引用属性,除了作为赋值目标以外,调用的属性 get 访问器还可用于计算属性值。

set 访问器对应于包含一个名为 value 的参数但不含返回类型的方法。 如果将属性引用为赋值目标或 ++/-- 的操作数,将调用 set 访问器(由自变量提供新值)。

List<T> 类声明以下两个属性:Count 和 Capacity(分别是只读和只写属性)。 下面的示例展示了如何使用这些属性。

List<string> names = new List<string>();
names.Capacity = 100;   // Invokes set accessor
int i = names.Count;    // Invokes get accessor
int j = names.Capacity; // Invokes get accessor

类似于字段和方法,C# 支持实例属性和静态属性。 静态属性使用静态修饰符进行声明,而实例属性则不使用静态修饰符进行声明。

属性的访问器可以是虚的。 如果属性声明包含 virtualabstract 或 override 修饰符,则适用于属性的访问器。

 

索引器

借助索引器成员,可以将对象编入索引(像处理数组一样)。 索引器的声明方式与属性类似,不同之处在于,索引器成员名称格式为后跟分隔符 [ 和 ],其中写入参数列表。 这些参数在索引器的访问器中可用。 类似于属性,索引器分为读写、只读和只写索引器,且索引器的访问器可以是虚的。

List 类声明一个需要使用 int 参数的读写索引器。 借助索引器,可以使用 int 值将 List 实例编入索引。 例如:

List<string> names = new List<string>();
names.Add("Liz");
names.Add("Martha");
names.Add("Beth");
for (int i = 0; i < names.Count; i++) 
{
    string s = names[i];
    names[i] = s.ToUpper();
}

索引器可以进行重载。也就是说,类可以声明多个索引器,只要其参数的数量或类型不同即可。

 

事件

借助事件成员,类或对象可以提供通知。 事件的声明方式与字段类似,不同之处在于,事件声明包括事件关键字,且类型必须是委托类型。

在声明事件成员的类中,事件的行为与委托类型的字段完全相同(前提是事件不是抽象的,且不声明访问器)。 字段存储对委托的引用,委托表示已添加到事件的事件处理程序。 如果没有任何事件处理程序,则字段为 null

List<T> 类声明一个 Changed 事件成员,指明已向列表添加了新项。 Changed 事件由 OnChanged 虚方法引发,此方法会先检查事件是否是 null(即不含任何处理程序)。 引发事件的概念恰恰等同于调用由事件表示的委托,因此,没有用于引发事件的特殊语言构造。

客户端通过事件处理程序响应事件。 使用 += 和 -= 运算符分别可以附加和删除事件处理程序。 下面的示例展示了如何向 List<string> 的 Changed 事件附加事件处理程序。

class EventExample
{
    static int changeCount;
    static void ListChanged(object sender, EventArgs e) 
    {
        changeCount++;
    }
    public static void Usage() 
    {
        List<string> names = new List<string>();
        names.Changed += new EventHandler(ListChanged);
        names.Add("Liz");
        names.Add("Martha");
        names.Add("Beth");
        Console.WriteLine(changeCount);		// Outputs "3"
    }
}

对于需要控制事件的基础存储的高级方案,事件声明可以显式提供 add 和 remove 访问器,这在某种程度上与属性的 set 访问器类似。

 

运算符

运算符是定义向类实例应用特定表达式运算符的含义的成员。 可以定义三种类型的运算符:一元运算符、二元运算符和转换运算符。 所有运算符都必须声明为 public 和 static

List<T> 类声明两个运算符(operator == 和 operator !=),因此定义了向 List 实例应用这些运算符的表达式的新含义。 具体而言,这些运算符定义的是两个 List<T> 实例的相等性(使用其 Equals 方法比较所包含的每个对象)。 下面的示例展示了如何使用 == 运算符比较两个 List<int> 实例。

List<int> a = new List<int>();
a.Add(1);
a.Add(2);
List<int> b = new List<int>();
b.Add(1);
b.Add(2);
Console.WriteLine(a == b);  // Outputs "True" 
b.Add(3);
Console.WriteLine(a == b);  // Outputs "False"

第一个 Console.WriteLine 输出 True,因为两个列表包含的对象不仅数量相同,而且值和顺序也相同。如果 List<T> 未定义 operator ==,那么第一个 Console.WriteLine 会输出 False,因为 a 和 b 引用不同的 List<int> 实例。

 

终结器

终结器是实现完成类实例所需的操作的成员。 终结器既不能包含参数和可访问性修饰符,也不能进行显式调用。 实例的终结器在垃圾回收期间自动调用。

垃圾回收器在决定何时收集对象和运行终结器时有很大自由度。 具体来说,终结器的调用时间具有不确定性,可以在任意线程上执行终结器。 因为这样或那样的原因,只有在没有其他可行的解决方案时,类才能实现终结器。

处理对象析构的更好方法是使用 using 语句。

结构

结构是可以包含数据成员和函数成员的数据结构,这一点与类一样;与类不同的是,结构是值类型,无需进行堆分配。 结构类型的变量直接存储结构数据,而类类型的变量存储对动态分配的对象的引用。 结构类型不支持用户指定的继承,并且所有结构类型均隐式继承自类型 ValueType,后者又隐式继承自 object

结构对包含值语义的小型数据结构特别有用。 复数、坐标系中的点或字典中的键值对都是结构的典型示例。 对小型数据结构使用结构(而不是类)在应用程序执行的内存分配次数上存在巨大差异。 例如,以下程序创建并初始化包含 100 个点的数组。 通过将 Point 实现为类,可单独实例化 101 个对象,一个对象用于数组,其他所有对象分别用于 100 个元素。

public class PointExample
{
    public static void Main() 
    {
        Point[] points = new Point[100];
        for (int i = 0; i < 100; i++)
            points[i] = new Point(i, i);
    }
}

也可以选择将 Point 实现为结构。

struct Point
{
    public int x, y;
    public Point(int x, int y) 
    {
        this.x = x;
        this.y = y;
    }
}

现在,仅实例化一个对象(即用于数组的对象),Point 实例存储内嵌在数组中。

结构构造函数使用 new 运算符进行调用,但这不并表示要分配内存。 结构构造函数只返回结构值本身(通常在堆栈的临时位置中),并在必要时复制此值,而非动态分配对象并返回对此对象的引用。

借助类,两个变量可以引用同一对象;因此,对一个变量执行的运算可能会影响另一个变量引用的对象。借助结构,每个变量都有自己的数据副本;因此,对一个变量执行的运算不会影响另一个变量。 例如,以下代码片段生成的输出取决于 Point 是类还是结构。

Point a = new Point(10, 10);
Point b = a;
a.x = 20;
Console.WriteLine(b.x);

如果 Point 是类,则输出 20,因为 a 和 b 引用同一对象。 如果 Point 是结构,则输出 10,因为将 a 赋值给 b 创建了值副本,而此副本不受后面对 a.x 的赋值的影响。

以上示例突出显示了结构的两个限制。 首先,复制整个结构通常比复制对象引用效率更低,因此通过结构进行的赋值和值参数传递可能比通过引用类型成本更高。 其次,除 inref 和 out 参数以外,无法创建对结构的引用,这就表示在很多应用场景中都不能使用结构。

数组

数组是一种数据结构,其中包含许多通过计算索引访问的变量。 数组中的变量(亦称为数组的元素)均为同一种类型,我们将这种类型称为数组的元素类型

数组类型是引用类型,声明数组变量只是为引用数组实例预留空间。 实际的数组实例是在运行时使用 new 运算符动态创建而成。 new 运算指定了新数组实例的长度,然后在此实例的生存期内固定使用这个长度。数组元素的索引介于 0 到 Length - 1 之间。 new 运算符自动将数组元素初始化为其默认值(例如,所有数值类型的默认值为 0,所有引用类型的默认值为 null)。

以下示例创建 int 元素数组,初始化此数组,然后打印输出此数组的内容。

using System;
class ArrayExample
{
    static void Main() 
    {
        int[] a = new int[10];
        for (int i = 0; i < a.Length; i++) 
        {
            a[i] = i * i;
        }
        for (int i = 0; i < a.Length; i++) 
        {
            Console.WriteLine($"a[{i}] = {a[i]}");
        }
    }
}

上面的示例创建一维数组,并对其执行运算。 C# 还支持多维数组。 数组类型的维数(亦称为数组类型的)是 1 与数组类型方括号内的逗号数量相加的结果。 以下示例分别分配一维、二维、三维数组。

int[] a1 = new int[10];
int[,] a2 = new int[10, 5];
int[,,] a3 = new int[10, 5, 2];

a1 数组包含 10 个元素,a2 数组包含 50 个元素 (10 × 5),a3 数组包含 100 个元素 (10 × 5 × 2)。 数组的元素类型可以是任意类型(包括数组类型)。 包含数组类型元素的数组有时称为交错数组,因为元素数组的长度不必全都一样。 以下示例分配由 int 数组构成的数组:

int[][] a = new int[3][];
a[0] = new int[10];
a[1] = new int[5];
a[2] = new int[20];

第一行创建包含三个元素的数组,每个元素都是 int[] 类型,并且初始值均为 null。 后面的代码行将这三个元素初始化为引用长度不同的各个数组实例。

通过 new 运算符,可以使用数组初始值设定项(在分隔符 { 和 } 内编写的表达式列表)指定数组元素的初始值。 以下示例分配 int[],并将其初始化为包含三个元素。

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

请注意,可从 { 和 } 内的表达式数量推断出数组的长度。 局部变量和字段声明可以进一步缩短,这样就不用重新声明数组类型了。

int[] a = {1, 2, 3};

以上两个示例等同于以下示例:

int[] t = new int[3];
t[0] = 1;
t[1] = 2;
t[2] = 3;
int[] a = t;

接口

接口定义了可由类和结构实现的协定。 接口可以包含方法、属性、事件和索引器。 接口不提供所定义的成员的实现代码,仅指定必须由实现接口的类或结构提供的成员。

接口可以采用多重继承。 在以下示例中,接口 IComboBox 同时继承自 ITextBox 和 IListBox

interface IControl
{
    void Paint();
}
interface ITextBox: IControl
{
    void SetText(string text);
}
interface IListBox: IControl
{
    void SetItems(string[] items);
}
interface IComboBox: ITextBox, IListBox {}

类和结构可以实现多个接口。 在以下示例中,类 EditBox 同时实现 IControl 和 IDataBound

interface IDataBound
{
    void Bind(Binder b);
}
public class EditBox: IControl, IDataBound
{
    public void Paint() { }
    public void Bind(Binder b) { }
} 

当类或结构实现特定接口时,此类或结构的实例可以隐式转换成相应的接口类型。 例如

EditBox editBox = new EditBox();
IControl control = editBox;
IDataBound dataBound = editBox;

如果已知实例不是静态地实现特定接口,可以使用动态类型显式转换功能。 例如,以下语句使用动态类型显式转换功能来获取对象的 IControl 和 IDataBound 接口实现代码。 因为对象的运行时实际类型是 EditBox,所以显式转换会成功。

object obj = new EditBox();
IControl control = (IControl)obj;
IDataBound dataBound = (IDataBound)obj;

在前面的 EditBox 类中,IControl 接口中的 Paint 方法和 IDataBound 接口中的 Bind 方法均使用公共成员进行实现。 C# 还支持显式接口成员实现代码,这样类或结构就不会将成员设为公共成员。 显式接口成员实现代码是使用完全限定的接口成员名称进行编写。 例如,EditBox 类可以使用显式接口成员实现代码来实现 IControl.Paint 和 IDataBound.Bind 方法,如下所示。

public class EditBox: IControl, IDataBound
{
    void IControl.Paint() { }
    void IDataBound.Bind(Binder b) { }
}

显式接口成员只能通过接口类型进行访问。 例如,只有先将 EditBox 引用转换成 IControl 接口类型,才能调用上面 EditBox 类提供的 IControl.Paint 实现代码。

EditBox editBox = new EditBox();
editBox.Paint();            // Error, no such method
IControl control = editBox;
control.Paint();            // Ok

枚举

枚举类型是包含一组已命名常量的独特值类型。 需要定义包含一组离散值的类型时,可以定义枚举。 枚举使用一种整型值类型作为其基础存储, 并提供离散值的语义含义。

以下示例声明并使用名为“Color”的 enum 类型,其中包含三个常量值(RedGreen 和 Blue)。

using System;
enum Color
{
    Red,
    Green,
    Blue
}
class EnumExample
{
    static void PrintColor(Color color) 
    {
        switch (color) 
        {
            case Color.Red:
                Console.WriteLine("Red");
                break;
            case Color.Green:
                Console.WriteLine("Green");
                break;
            case Color.Blue:
                Console.WriteLine("Blue");
                break;
            default:
                Console.WriteLine("Unknown color");
                break;
        }
    }
    static void Main() 
    {
        Color c = Color.Red;
        PrintColor(c);
        PrintColor(Color.Blue);
    }
}

每个 enum 类型都有对应的整型类型(称为 enum 类型的基础类型)。 如果 enum 类型未显式声明基础类型,则基础类型为 int。 enum 类型的存储格式和可取值范围由基础类型决定。 enum 类型需要使用的一组值不受其 enum 成员限制。 尤其是,基础类型的 enum 的任何值都可以显式转换成 enum 类型,并作为 enum 类型的不同有效值。

以下示例声明基础类型为 sbyte 且名为“Alignment”的 enum 类型。

enum Alignment: sbyte
{
    Left = -1,
    Center = 0,
    Right = 1
}

如上面的示例所示,enum 成员声明可以包含用于指定成员值的常数表达式。 每个 enum 成员的常量值都必须介于 enum 的基础类型范围内。 如果 enum 成员声明未显式指定值,那么会为成员指定值 0(如果是 enum 类型中的首个成员)或原文前一个 enum 成员的值加 1。

可使用类型显式转换功能将 Enum 值转换成整型值,反之亦然。 例如:

int i = (int)Color.Blue;    // int i = 2;
Color c = (Color)2;         // Color c = Color.Blue;  

任何 enum 类型的默认值都是已转换成 enum 类型的整型值 0。 如果变量被自动初始化为默认值,这就是为 enum 类型的变量指定的值。 为了让 enum 类型的默认值可供方便使用,文本类型 0 隐式转换成任意 enum 类型。 因此,可以运行以下命令。

Color c = 0;

委托

委托类型表示对具有特定参数列表和返回类型的方法的引用。 通过委托,可以将方法视为可分配给变量并可作为参数传递的实体。 委托类似于其他一些语言中的函数指针概念,但与函数指针不同的是,委托不仅面向对象,还类型安全。

下面的示例声明并使用 Function 委托类型。

using System;
delegate double Function(double x);
class Multiplier
{
    double factor;
    public Multiplier(double factor) 
    {
        this.factor = factor;
    }
    public double Multiply(double x) 
    {
        return x * factor;
    }
}
class DelegateExample
{
    static double Square(double x) 
    {
        return x * x;
    }
    static double[] Apply(double[] a, Function f) 
    {
        double[] result = new double[a.Length];
        for (int i = 0; i < a.Length; i++) result[i] = f(a[i]);
        return result;
    }
    static void Main() 
    {
        double[] a = {0.0, 0.5, 1.0};
        double[] squares = Apply(a, Square);
        double[] sines = Apply(a, Math.Sin);
        Multiplier m = new Multiplier(2.0);
        double[] doubles =  Apply(a, m.Multiply);
    }
}

Function 委托类型实例可以引用需要使用 double 自变量并返回 double 值的方法。 Apply 方法将给定的函数应用于 double[] 的元素,从而返回包含结果的 double[]。 在 Main 方法中,Apply 用于向 double[] 应用三个不同的函数。

委托可以引用静态方法(如上面示例中的 Square 或 Math.Sin)或实例方法(如上面示例中的 m.Multiply)。 引用实例方法的委托还会引用特定对象,通过委托调用实例方法时,该对象会变成调用中的 this

还可以使用匿名函数创建委托,这些函数是便捷创建的“内联方法”。 匿名函数可以查看周围方法的局部变量。 因此,可以更轻松地编写上面的乘数示例,而无需使用 Multiplier 类:

double[] doubles =  Apply(a, (double x) => x * 2.0);

委托的一个有趣且有用的属性是,它不知道也不关心所引用的方法的类;只关心引用的方法是否具有与委托相同的参数和返回类型。

特性

C# 程序中的类型、成员和其他实体支持使用修饰符来控制其行为的某些方面。 例如,方法的可访问性是由 publicprotectedinternal 和 private 修饰符控制。 C# 整合了这种能力,以便可以将用户定义类型的声明性信息附加到程序实体,并在运行时检索此类信息。 程序通过定义和使用特性来指定此类额外的声明性信息。

以下示例声明了 HelpAttribute 特性,可将其附加到程序实体,以提供指向关联文档的链接。

using System;

public class HelpAttribute: Attribute
{
    string url;
    string topic;
    public HelpAttribute(string url) 
    {
        this.url = url;
    }

    public string Url => url;

    public string Topic {
        get { return topic; }
        set { topic = value; }
    }
}

所有特性类都派生自标准库提供的 Attribute 基类。 特性的应用方式为,在相关声明前的方括号内指定特性的名称以及任意自变量。 如果特性的名称以 Attribute 结尾,那么可以在引用特性时省略这部分名称。 例如,可按如下方法使用 HelpAttribute

[Help("https://docs.microsoft.com/dotnet/csharp/tour-of-csharp/attributes")]
public class Widget
{
    [Help("https://docs.microsoft.com/dotnet/csharp/tour-of-csharp/attributes", 
    Topic = "Display")]
    public void Display(string text) {}
}

此示例将 HelpAttribute 附加到 Widget 类。 还向此类中的 Display 方法附加了另一个 HelpAttribute。 特性类的公共构造函数控制了将特性附加到程序实体时必须提供的信息。 可以通过引用特性类的公共读写属性(如上面示例对 Topic 属性的引用),提供其他信息。

通过反射请求获得特定特性时,将调用特性类的构造函数(由程序源提供信息),并返回生成的特性实例。如果是通过属性提供其他信息,那么在特性实例返回前,这些属性会设置为给定值。

github地址: 

代码汇总_1:

using System;
using Acme.Collections;
using System.Collections;
using System.Collections.Generic;

using System.IO;
namespace cs_07
{
    class Account {
        decimal balance = 100.00M;
        private readonly object syncObj = new object();
        public void withdraw(decimal amount){
            // keep lock status while operation 
            lock(syncObj){
                if(amount > balance){
                    throw new Exception("Insufficient funds");
                }
                balance -= amount;
                Console.WriteLine($"balance is {balance}");
            }
        }
    }
    class MainClass
    {
        public static void Main(string[] args)
        {
            if(0 == 1){
                Console.WriteLine("Hello Beyond!");
                Acme.Collections.Stack s = new Acme.Collections.Stack();
                s.push(5);
                s.push(6);
                s.push(7);
                Console.WriteLine(s.Pop());
                Console.WriteLine(s.Pop());
                Console.WriteLine(s.Pop());
                Console.WriteLine(s.Pop());
            }
            if(1 == 2){
                int i = 5267;
                object o = i;
                int j = (int)o;
                    
            }

            if (2 == 3)
            {
                int a;
                int b = 6, c = 7;
                a = 5;
                Console.WriteLine(a + b + c);

            }

            if (3 == 4)
            {
                const float pi = 3.1415927f;
                const int r = 5;
                Console.WriteLine(pi * r * r);

            }

            if (4 == 5)
            {
                int i;
                i = 5267;
                Console.WriteLine(i);
                i++;
                Console.WriteLine(i);
            }


            if (5 == 6)
            {
                if(args.Length == 0){
                    Console.WriteLine("No Arguments");
                }else{
                    Console.WriteLine("Has Arguments");
                }
            }

            if (6 == 7)
            {
                switch(args.Length){
                    case 0:
                        Console.WriteLine("No Arguments");
                        break;
                    case 1:
                        Console.WriteLine("1 Arguments");
                        break;
                    default:
                        // attention please!
                        Console.WriteLine($"{args.Length} Arguments");
                        break;
                }
            }

            if (7 == 8)
            {
                int i = 0;
                while(i < args.Length){
                    Console.WriteLine(args[i]);
                    i++;
                }
            }

            if (8 == 9)
            {
                string s;
                do
                {
                    s = Console.ReadLine();
                    Console.WriteLine(s);
                } while (!string.IsNullOrEmpty(s));
                // 不是空的, 就能一直输入 
            }

            if (9 == 10)
            {
                for (int i = 0; i < args.Length; i++){
                    Console.WriteLine(args[i]);
                }
            }

            if (10 == 11)
            {
                foreach(string s in args){
                    Console.WriteLine(s);
                }
            }

            if (11 == 12)
            {
                while(true){
                    string s = Console.ReadLine();
                    if(string.IsNullOrEmpty(s)){
                        break;
                    }
                    Console.WriteLine(s);
                }
            }

            if (12 == 13)
            {
                for (int i = 0; i < args.Length; i++){
                    if(args[i].StartsWith("/")){
                        continue;
                    }
                    Console.WriteLine(args[i]);
                }
            }

            if (13 == 14)
            {
                int i = 0;
                goto checkStatement;

            loopStatement:
                    Console.WriteLine(args[i++]);
            checkStatement:
                    if(i < args.Length){
                        goto loopStatement;
                    }
            }

            if (14 == 15)
            {
                Console.WriteLine(add(6, 7));
                return;
            }

            if (15 == 16)
            {
                foreach(int i in Range(-5,5)){
                    Console.WriteLine(i);
                }
            }

            if (16 == 17)
            {
                foreach(int yieldReturnValue in Power(2,8)){
                    Console.Write("{0} ", yieldReturnValue);
                }
            }

            if (17 == 18)
            {
                try{
                    if(args.Length != 2){
                        throw new InvalidOperationException("Two numbers required");
                    }
                    // string --> double 
                    double x = double.Parse(args[0]);
                    double y = double.Parse(args[1]);
                    Console.WriteLine(divide(x, y));
                    Console.ReadKey();
                }catch (InvalidOperationException e){
                    Console.WriteLine(e.Message);
                    Console.ReadKey();
                }catch(DivideByZeroException e){
                    Console.WriteLine(e.Message);
                    Console.ReadKey();
                }finally{
                    Console.WriteLine("Bye Beyond!");
                    Console.ReadKey();
                }
            }

            if (18 == 19)
            {
                int x = int.MaxValue;
                // Console.WriteLine($"0Max Int: {x}");

                // 如果使用了unchecked则不会检查溢出,算错了也不会报错。
                unchecked{
                    Console.WriteLine($"1Max Int: {x + 1}");
                }

                // 如果使用了checked发生数学运算溢出时会抛出OverflowException;
                checked
                {
                    Console.WriteLine($"2Max Int: {x + 1}");
                }
            }

            if (19 == 20)
            {
                Account account = new Account();
                account.withdraw(40);
                account.withdraw(40);
                account.withdraw(40);
            }

            if (20 == 20)
            {
                // file exists in bin/Debug subFolder
                using (TextWriter w = File.CreateText("test.txt")){
                    w.WriteLine("Line One");
                    w.WriteLine("Line Two");
                    w.WriteLine("Line Three");
                }
            }


        }

        // asdf ===================== function definition=========
        static int add(int a, int b){
            return a + b;
        }

        static IEnumerable<int> Range(int from ,int to ){
            for (int i = from; i < to; i++){
                yield return i;
            }
            yield break;
        }

        static System.Collections.Generic.IEnumerable<int>
                            Power(int number , int exponent){
            int yieldReturnValue = 1;
            for (int i = 0; i < exponent; i++){
                yieldReturnValue = yieldReturnValue * number;
                yield return yieldReturnValue;
            }
        }

        static double divide(double x , double y){
            if(y == 0){
                throw new DivideByZeroException();
            }
            return x / y; 
        }


    }
}

代码汇总_2:

using System;
using System.Collections.Generic;

namespace cs_08
{
    class MainClass
    {
        public static void Main(string[] args)
        {
            if(0 == 1){
                Point p1 = new Point(6, 7);
                Point p2 = new Point(5, 20);
            }
            if (1 == 2)
            {
                Pair<int, string> p1 = new Pair<int, string>
                {
                    first = 1,
                    second = "Beyond"
                };
                int i = p1.first;
                string str = p1.second;
                Console.WriteLine($"{i}  {str}");
                Console.ReadKey();
            }
            if(2 == 3){
                Point3D p3 = new Point3D(5, 6, 7);
                Console.WriteLine(p3.z);
            }
            if (3 == 4)
            {
                Color c1 = new Color(66, 66, 66);
            }
            if(4 == 5){
                // attention please
                int i = 6, j = 7;
                swap(ref i, ref j);
                Console.WriteLine($"i : {i}, j: {j}");
            }
            if (5 == 6)
            {
                divide(10, 3, out int result, out int remainder);
                Console.WriteLine("{0} {1}", result, remainder);
                Console.WriteLine($"{result} {remainder}");
            }

            if (6 == 7)
            {
                int[] a = new int[3];
                a[0] = 5;
                a[1] = 6;
                a[2] = 7;
                writeLine("hello beyond", a[0],a[1],a[2]);
            }

            if (7 == 8)
            {
                int i = 0;
                int j;
                while(i < 10){
                    j = i * i;
                    Console.WriteLine($"{i} * {i} = {j}");
                    i += 1;
                }
            }

            if(8 == 9){
                Entity e1 = new Entity();
                Console.WriteLine($"{e1.GetSerialNo()} , {Entity.GetNextSerailNo()}");

                Entity e2 = new Entity();
                Console.WriteLine($"{e2.GetSerialNo()} , {Entity.GetNextSerailNo()}");

            }
            if(9 == 10){
                VariableReference left = new VariableReference("x");
                Constant right = new Constant(67);
                // polymorphic
                Expression baseEx = new Operation(left,
                                             '+',
                                             right);
                Dictionary<string, object> dict = new Dictionary<string, object>();
                dict["x"] = 52;
                Console.WriteLine(baseEx.Evaluate(dict));

            }

            if (10 == 11)
            {
                // show how to calculate        x * (y + 5)
                // attention please: parenthesis first
                VariableReference left1 = new VariableReference("y");
                char op1 = '+';
                Constant right1 = new Constant(5);
                // polymorphic
                Expression baseEx1 = new Operation(left1,
                                                   op1,
                                                   right1);


                VariableReference left2 = new VariableReference("x");
                char op2 = '*';
                Expression baseEx2 = new Operation(left2, op2, baseEx1);


                Dictionary<string, object> dict = new Dictionary<string, object>();
                // x * (y + 5)
                dict["x"] = 6;
                dict["y"] = 7;
                Console.WriteLine(baseEx2.Evaluate(dict));  

                // x * (y + 5)
                dict["x"] = 2;
                dict["y"] = 4;
                Console.WriteLine(baseEx2.Evaluate(dict));  

            }

            if(11 == 12){
                Func();
                Func(1);
                Func(1.0);
                Func("1");
                Func((double)1);
                Func((object)1);
                Func<int>(1);
                Func(1,1);
            }

            if (12 == 13)
            {
                // 索引器、事件、运算符和终结器
                List<string> list = new List<string>(1);
                list.Add("keke");
                list.Add("menma");
                list.Add("mathildar");
                Console.WriteLine(list.Capacity);

                Console.WriteLine(list[0]);
                Console.WriteLine(list[1]);
                Console.WriteLine(list[0] == list[1]);
                Console.WriteLine(list[0].Equals(list[1]));

            }

            if (13 == 14)
            {
                // 索引器、事件、运算符和终结器
                BeyondEventHandler eventHandler = new BeyondEventHandler(Boya.MeetAGirlFunction);
                // 
                eventHandler += Boya.FallInLoveFunction;

                // 触发事件
                eventHandler();
            }

            if(14 == 15){
                List<string> names = new List<string>();
                names.Capacity = 100;   // Invokes set accessor
                int i = names.LastPosition;    // Invokes get accessor
                int j = names.Capacity; // Invokes get accessor
                Console.WriteLine($"index: {i}, capacity: {j}");
            }

            if (15 == 16)
            {
                List<string> names = new List<string>();
                names.Add("Mathildar");
                names.Add("Beyond");
                names.Add("Lolita");

                for (int i = 0; i < names.LastPosition; i++){
                    string s = names[i];
                    names[i] = s.ToUpper();
                    Console.WriteLine(names[i]);
                }
            }

            if (16 == 17){
                // 使用 += 和 -= 运算符分别可以附加和删除事件处理程序
                List<string> names = new List<string>();
                // 订阅感兴趣的事件
                names.isChanged += new EventHandler(ListHasChangedListener);

                names.Add("Beyond");
                names.Add("Mathilder");
                names.Add("Lolita");

                Console.WriteLine(listChangeCount); 
            }

            if (17 == 18)
            {
                List<int> a = new List<int>();
                a.Add(6);
                a.Add(7);

                List<int> b = new List<int>();
                b.Add(6);
                b.Add(7);

                Console.WriteLine(a == b);

                b.Add(520);
                Console.WriteLine(a == b);
            }

            if(18 == 19){
                // create 101 object
                Point[] pointArr = new Point[100];
                for (int i = 0; i < 100; i++){
                    pointArr[i] = new Point(i, i);
                }
            }

            if (19 == 20)
            {
                // create 1 array object
                PointStruct[] pointArr = new PointStruct[100];
                for (int i = 0; i < 100; i++)
                {
                    pointArr[i] = new PointStruct(i, i);
                }
                Console.WriteLine(pointArr.Length);
            }

            if(20 == 21){
                int[] a = new int[10];
                for (int i = 0; i < a.Length; i++){
                    a[i] = i * i;
                }
                for (int i = 0; i < a.Length; i++){
                    Console.WriteLine($"a[{i}] = {a[i]}");
                }
            }

            if(21 == 22){
                int[] a1 = new int[10];
                int[,] a2 = new int[10, 5];
                int[,,] a3 = new int[10, 5, 2];
            }

            if (22 == 23)
            {
                // 长度不必全都一样
                int[][] a = new int[3][];
                a[0] = new int[10];
                a[1] = new int[5];
                a[2] = new int[20];
                for (int i = 0; i < a.Length; i++)
                {
                    Console.WriteLine($"a[{i}] = {a[i]}");
                }
            }

            if (23 == 24)
            {
                int[] a = new int[]{5,6,7};
                for (int i = 0; i < a.Length; i++)
                {
                    Console.WriteLine($"a[{i}] = {a[i]}");
                }
            }

            if (24 == 25)
            {
                int[] a = { 5, 6, 7 };
                for (int i = 0; i < a.Length; i++)
                {
                    Console.WriteLine($"a[{i}] = {a[i]}");
                }
            }

            if (25 == 26)
            {
                int[] tempArr = new int[3];
                tempArr[0] = 5;
                tempArr[1] = 6;
                tempArr[2] = 7;
                int[] a = tempArr;

                for (int i = 0; i < a.Length; i++)
                {
                    Console.WriteLine($"a[{i}] = {a[i]}");
                }
            }

            if (26 == 27)
            {
                EditBox editBox = new EditBox();
                IControl ctrl = editBox;
                IDataBound dbBount = editBox;

                Console.WriteLine(editBox);
                Console.WriteLine(ctrl);
                Console.WriteLine(dbBount);
            }

            if (27 == 28)
            {
                object obj = new EditBox();
                IControl ctrl = (IControl)obj;
                IDataBound dbBount = (IDataBound)obj;

                Console.WriteLine(obj);
                Console.WriteLine(ctrl);
                Console.WriteLine(dbBount);
            }

            if (28 == 29)
            {
                EditBox2 editBox = new EditBox2();
                // editBox.Paint();
                IControl ctrl = editBox;
                ctrl.Paint();
            }

            if (29 == 30)
            {
                ColorRGB c = ColorRGB.Red;
                PrintColor(c);
                PrintColor(ColorRGB.Blue);
            }

            if (30 == 31)
            {
                int i = (int)ColorRGB.Blue;
                ColorRGB c = (ColorRGB)2;
                Console.WriteLine(i); // 2
                Console.WriteLine(c); // Blue
            }

            if(31 == 32){
                // 0 特殊, 不用显示转换
                ColorRGB c = 0;
                Console.WriteLine(c); // Red
            }

            if (32 == 33)
            {
                double[] arr = { 0.0, 0.5, 1.0 };
                double[] squareArr = Apply(arr, Square);
                for (int i = 0; i < squareArr.Length; i++)
                {
                    Console.WriteLine($"squareArr[{i}] = {squareArr[i]}");
                }

                double[] sinArr = Apply(arr, Math.Sin);
                for (int i = 0; i < sinArr.Length; i++)
                {
                    Console.WriteLine($"sinArr[{i}] = {sinArr[i]}");
                }

                Multiplier multi = new Multiplier(2.0);
                double[] multiArr = Apply(arr, multi.multiplyFunc);
                for (int i = 0; i < multiArr.Length; i++)
                {
                    Console.WriteLine($"multiArr[{i}] = {multiArr[i]}");
                }
            }

            if (33 == 34)
            {
                double[] arr = { 0.0, 0.5, 1.0 };
                // 使用匿名函数创建委托
                double[] squareArr = Apply(arr, (double x) => x * 2.0);
                for (int i = 0; i < squareArr.Length; i++)
                {
                    Console.WriteLine($"squareArr[{i}] = {squareArr[i]}");
                }
            }

            if (34 == 34)
            {
                
            }

        }

        // ==========other functions============
        static void swap(ref int x, ref int y)
        {
            int temp = x;
            x = y;
            y = temp;
        }

        static void divide(int x, int y, out int result , out int remainder){
            result = x / y;
            remainder = x % y;
        }

        static void writeLine(string str, params object[] args){
            Console.WriteLine($"Length is : {args.Length}, the last number is : {args[2]}");
        }

        static void Func(){
            Console.WriteLine("F()");
        }
        static void Func(object o)
        {
            Console.WriteLine("F(object)");
        }

        static void Func(int i)
        {
            Console.WriteLine("F(int)");
        }

        static void Func(double d)
        {
            Console.WriteLine("F(double)");
        }

        static void Func<T>(T t)
        {
            Console.WriteLine("F<T>(T)");
        }

        static void Func(double d1, double d2)
        {
            Console.WriteLine("F(double,double)");
        }

        // ========================

        public delegate void BeyondEventHandler();

        // ========================

        static int listChangeCount;
        static void ListHasChangedListener(object sender, EventArgs e){
            listChangeCount++;
            Console.WriteLine($"监听: {listChangeCount}");
        }

        // ========================

        static void PrintColor(ColorRGB color){
            switch(color){
                case ColorRGB.Red:
                    Console.WriteLine("this is Red Color");
                    break;
                case ColorRGB.Green:
                    Console.WriteLine("this is Green Color");
                    break;
                case ColorRGB.Blue:
                    Console.WriteLine("this is Blue Color");
                    break;
                default:
                    Console.WriteLine("this is Unkown Color");
                    break;
            }
        }

        // ========================

        static double Square(double x){
            return x * x;
        }
        static double[] Apply(double[] arr, Function func){
            double[] result = new double[arr.Length];
            for (int i = 0; i < arr.Length; i++){
                result[i] = func(arr[i]);
            }
            return result;
        }

    }

    // ===========other classes===========

    class Point {
        public int x, y;
        public Point(int x, int y){
            this.x = x;
            this.y = y;
        }
    }

    class Pair<TFirst, TSecond>{
        public TFirst first;
        public TSecond second;
    }

    class Point3D: Point {
        public int z;
        public Point3D(int x,int y,int z ) : base(x,y)
        {
            this.z = z;
        }
    }
    // ===============================
    class Color{
        public static readonly Color Black = new Color(0, 0, 0);
        public static readonly Color White = new Color(255, 255, 255);
        public static readonly Color Red = new Color(255, 0, 0);
        public static readonly Color Green = new Color(0, 255, 0);
        public static readonly Color Blue = new Color(0, 0, 255);
        private byte r, g, b;
        public Color(byte r, byte g, byte b){
            this.r = r;
            this.g = g;
            this.b = b;
        }
    }
    // ===============================
    class Entity{
        static int nextSerialNo;
        int serailNo;
        public Entity(){
            this.serailNo = nextSerialNo++;
        }            
        public int GetSerialNo(){
            return this.serailNo;
        }
        public static int GetNextSerailNo(){
            return nextSerialNo;
        }
        public static void SetNextSerialNo(int val){
            Entity.nextSerialNo = val;
        }

    }
    // ===============================
    abstract class Expression{
        public abstract double Evaluate(Dictionary<string, object> dict);
    }
    class Constant: Expression{
        double value;
        public Constant(double value){
            this.value = value;
        }
        public override double Evaluate(Dictionary<string, object> dict)
        {
            return this.value;
        }
    }
    class VariableReference: Expression {
        string name;
        public VariableReference(string name){
            this.name = name;
        }
        public override double Evaluate(Dictionary<string, object> dict)
        {
            object obj = dict[name];
            if(obj == null){
                throw new Exception("UnKnow Variabel: " + name);
            }
            return Convert.ToDouble(obj);
        }
    }
    class Operation: Expression{
        Expression left;
        char op;
        Expression right;
        public Operation(Expression left, char op , Expression right){
            this.left = left;
            this.op = op;
            this.right = right;
        }
        public override double Evaluate(Dictionary<string, object> dict)
        {
            double x = left.Evaluate(dict);
            double y = right.Evaluate(dict);
            switch(op){
                case '+': return x + y;
                case '-': return x - y;
                case '*': return x * y;
                case '/': return x / y;    
            }
            throw new Exception("UnKnow operator");
        }
    }
    // ===============================
    class List<T>
    {
        // const int defaultCapacity = 4;

        // Fields
        T[] items;
        int lastPosition;   

        public List(int capacity = 2)
        {
            items = new T[capacity];
        }

        // Properties
        public int LastPosition => lastPosition;
        // what ?
        public int Capacity
        {
            get
            {
                return items.Length;
            }
            // value なに?
            set
            {
                if (value < this.lastPosition)
                {
                    value = this.lastPosition;
                }
                // copy to large new array
                if (value != items.Length)
                {
                    T[] newItems = new T[value];
                    Array.Copy(items, 0, newItems, 0, this.lastPosition);
                    items = newItems;
                }
            }
        }

        // what ??
        public T this[int lastPosition]
        {
            get
            {
                return this.items[lastPosition];
            }
            set
            {
                this.items[lastPosition] = value;
                OnChangedHandler();
            }
        }

        // methods
        public void Add(T item)
        {
            if (lastPosition == Capacity)
            {
                Capacity = lastPosition * 2;
            }
            items[lastPosition] = item;
            lastPosition++;
            OnChangedHandler();
        }

        // event
        public event EventHandler isChanged;

        // Operators
        public static bool operator ==(List<T> a, List<T> b) =>
            Equals(a, b);
        public static bool operator !=(List<T> a, List<T> b) =>
            !Equals(a, b);


        // what ???
        protected virtual void OnChangedHandler() =>
             isChanged?.Invoke(this, EventArgs.Empty);
    

        public override bool Equals(Object other) =>
            Equals(this, other as List<T>);

        // what ???
        static bool Equals(List<T>a, List<T>b){
            if(Object.ReferenceEquals(a,null)){
                return Object.ReferenceEquals(b, null);
            }

            // a is not null
            if(Object.ReferenceEquals(b,null) || a.lastPosition != b.lastPosition){
                return false;
            }

            // a,b is not null  
            // a.count == b.count
            for (int i = 0; i < a.lastPosition; i++){
                if(!object.Equals(a.items[i],b.items[i])){
                    return false;
                }
            }
            return true;
        }

    }

    // ===============================
    class Boya
    {
        public static void MeetAGirlFunction()
        {
            Console.WriteLine("boya meet a girl");
        }
        public static void FallInLoveFunction()
        {
            Console.WriteLine("boya fall in love");
        }
    }


    // ========= other struct =======================
    struct PointStruct {
        public int x, y;
        public PointStruct(int x, int y){
            this.x = x;
            this.y = y;
        }
    }


    // ========= other interface =======================
    interface IControl {
        void Paint();
    }
    interface ITextBox: IControl{
        void SetText(string text);
    }
    interface IListBox: IControl{
        void SetItems(string[] items);
    }
    interface IComboBox: ITextBox, IListBox{}
    interface IDataBound{
        void Bind(Binder b);
    }
    class EditBox: IControl, IDataBound{
        public void Paint(){
            
        }
        public void Bind(Binder b){
            
        }
    }

    class EditBox2: IControl, IDataBound{
        // 显式接口成员实现代码 and 不会将成员设为公共成员
        void IControl.Paint(){
            Console.WriteLine("EditBox2 Paint Function");
        }
        void IDataBound.Bind(Binder b){}
    }

    // 手动打补丁 写一个Binder类
    class Binder{
        
    }

    // ========= other enum =======================
    enum ColorRGB{
        Red,
        Green,
        Blue
    }

    enum Alignment : sbyte
    {
        Left = -1,
        Center = 0,
        Right = 1
    }

    // ========= other class =======================
    class Multiplier{
        double factor;
        public Multiplier(double factor){
            this.factor = factor;
        }
        public double multiplyFunc(double x){
            return x * factor;
        }
    }

    delegate double Function(double x);

    // ========= other Attribute =======================
    class BeyondAttribute: Attribute{
        string url;
        string topic;
        public BeyondAttribute(string url){
            this.url = url;
        }
        public string Url => url;
        public string Topic{
            get{
                return topic;
            }
            set{
                topic = value;
            }
        }
    }

    [Beyond("https://docs.microsoft.com/dotnet/csharp/tour-of-csharp/attributes")]
    class Widget{
        [Beyond("https://docs.microsoft.com/dotnet/csharp/tour-of-csharp/attributes",
                Topic ="Display")]
        public void Display(string text){
            
        }
    }

}

未完待续,下一章节,つづく

猜你喜欢

转载自blog.csdn.net/u012576807/article/details/81292480