一、.NET Framework和C#
1.1. .NET Framework
.NET Framework具有以下功能:
- 提供了一个运行环境,以简化软件开发过程,降低版本冲突的可能性
- 确保代码的安全执行
- 基于行业标准进行所有通信,从而能够与非.NET代码集成
- 让应用程序的开发过程独立于语言与平台,确保各种应用程序的开发体验都是一致的
- 提供了一个运行环境,尽可能消除了脚本语言或解释性语言的性能问题
- .NET Framework包含组件
- Common Language Runtime:可视为NET Framework的核心,提供了低级核心服务供应用程序使用,并负责管理应用程序的代码。针对.NET Framework编写的代码成为托管代码,而其他代码成为非托管代码
- 类库:包含大量可重用的类(类型),可用于开发能够想象得到的任何应用程序(Framework类库包含4000多个公共类,是当今最大的类库之一)
- 并行计算平台:能够以自然而简单的方式编写高效的可扩展代码,以充分利用多个处理器
- DLR(动态语言运行时):建立在CLR的基础上,向诸如IronRuby和IronPython等动态语言提供语言服务。与诸如C#等通用语言相比,动态语言更擅长执行某些任务,而动态语言运行时让您能够根据业务需求选择最合适的语言。不仅如此,动态语言运行时还让非动态语言(如C#)能够以简单而一致的语法使用动态对象,而不管这些对象源自COM、IronRuby、IronPython还是JavaScript。
ps:动态语言:在诸如 C#等使用静态类型的语言中,由编译器确定类型安全,如果无法确定,就将生成错误。在动态语言中,不会尝试确定类型安全。另外,大多数动态语言在运行阶段执行更复杂的类型操作,如判断方法重载是否正确,而C#在编译阶段做出这种判断。
1.3.关于.NET Framework
针对.NET Framework编写的代码称为托管代码,而根据这些代码生成的应用程序称为托管应用程序。托管应用程序运行时,将自动承载( host )相应的公共语言运行时。.NET Framework 不仅提供了大量的运行时宿主,还提供了能够编写宿主的工具。这样,诸如Internet信息服务(IIS)和Microsoft SQL Server等非托管应用程序也可承载公共语言运行时备份,从而能够利用托管功能和非托管功能。
该图说明了.NET Framework的各个组件与应用程序、操作系统和非托管应用程序之间的关系。
1.4.CLR
CLR是.NET Framework的核心,提供了一个统一类型系统和一个托管运行环境,为开发和执行独立语言和平台的应用程序打下了基础,还有助于消除(至少是减少)众多常见的BUG
1.4.1 通用类型系统
Common Type System,CTS让所有.NET语言共享相同的类型定义,这样便可以一致的方式操作这些类型,有助于确保正确地编写应用程序
原因如下:
- 消除了将不兼容的数据赋给类型的可能性
- 每种.NET语言的类型描述都相同,而不管用于定义该类型的语言是什么
- 以一致的方式操作类型
ps:类型安全和CTS
通用类型系统和公共语言规范为.NET Framework类型安全打下了坚实的基础。
这让.NET Framework能够以一致的方式倡导类型安全,而无需强制执行类型安全。强制执行类型安全的任务留给了语言编译器和稍后将介绍的虚拟执行系统。
通用语言系统以独立于语言的方式定义类型,因此它必须考虑这些语言之间的差别。通用语言系统提供了一个最基本的规则集,所有.NET语言及其编译器都必须遵守,这个规则集称为公共语言规范(Common Language Specification,CLS)。这种通用定义让语言集成成为可能:使用另一种语言中定义的类型,就像它是在当前语言中定义的一样。
ps:CLS遵从性
几乎Framework类库中的所有类都符合CLS,因此任何.NET语言都可使用这个类库。如果要开发自己的类库,那么建议确保这些类也符合CLS,这样它们才能得到最广泛的使用。
1.4.2 通用中间语言
通用类型系统和公共语言规范有助于实现独立于语言和平台的目标,但是如果编译器生成的可执行目标代码依赖于硬件平台,那么它们带来的好处将消失殆尽。为解决这个问题,将托管代码进行了部分编译,以生成使用一种低级语言的代码,这种语言称为通用中间语言(Common Intermediate Language,CIL)。通用中间语言类似于汇编语言,由表示高级语言代码的低级指令组成。
程序集(assembly)是部分编译单元(包),包含CIL指令并为定义类型提供了逻辑边界。由于程序集是部分编译的,因此可以是32位的,也可以是64位的,这取决于操作系统和硬件。这意味着托管应用程序是独立于平台的,且无需重新编译或添加特殊指令就可以充分利用硬件技术。
1.4.3 虚拟执行系统
公共语言运行时的另一个重要组成部分是托管运行环境,这种环境称为虚拟执行系统(Virtual Execution System,VES),它负责处理应用程序所需的低级核心服务。就像 Java应用程序需要有 Java 虚拟机(JVM)才能运行一样,托管应用程序也需要有 CLR(具体地说是VES)才能运行。
.NET应用程序启动时,由VES负责加载并执行CIL代码,并管理应用程序所需的内存分配。换句话说,VES提供服务和基础设施,从而消除平台和语言差异带来的影响。
在加载和编译过程中,VES执行各种验证,以确保文件格式、程序集元数据和CIL是一致的,并确保CIL指令不能非法访问内存。这样,应用程序将只能访问它被授权访问的内存和资源。可将这种受限的环境视为沙箱(sandbox)。
既然VES提供了运行环境并执行包含CIL的程序集,那么这些程序集是解释型还是编译型的呢?别忘了,.NET Framework的目标之一是提供这样的运行环境:最大程度地减少甚至消除脚本(解释型语言)的性能问题。这意味着CIL代码是编译型的,VES 提供的服务之一是即时(Just-In-Time,JIT)编译器。即时编译指的是在运行阶段将经过部分编译的CIL代码转换为可执行的目标代码(本机代码)。
ps:即时编译
即时编译过程被称为jitting,而JIT编译器被称为jitter。
通过以这种方式编译代码,.NET Framework获得了比传统解释型语言高得多的速度。相对于常规(静态)编译,即时编译也有优势,因为它可在速度。相对于常规(静态)编译,即时编译也有优势,因为它可在运行阶段实施安全保障,还可在运行阶段重新编译以进一步优化代码。.NET Framework JIT编译器进行了高度优化,以便将CIL代码编译成高效的目标代码;它在需要时运行,并缓存编译后的代码供以后使用。
1.4.4 内存管理和垃圾收集
.NET Framework让VES自动分配和回收内存,这种自动内存管理称为垃圾收集,正是它让C#(和其他.NET语言)成为一种垃圾收集语言。垃圾收集让您无需过多地为释放不需要的内存而操心,它避免了众多常见的编程错误,让您创建的应用程序更稳定,并可重点关注应用程序所需的业务逻辑。
即使有了自动内存管理,理解垃圾收集器如何与程序以及您创建的类型交互也很重要。
1.5 Framework类库
虽然CLR是.NET Framework的核心,但真正赋予它力量的是Framework类库(FCL)。这个类库类似于Java类库、C++标准模板库(STL)、Microsoft活动模板库(ATL)、Microsoft Foundation Class(MFC)、Borland对象窗口库(OWL)及其他类库。
与这些类库一样,FCL也包含大量可重用的类型,这简化了众多常见的编程任务,从而提供了开发效率。
该图列出了FCL中的一些类型,并按功能对它们进行了编组
ps:虽然不使用 FCL 提供的类型也可以创建应用程序,但实际上不可能这样做。
在最底层是基础类库(Base Class Libraries,BCL),它们充当所有.NET语言的标准运行环境,提供的类型可表示CLR类型、集合、流和其他数据结构,可用于操作字符串、执行基本的文件存取和各种其他操作。BCL总共有172 种公有类型,而Ecma-335 标准(Common Language Infrastructure (CLI), 4th Edition)定义的标准库总共有 331种公有类型。
CLI是公共语言运行时(Common Language Runtime)的开源版本。在Ecma 的鼓励下,开发了 C#和.NET Framework 的多个开源版本,包括DotGNU和Mono。其中最著名的可能是Mono,它提供的.NET开发平台实现可用于Linux、BSD、UNIX、Mac OS X、Solaris和Windows操作系统。
FCL 中的其他类关注的都是特定功能,如提供数据访问、XML 支持、全球化支持、诊断、配置、联网、通信、业务流程支持、Web应用程序和Windows桌面应用程序等。
1.5.1 命名空间
鉴于.NET Framework类库包含数千个类,需要采取某种方式避免类名混淆,并提供一种方便的层次编组机制。为此,.NET Framework采用了命名空间的概念。命名空间不过是一系列类型,对类型的可见性没有任何影响。命名空间可包含多个程序集。.NET Framework使用层次型命名空间提供了一个渐进型框架,打造出了一个功能强大而又易于使用的开发平台。
ps:命名空间和类型名
命名空间使用点分式语法表示层次结构,每层之间都用句点(.)分隔。
在类型的完整名称中,以最右边的句点为分界点,左边是命名空间,而右边是类型名。例如, System.Printing.PrintDriver 是位于命名空间System.Printing中的类型PrintDriver的完整名称。
然而,只有.NET编程语言支持命名空间。在CLR中,类型总是用完整名称标识,其中包含类型的名称及其所属的命名空间。
常用命名空间
1.5.2 并行计算平台
这是一种全新的编程模型,适用于托管代码和非托管代码。它提高了抽象程度,无需考虑较低级的概念,如线程和锁定。
对于托管代码,并行计算平台提供了常见循环指令的并行实现和LINQ to Objects的并行实现,还新增了可避免死锁的线程安全集合。
并行计算平台简化了这样的机制:编写可高效利用多个处理器的代码。要确定什么样的代码适用于并行计算,仍需要进行分析
1.5.3 DLR
DLR 建立在公共语言运行时的基础之上,这意味着动态语言可与其他.NET 语言集成。DLR 还让现有的静态语言(如 C#)能够支持动态功能,这让它们能够以一致的方式使用动态对象,而不管这些对象来自何方。
有了 DLR 后,便可支持动态语言,而静态语言也可使用动态功能。这样,开发人员就可根据要解决的问题选择最合适的语言,且创建的动态代码可供其他开发人员和.NET语言轻松地使用。
二、C#语言
2.1 类型
在C#中,类型是对值的描述。每当需要一个值时,就需要一种类型。类型定义了允许的值以及这些值支持的操作。在C#中,每个值都由其所属的类型全面描述,且是其所属类型的一个实例。所谓全面描述,指的是类型明确地指定了值的表示方式以及可对它执行的操作。
C#类型分为两类:
值类型和引用类型。值类型描述的值是完全独立的,这包括数值类型、枚举类型和结构;
引用类型存储指向值的引用,而不是值本身
C#提供了众多预定义的值类型和几种预定义的引用类型,还可以创建用户定义的类型。只需知道一个最重要的差别:值类型包含实际值,而引用类型包含指向实际数据的引用。
2.2 语句和表达式
语句是一条完整的程序指令,必须以分号(;)结尾。每条语句中只能包含一条指令,这看起来限制性很强,但C#还提供了语句块,这是一组用大括号括起的语句。在可以使用单条语句的任何地方都可使用语句块。
由于语句以分号结尾,因此可使用空白(如空格、制表符和换行符)调整代码的排列方式。最佳的方法是采用一种简单而一致的风格,让代码更容易阅读和维护。
ps:虽然编译器通常忽略空白,但是类型声明、标识符和其他关键字之间的空白很重要。如果没有空白,编译器将无法识别关键字。
表达式的结果为值。如果将语句视为操作,那么表达式就是计算。结果为布尔值(true或false)的表达式常用于判断条件是否满足,这种表达式称为布尔表达式
2.3 变量和常量
对于变量,最简单的定义是它表示一个存储位置,其中的值可随时间流逝而变化。最常见的变量是局部变量和字段,它们都可通过指定类型、标识符和可选的初值来定义:
int a;
int b = 1;
如果要声明多个类型相同的变量,就可将声明合并在一起,如下所示:
int a, b;
在限定作用域(如方法)内声明的变量称为局部变量,只能在该作用域内通过名称访问它。
ps:作用域、生命空间和寿命
可将作用域视为容器,只有在该容器内才能通过非限定名合法地访问变量。这不同于声明空间,在声明空间内,不允许有两个名称相同的标识符。如果说作用域指定了您在哪里能够使用某个名称,那么声明空间就指出了该名称在哪里是唯一的。
变量的寿命与其作用域紧密相连,它决定了变量在多长时间内可用。只要当前执行的代码还位于变量的作用域内,该变量就可用。
字段是在限定作用域内声明的变量,它要么与类型本身相关联,要么与类型的一个实例相关联。在前一种情况下称为静态变量(可将其视为一种全局变量);在后一种情况下则称为实例变量。使用局部变量和字段之前,必须将其初始化;另外,只有在这些变量的声明所属的代码块内才能访问它们。
代码:
class Color
{
private byte red;
private byte blue;
private byte green;
public Color(byte red, byte blue, byte green)
{
this.red = red;
this.blue = blue;
this.green = green;
}
public static Color White = new Color(0xFF,0xFF,0xFF);
public static Color Red = new Color(0xFF,0,0);
public static Color Blue = new Color(0,0xFF,0);
public static Color Green = new Color(0,0,0xFF);
}
ps:使用静态字段之前,需要对其进行初始化,但是此后无法保证它们的值不会变化。为了声明初始化后就不能修改的字段,可以创建只读字段。
只读字段的Color类代码如下:
class Color
{
private byte red;
private byte blue;
private byte green;
public Color(byte red, byte blue, byte green)
{
this.red = red;
this.blue = blue;
this.green = green;
}
public static readonly Color White = new Color(0xFF,0xFF,0xFF);
public static readonly Color Red = new Color(0xFF,0,0);
public static readonly Color Blue = new Color(0,0xFF,0);
public static readonly Color Green = new Color(0,0,0xFF);
}
ps:常量表示在编译阶段可计算的值。常量与类型本身相关联,就像是静态的。与变量一样,常量可在限定作用域内声明,也可以是全局的;与变量不同的是,必须在声明常量时对其进行初始化。
ps:字面值和魔数
字面值通常是有特殊含义的数值,并在代码中直接指定。随着时间的推移,可能忘记字面值的含义,导致相应的代码难以维护。因此,这些字面值常被称为魔数(magic numbers)。通过使用常量而不是字面值,可将其含义保留下来,让代码的含义不言自明。
static float Compute(float f1)
{
const float SpeedOfLight = 299792458
return SpeedOfLight / f1;
}
声明变量或常量的语句通常称为声明语句,可位于代码块的任何地方。
2.4 标识符和关键字
声明变量、字段或常量时,必须指定数据类型并提供有意义的名称,后者成为标识符
标识符遵循的规则:
- 只能包含字母(大写和小写)、数字和下划线
- 标识符必须以字母或下划线打头,但对于公有标识符,以一个或多个下划线打头是一种糟糕的做法,应避免这样做
- 在给定的声明空间内,标识符必须是唯一的
- 标识符是区分大小写的
选择标识符时,应遵守的其他知道原则如下:
- 标识符应易于阅读
- 标识符应使用缩写
- 标识符应提供尽可能丰富的含义
C#中,标识符是区分大小写的。推荐的命名约定如下:
- 对于变量名和参数名,使用Camel大小写规则,该规则要求除第一个单词外,其他单词的首字母都大写
- 对于方法名和其他标识符,使用Pascal大小写规则,该规则要求每个单词的首字母都大写
ps:Camel和Pascal大小写规则
采用Camel大小写规则时,标识符中的大写字母看起来向驼峰,这种大小写规则因此而得名。Pascal大小写规则因其被Pascal编程语言广泛采用而得名(Turbo Pascal语言最初是由Anders设计的)。
Microsoft不再推荐使用Hungarian表示法,也不推荐使用下划线分隔单词,这两种表示法在其他语言中很常用。
ps:C#中有77个标识符在任何情况下都属于关键字
ps:还有24个关键字成为上下文关键字,它们仅在特定情况下(上下文)中有特殊含义