C#语言规范之一:介绍

2021/11/17 1:39:43

本文主要是介绍C#语言规范之一:介绍,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

1.1 概念

C# 是面向对象的语言,然而C# 进一步提供了对面向组件(component-oriented) 编程的支持。

现代软件设计日益依赖于自包含和自描述功能包形式的软件组件。

这种组件的关键在于,它们通过属性、方法和事件来提供编程模型;它们具有提供了关于组件的声明性信息的特性;同时,它们还编入了自己的文档。

C# 提供的语言构造直接支持这些概念,这使得C# 语言自然而然成为创建和使用软件组件之选。

C# 具有一个同一类型系统(unified type system)。所有C# 类型(包括诸如int 和double 之类的基元类型)都继承于单个根类型:object。

因此,所有类型都共享一组通用操作,并且任何类型的值都能够以一致的方式进行存储、传递和操作。此外,C# 同时支持用户定义的引用类型和值类型,既允许对象的动态分配,也允许轻量结构的内联存储。

默认情况下,Microsoft C# 编译器自动引用 System 类库。注意,C# 语言本身没有单独的运行库。事
实上,.NET Framework 就是C# 运行库。

1.2 程序结构

C# 中的组织结构的关键概念是程序(program)命名空间(namespace)类型(type)成员(member) 程序集(assembly)

  • C# 程序由一个或多个源文件组成。
  • 程序中声明类型,类型包含成员,并且可按命名空间进行组织。
    • 类和接口就是类型的示例。
    • 字段(field)、方法、属性和事件是成员的示例。

在编译C# 程序时,它们被物理地打包为程序集。程序集通常具有文件扩展名.exe 或.dll,具体取决于它们是实现应用程序(application) 还是实现库(library)。

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

在执行程序集之前,.NET 公共语言运行时的实时(JIT) 编译器将程序集中的 IL 代码自动转换为特定于处理器的代码。

由于程序集是一个自描述的功能单元,它既包含代码又包含元数据,因此,C# 中不需要#include 指令和头文件。若要在C# 程序中使用某特定程序集中包含的公共类型和成员,只需在编译程序时引用该程序集即可。c

C# 允许将一个程序的源文本存储在多个源文件中。
在编译多个文件组成的C# 程序时,所有源文件将一起处理,并且源文件可以自由地相互引用— 从概念上讲,就像是在处理之前将所有源文件合并为一个大文件。
C# 中从不需要前向声明,因为除了极少数的例外情况,声明顺序无关紧要。C# 不限制一个源文件只能声明一个公共类型,也不要求源文件的名称与该源文件中声明的类型匹配。

1.3 类型和变量

C# 中的类型有两种:值类型(value type) 和引用类型(reference type)。

  • 值类型的变量直接包含它们的数据,而引用类型的变量存储对它们的数据的引用,后者称为对象。
  • 对于引用类型,两个变量可能引用同一个对象,因此对一个变量的操作可能影响另一个变量所引用的对象。
  • 对于值类型,每个变量都有它们自己的数据副本(除ref 和out 参数变量外),因此对一个变量的操作不可能影响另一个变量。

C# 的值类型进一步划分为简单类型(simple type)、枚举类型(enum type)、结构类型(struct type) 和可以为 null 的类型(nullable type)

C# 的引用类型进一步划分为类类型(class type)、接口类型(interface type)、数组类型(array type) 和委托类型(delegate type)。

值类型:

  • 简单类型
    • 有符号类型:sbyte、short、int、long
    • 无符号整型:byte、ushort、uint、ulong
    • Unicode:char
    • IEEE浮点:float、double
    • 高精度小数型: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 位双精度的IEEE 754 格式表示。

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

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

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

类类型定义了一个包含数据成员(字段)函数成员(方法、属性等)的数据结构。类类型支持单一继承和多态,这些是派生类可用来扩展和专用化基类的机制。

结构类型与类类型相似,表示一个带有数据成员和函数成员的结构。但是,与类不同,结构是一种值类型,并且不需要堆分配。结构类型不支持用户指定的继承,并且所有结构类型都隐式地从类型object继承。

接口类型定义了一个协定,作为一个公共函数成员的命名集。实现某个接口的类或结构必须提供该接口的函数成员的实现。一个接口可以从多个基接口继承,而一个类或结构可以实现多个接口。

委托类型表示对具有特定参数列表和返回类型的方法的引用。通过委托,我们能够将方法作为实体赋值
给变量和作为参数传递。委托类似于在其他某些语言中的函数指针的概念,但是与函数指针不同,委托
是面向对象的,并且是类型安全的。

类类型、结构类型、接口类型和委托类型都支持泛型,因此可以通过其他类型将其参数化。

枚举类型是具有命名常量的独特的类型。每种枚举类型都具有一个基础类型,该基础类型必须是八种整
型之一。枚举类型的值集和它的基础类型的值集相同。

  • C# 支持由任何类型组成的一维和多维数组。
  • 可以为null 的类型也不必声明就可以使用。对于每个不可以为null 的值类型T,都有一个相应的可以为null 的类型T?,该类型可以容纳附加值null。
  • C# 的类型系统是统一的,因此任何类型的值都可以按对象处理。C# 中的每个类型直接或间接地从object 类类型派生,而object 是所有类型的最终基类。引用类型的值都被视为object 类型,被简单地当作对象来处理。值类型的值则通过对其执行装箱和拆箱操作按对象处理。

C# 的统一类型系统实际上意味着值类型可以“按需”转换为对象。因为统一,所以使用类型object的通用库可以与引用类型和值类型一同使用。

1.4 表达式

表达式由操作数(operand) 和运算符(operator) 构成。表达式的运算符指示对操作数适用什么样的运算。

运算符的示例包括 +、-、*、/ 和new。操作数的示例包括文本、字段、局部变量和表达式。

当表达式包含多个运算符时,运算符的优先级(precedence) 控制各运算符的计算顺序。

大多数运算符都可以重载(overload)。运算符重载允许指定用户定义的运算符实现来执行运算,这些运算的操作数中至少有一个,甚至所有操作数都属于用户定义的类类型或结构类型。

1.5 语句

程序的操作是使用语句(statement) 来表示的。C# 支持几种不同的语句,其中许多以嵌入语句的形式定
义。

block 用于在只允许使用单个语句的上下文中编写多条语句。块由位于一对大括号{ 和} 之间的语句列
表组成。

声明语句(declaration statement) 用于声明局部变量和常量。

  1. 表达式语句(expression statement) 用于对表达式求值。可用作语句的表达式包括方法调用、使用new运算符的对象分配、使用= 和复合赋值运算符的赋值、使用++ 和-- 运算符的增量和减量运算以及await表达式。

  2. 选择语句(selection statement) 用于根据表达式的值从若干个给定的语句中选择一个来执行。这一组中有if 和switch 语句。

  3. 迭代语句(iteration statement) 用于重复执行嵌入语句。这一组中有while、do、for 和foreach 语句。

  4. 跳转语句(jump statement) 用于转移控制。这一组中有break、continue、goto、throw、return 和yield 语句。

  5. try...catch 语句用于捕获在块的执行期间发生的异常,try...finally 语句用于指定终止代码,不管是否发生异常,该代码都始终要执行。

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

  7. lock 语句用于获取某个给定对象的互斥锁,执行一个语句,然后释放该锁。

  8. using 语句用于获得一个资源,执行一个语句,然后释放该资源。

1.6 类和对象

  • 类(class) 是最基础的C# 类型。
  • 类是一个数据结构,将状态(字段)和操作(方法和其他函数成员)组合在一个单元中。
  • 类为动态创建的类实例(instance) 提供了定义,实例也称为对象(object)。
  • 类支持继承(inheritance) 和多态性(polymorphism),这是派生类(derived class) 可用来扩展和专用化基类(base class)的机制。

1.6.1 成员

类的成员或者是静态成员(static member),或者是实例成员(instance member)。
静态成员属于类,实例成员属于对象(类的实例)。

类所能包含的成员种类:

成员 说明
常量 与类关联的常量值
字段 类的变量
方法 类可执行的计算和操作
属性 与读写类的命名属性相关联的操作
索引器 与以数组方式索引类的实例相关联的操作
事件 可由类生成的通知
运算符 类所支持的转换和表达式运算符
构造函数 初始化类的实例或类本身所需的操作
析构函数 在永久丢弃类的实例之前执行的操作
类型 类所声明的嵌套类型

1.6.2 可访问性

类的每个成员都有关联的可访问性,它控制能够访问该成员的程序文本区域。有五种可能的可访问性形
式。

可访问性 含义
public 访问不受限制
protected 访问仅限于此类或从此类派生的类
internal 访问仅限于此程序
protected internal 访问仅限于此程序或从此类派生的类
private 访问仅限于此类

1.6.3 类型形参

类定义可以通过在类名后添加用尖括号括起来的类型参数名称列表来指定一组类型参数。类型参数可用于在类声明体中定义类的成员。

要声明为采用类型参数的类类型称为泛型类类型。结构类型、接口类型和委托类型也可以是泛型。
当使用泛型类时,必须为每个类型参数提供类型实参。

1.6.4 基类

类声明可通过在类名和类型参数后面添加一个冒号和基类的名称来指定一个基类。省略基类的指定等同于从类型object 派生。

类继承其基类的成员。继承意味着一个类隐式地将它的基类的所有成员当作自已的成员,但基类的实例构造函数、静态构造函数和析构函数除外。派生类能够在继承基类的基础上添加新的成员,但是它不能移除继承成员的定义。

从某个类类型到它的任何基类类型存在隐式的转换。因此,类类型的变量可以引用该类的实例或任何派生类的实例。

1.6.5 字段

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

使用static 修饰符声明的字段定义了一个静态字段(static field)。一个静态字段只标识一个存储位置。无论对一个类创建多少个实例,它的静态字段永远都只有一个副本。

不使用static 修饰符声明的字段定义了一个实例字段(instance field)。类的每个实例都为该类的所有实例字段包含一个单独副本。

1.6.6 方法

方法(method) 是一种成员,用于实现可由对象或类执行的计算或操作。

静态方法(static method) 通过类来访问。实例方法(instance method) 通过类的实例来访问。

方法具有一个参数(parameter) 列表(可以为空),表示传递给该方法的值或变量引用;
方法还具有一个返回类型(return type),指定该方法计算和返回的值的类型。如果方法不返回值,则其返回类型为void。
与类型一样,方法也可以有一组类型参数,当调用方法时必须为类型参数指定类型实参。
与类型不同的是,类型实参经常可以从方法调用的实参推断出,而无需显式指定。

方法的签名(signature) 在声明该方法的类中必须唯一。
方法的签名由方法的名称、类型参数的数目以及该方法的参数的数目、修饰符和类型组成。方法的签名不包含返回类型。

1.6.6.1 参数

参数用于向方法传递值或变量引用。

方法的参数从调用该方法时指定的实参(argument) 获取它们的实际值。

有四类参数:值参数、引用参数、输出参数和参数数组。

  • 值参数(value parameter) 用于传递输入参数。一个值参数相当于一个局部变量,只是它的初始值来自为该形参传递的实参。对值参数的修改不影响为该形参传递的实参。值参数可以是可选的,通过指定默认值可以省略对应的实参。
  • 引用参数(reference parameter) 用于传递输入和输出参数。为引用参数传递的实参必须是变量,并且在方法执行期间,引用参数与实参变量表示同一存储位置。引用参数使用ref 修饰符声明。
  • 输出参数(output parameter) 用于传递输出参数。对于输出参数来说,调用方提供的实参的初始值并不重要。除此之外,输出参数与引用参数类似。输出参数是用out 修饰符声明的。
  • 参数数组(parameter array) 允许向方法传递可变数量的实参。参数数组使用params 修饰符声明。只有方法的最后一个参数才可以是参数数组,并且参数数组的类型必须是一维数组类型。

1.6.6.2 方法体和局部变量

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

方法体可以声明仅用在该方法调用中的变量。这样的变量称为局部变量(local variable)。局部变量声明指定了类型名称、变量名称,还可指定初始值。

1.6.6.3 静态方法和实例方法

使用static 修饰符声明的方法为静态方法(static method)。静态方法不对特定实例进行操作,并且只能直接访问静态成员。

不使用static 修饰符声明的方法为实例方法(instance method)。实例方法对特定实例进行操作,并且能够访问静态成员和实例成员。

在调用实例方法的实例上,可以通过this 显式地访问该实例。而在静态方法中引用this 是错误的。

1.6.6.4 虚方法、重写方法和抽象方法

若一个实例方法的声明中含有virtual 修饰符,则称该方法为虚方法。若其中没有virtual 修饰符,
则称该方法为非虚方法。

在调用一个虚方法时,该调用所涉及的实例的运行时类型(runtime type) 确定了要实际调用的方法实现。

在非虚方法调用中,实例的编译时类型(compile-time type) 负责做出此决定。

虚方法可以在派生类中重写(override)。当某个实例方法声明包括override 修饰符时,该方法将重写所继承的具有相同签名的虚方法。虚方法声明用于引入新方法,而重写方法声明则用于使现有的继承虚方
法专用化(通过提供该方法的新实现)。

抽象(abstract) 方法是没有实现的虚方法。抽象方法使用abstract 修饰符进行声明,并且只允许出现在同样被声明为abstract 的类中。抽象方法必须在每个非抽象派生类中重写。

1.6.6.5 方法重载

方法重载(overloading) 允许同一类中的多个方法具有相同名称,条件是这些方法具有唯一的签名。

在编译一个重载方法的调用时,编译器使用重载决策(overload resolution) 确定要调用的特定方法。

重载决策将查找与参数最佳匹配的方法,如果没有找到任何最佳匹配的方法则报告错误信息。

1.6.7 其他函数成员

包含可执行代码的成员统称为类的函数成员(function member)。

C# 支持的其他类型的函数成员:构造函数、属性、索引器、事件、运算符和析构函数。

1.6.7.1 构造函数

C# 支持两种构造函数:实例构造函数和静态构造函数。
实例构造函数(instance constructor) 是实现初始化类实例所需操作的成员。
静态构造函数(static constructor) 是一种用于在第一次加载类本身时实现其初始化所需操作的成员。

构造函数的声明如同方法一样,不过它没有返回类型,并且它的名称与其所属的类的名称相同。
如果构造函数声明包含static 修饰符,则它声明了一个静态构造函数。否则,它声明的是一个实例构造函数。

实例构造函数可以被重载。

实例构造函数不同于其他成员,它是不能被继承的。

一个类除了其中实际声明的实例构造函数外,没有其他的实例构造函数。如果没有为某个类提供任何实例构造函数,则将自动提供一个不带参数的空的实例构造函数。

1.6.7.2 属性

属性(property) 是字段的自然扩展。

属性和字段都是命名的成员,都具有相关的类型,且用于访问字段和属性的语法也相同。然而,与字段不同,属性不表示存储位置。相反,属性有访问器(accessor),这些访问器指定在它们的值被读取或写入时需执行的语句。

属性的声明与字段类似,不同的是属性声明以位于定界符{ 和} 之间的一个get 访问器和/或一个set访问器结束,而不是以分号结束。

同时具有get 访问器和set 访问器的属性是读写属性(read-writeproperty)
只有get 访问器的属性是只读属性(read-only property)
只有set 访问器的属性是只写属性(write-only property)。

get 访问器相当于一个具有属性类型返回值的无形参方法。除了作为赋值的目标,当在表达式中引用属性时,将调用该属性的get 访问器以计算该属性的值。

set 访问器相当于具有一个名为value 的参数并且没有返回类型的方法。当某个属性作为赋值的目标被引用,或者作为++ 或-- 的操作数被引用时,将调用set 访问器,并传入提供新值的实参。

属性的访问器可以是虚的。当属性声明包括virtual、abstract 或override 修饰符时,修饰符应用于该属性的访问器。

1.6.7.3 索引器

索引器(indexer) 是这样一个成员:它支持按照索引数组的方法来索引对象。

索引器的声明与属性类似,不同的是该成员的名称是this,后跟一个位于定界符[和]之间的参数列表。在索引器的访问器中可以使用这些参数。与属性类似,索引器可以是读写、只读和只写的,并且索引器的访问器可以是虚的。

1.6.7.4 事件

事件(event) 是一种使类或对象能够提供通知的成员。

事件的声明与字段类似,不同的是事件的声明包含event 关键字,并且类型必须是委托类型。

在声明事件成员的类中,事件的行为就像委托类型的字段(前提是该事件不是抽象的并且未声明访问器)。

该字段存储对一个委托的引用,该委托表示已添加到该事件的事件处理程序。如果尚未添加事件处理程序,则该字段为null。

1.6.7.5 运算符

运算符(operator) 是一种类成员,它定义了可应用于类实例的特定表达式运算符的含义。

可以定义三类运算符:一元运算符、二元运算符和转换运算符。所有运算符都必须声明为public 和static。

1.6.7.6 析构函数

析构函数(destructor) 是一种用于实现销毁类实例所需操作的成员。
析构函数不能带参数,不能具有可访问性修饰符,也不能被显式调用。垃圾回收期间会自动调用所涉及实例的析构函数。

垃圾回收器在决定何时回收对象和运行析构函数方面允许有广泛的自由度。

具体而言,析构函数调用的时机并不是确定的,析构函数可以在任何线程上执行。由于这些以及其他原因,仅当没有其他可行的解决方案时,才应在类中实现析构函数。

using 语句提供了更好的对象析构方法。

1.7 结构

像类一样,结构(struct) 是能够包含数据成员和函数成员的数据结构。
但是与类不同,结构是值类型,不需要堆分配。结构类型的变量直接存储该结构的数据,而类类型的变量则存储对动态分配的对象的引用。

结构类型不支持用户指定的继承,并且所有结构类型都隐式地从类型object 继承。

结构对于具有值语义的小型数据结构尤为有用。复数、坐标系中的点或字典中的“键-值”对都是结构的典型示例。

对小型数据结构而言,使用结构而不使用类会大大节省需要为应用程序分配的内存数量。

结构构造函数也是使用new 运算符调用,但是这并不意味着会分配内存。

结构构造函数并不动态分配对象并返回对它的引用,而是直接返回结构值本身(通常是堆栈上的一个临时位置),然后根据需要复制该结构值。

对于类,两个变量可能引用同一对象,因此对一个变量进行的操作可能影响另一个变量所引用的对象。

对于结构,每个变量都有自己的数据副本,对一个变量的操作不会影响另一个变量。

1.8 数组

数组(array) 是一种包含若干变量的数据结构,这些变量都可以通过计算索引进行访问。

数组中包含的变量(又称数组的元素)具有相同的类型,该类型称为数组的元素类型。

数组类型为引用类型,因此数组变量的声明只是为数组实例的引用留出空间。

实际的数组实例在运行时使用new 运算符动态创建。
new 运算符指定新数组实例的长度(length),它在该实例的生存期内是固定不变的。数组元素的索引范围从0 到Length - 1。
new 运算符自动将数组的元素初始化为它们的默认值

例如将所有数值类型初始化为零,将所有引用类型初始化为null

1.9 接口

接口(interface) 定义了一个可由类和结构实现的协定。

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

接口可支持多重继承。

当类或结构实现某个特定接口时,该类或结构的实例可以隐式地转换为该接口类型。

在无法静态知道某个实例是否实现某个特定接口的情况下,可以使用动态类型强制转换。x

1.10 枚举

枚举类型(enum type) 是具有一组命名常量的独特的值类型。

每个枚举类型都有一个相应的整型类型,称为该枚举类型的基础类型(underlying type)。

没有显式声明基础类型的枚举类型所对应的基础类型是int。

枚举类型的存储格式和取值范围由其基础类型确定。一个枚举类型的值域不受它的枚举成员限制。具体而言,一个枚举的基础类型的任何一个值都可以被强制转换为该枚举类型,成为该枚举类型的一个独特的有效值。

枚举成员的声明中包含常量表达式,用于指定该成员的值。每个枚举成员的常数值必须在该枚举的基础类型的范围之内。如果枚举成员声明未显式指定一个值,该成员将被赋予值零(如果它是该枚举类型中的第一个值)或前一个枚举成员(按照文本顺序)的值加1。

任何枚举类型的默认值都是转换为该枚举类型的整型值零。在变量被自动初始化为默认值的情况下,该默认值就是赋予枚举类型的变量的值。为了便于获得枚举类型的默认值,文本0 隐式地转换为任何枚举类型

1.11 委托

委托类型(delegate type) 表示对具有特定参数列表和返回类型的方法的引用。

通过委托,我们能够将方法作为实体赋值给变量和作为参数传递。委托类似于在其他某些语言中的函数指针的概念,但是与函数指针不同,委托是面向对象的,并且是类型安全的。

1.12 特性

C# 程序中的类型、成员和其他实体都支持修饰符,这些修饰符控制它们的行为的某些方面。

例如,方法的可访问性是使用public、protected、internal 和private 修饰符来控制的。

C# 使此功能一般化,以便能够将用户定义类型的声明信息附加到程序实体,并在运行时检索。这种附加的声明信息是程序通过定义和使用特性(attribute) 来指定的。

所有特性类都从.NET Framework 提供的System.Attribute 基类派生而来。

可以通过在相关声明之前紧邻的方括号内提供特性名和任何实参来应用特性。
如果特性的名称以Attribute 结尾,在引用该特性时可以省略此名称后缀。

特性类的公共构造函数控制在将特性附加到程序实体时,必须提供的信息。可以通过引用特性类的公共读写属性提供附加信息。

当通过反射请求特定特性时,将使用程序源中提供的信息调用特性类的构造函数,并返回生成的特性实例。如果通过属性提供了附加信息,那些属性将在返回特性实例之前被设置为给定的值。



这篇关于C#语言规范之一:介绍的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程