第 78 讲:C# 2 之类型和命名空间别名
总算是结束了委托的相关知识点了。下面我们来看一个新的语言特性:类型和命名空间别名。
1-1 回顾命名空间的语法
命名空间一直是我这个教程里最“疏忽”的知识点。之所以这么说,是因为整个教程对命名空间的讲解都相当少。因为,现代 C# 编程基本都无需用户针对命名空间和库 API 的所处命名空间有任何的关心,就可以写代码。因为 Visual Studio 现在已经可以帮助用户添加任何的命名空间的信息了,因此你完全不必去记住它们。不过,为了学习 API,更加稳妥和经验性的学习办法,仍旧是建议大家记住一些基本 API 的所处命名空间,这样你在使用的时候就可以做到得心应手了。
我们来回顾一下命名空间的基本知识点。命名空间是使用标识符搭建起来的项目代码文件管理机制。就好比你使用文件夹来分离不同功能的 API 一样,命名空间则是完全不依赖于文件夹,而单独创建命名空间的形式来区分和辨认不同代码文件的一种语言机制。
文件夹可以包含子文件夹,那么命名空间也可以包含子命名空间。命名空间采用标识符取名的规则得到一个完整的命名空间名,而下属的子命名空间名则使用小数点 .
后跟上子命名空间名称的方式来完成组合表示,类似于文件夹目录的 C:\Users\Sunnie\Desktop
里面的反斜杠 \
。
声明命名空间的方式是使用 namespace
关键字,后跟上命名空间的方式来对文件进行声明;而这种声明方式下,我们使用大括号定界,将属于这个命名空间的所有内容(类型之类的)包裹在其中,构成一个文件。像是这样:
比如这样的代码。
一个文件可以包含多个命名空间的声明,也可以只有一个。一般来说,一个文件都只包含一个命名空间的声明。如果包含多个的话,多半是嵌套存在的:
TestProject.MainProgram
所以,很少用到多命名空间声明在同一个文件下的情况。
1-2 命名空间别名
命名空间的别名指的是给命名空间换一个名字。它和 C 语言里的 typedef
有些类似,只不过这里,我们说的是命名空间而不是类型而已。
C# 2 允许我们使用 using
别名指令来完成,语法是这样的:
我们来举例说明吧。
C# 里的所有泛型集合规定的命名空间名称是位于 System.Collections.Generic
命名空间里,那么我们要想使用 List<>
集合的话,我们需要写 using System.Collections.Generic;
指令才能使用。不过现在我们有了这个机制,我们可以稍微简化一下:
还是有点复杂是吧?确实是这样,不过这个机制是伴随着类型别名一起出来的,因此我们接着来看看类型别名的语法。
Part 2 类型别名
类型别名是专门用于类型取别名的机制,它是货真价实跟 typedef
的 C# 翻版代替。
2-1 基本用法
我们可使用和前文一样的定义方式,来完成对一个数据类型的别名的定义。但是一定请注意,using
别名指令和 using
System.Collections.Generic.List<System.Int32>
的地方,都可以替换为 Integers
这里我们要稍微说一个点。我们在书写 using
别名指令的时候,因为要完全对应上一个具体的数据类型,因此你必须要写全你对应的数据类型,它的所在命名空间以及它的名字。比如上面举例用到的 List<>
集合,你必须要给出它所处的命名空间才行。否则,编译器也不能确定这个 List<>
到底在哪里。
另外,你要想使用像是这样的泛型数据类型的话,你甚至必须给出整个泛型数据类型指向的每一个泛型参数的对应类型名称的全名。当然,内置类型是可以写关键字的,因为这个编译器也知道它们都对应什么类型;但普通的、自己实现的数据类型可能就必须指定清楚了。比如前文给的这个 System.Collections.Generic.List<System.Int32>
就可以直接用关键字:System.Collections.Generic.List<int>
。
比如这样的数据类型,对应的是 Dictionary<,>
泛型集合,传入的泛型参数分别是 Student
数据类型(由我们自己实现,假设它位于 TestProject.Models
命名空间下),然后给予该同学成绩,用 double
类型的数组表示。所以,整个这么长的类型就可以通过我们自定义的 StudentsScoreList
来代替。如果你在当前文件的别处用上了这样的 Dictionary<Student, double[]>
的定义类型的话,它将可以用 StudentsScoreList
来代替掉。
它的目的解决的就是类型较长而且非常不方便书写的时候,来进行简化的格式和语法。像是上述这种,我们会在泛型数据类型的泛型参数上使用各种“奇怪”的数据类型,这样的语法就能够派上用场。
2-2 类型别名仅支持对普通和泛型数据类型的定义
如题,这种语法机制只针对于普通的(非泛型数据类型)和泛型数据类型有效果。你不能用在一个数组上,也不能用在一个可空值类型上,更不能用于指针类型上:
因为,这样的定义一般都没有实际意义,因为它们自己就自带记号(指针的 *
记号、可空值类型的 ?
记号、数组的 []
记号),所以我们已经都假设它们是简易的数据类型了,因此它们自身没必要取别名。
不过,你可以用在一个泛型数据类型,而且泛型数据类型的泛型参数上可以出现它们(当然,指针类型不能用在泛型参数上,这个是泛型机制自身的限制,并不属于这里语法的限制),比如刚才这个例子。
这里的 double[]
就出现在了泛型参数上,而这样书写的代码是允许的。
可能你现在觉得你自己书写的代码还不够复杂所以不必这么简写,当你学到后面 LINQ 机制的时候,大量使用到的接口类型,那时候的泛型接口还嵌套了一个泛型接口,你甚至还需要外面加一层泛型的数据类型,这时候你就知道这个机制有多么好用了。
2-3 注意类型名称冲突现象
假设我现在定义了类型的别名 A
,结果系统也有一个自带的类型也叫 A
,此时就无法区分这样的情况。举个例子,假设我自己创建了一个 ArrayList
类型是别名的情况,而由于 C# 自带 ArrayList
类型:
ArrayList
就无法区分到底是具体哪一个数据类型。这种现象就是类型别名冲突的现象。这种编译器会自动告知用户这么取别名是不合适的。如果你确实要用系统的 ArrayList
System.Collections
的 System
的情况,你在书写代码的时候,可省略 System