5.3 using指令
完全限定的名称可能很长、很笨拙。可将一个或多个命名空间的所有类型“导入”文件,这样在使用时就不需要完全限定。这可通过using指令(通常在文件顶部)来实现。例如代码清单5.7中的Console就没有附加System前缀,因为代码清单顶部使用了一个using System指令。
代码清单5.7 using指令的例子
代码清单5.7的结果如输出5.2所示。
输出5.2
虽然添加了using System,但使用System的某个子命名空间中的类型时还是不能省略System。例如,要访问System.Text中的StringBuilder类型,必须另外添加一个using System.Text指令或者对类型进行完全限定(System.Text.StringBuilder),而不能只是写Text.StringBuilder。简单地说,using指令不“导入”任何嵌套命名空间中的类型。嵌套命名空间(由命名空间中的句点符号来标识)必须显式导入。
语言对比:Java——import指令中的通配符
Java允许使用通配符导入命名空间,例如:
import javax.swing.*;
相反,C#不允许在using指令中使用通配符,每个命名空间都必须显式导入。
语言对比:Visual Basic .NET——项目范围的Imports指令
和C#不同,Visual Basic .NET允许为整个项目(而非只是单个文件)使用与using指令等价的Imports指令。换言之,Visual Basic .NET提供了using指令的一个命令行版本,它对项目的所有文件起作用。
通常,程序要使用一个命名空间中的许多类型,就应考虑为该命名空间使用using指令,避免对该命名空间中的所有类型都进行完全限定。正是这个原因,几乎所有C#文件都在顶部添加了using System指令。在本书剩余的部分,代码清单会经常省略using System指令,但其他命名空间指令都会显式地包含。
使用using System指令的一个有趣结果是,可以使用不同的大小写形式来表示字符串数据类型:String或者string。前者的基础是using System指令,后者使用的是string关键字。两者在C#中都引用System.String数据类型,最终生成的CIL代码毫无区别[1]。
高级主题:嵌套using指令
using指令不仅可以在文件顶部使用,还可以在命名空间声明的顶部使用。例如,声明新命名空间EssentialCSharp时,可在该声明的顶部添加using指令,如代码清单5.8所示。
代码清单5.8 在命名空间声明中使用using指令
输出5.3展示了结果。
输出5.3
在文件顶部和命名空间声明的顶部使用using指令的区别在于,后者的using指令只在声明的命名空间内有效。如果在EssentialCSharp命名空间前后声明了新命名空间,新命名空间不会受别的命名空间中的using System指令的影响。但我们很少写这样的代码,特别是根据约定,每个文件只应该有一个类型声明。
5.3.1 using static指令
using指令允许省略命名空间限定符来简化类型名称。而using static指令允许将命名空间和类型名称都省略,只需写静态成员名称。例如,using static System.Console指令允许直接写WriteLine()而不必写完全限定名称System.Console.WriteLine()。基于这个技术,代码清单5.2可改写为代码清单5.9。
代码清单5.9 using static指令
本例不会降低代码的可读性。WriteLine()、Write()和ReadLine()明显与控制台指令有关。事实上,我们可以说代码显得比以往更简单、更清晰。
但这并非绝对,有的类定义了重叠的行为名称(方法名),例如文件和目录都提供了Exists()方法。在定义了using static指令的前提下直接调用Exists()无利于区分。类似地,如果你写的类定义了行为名称重叠的成员,例如Display()和Write(),读者会容易混淆。
编译器不允许这种歧义存在。两个成员如具有相同的签名(通过using static指令或者是单独声明的成员),调用它们时就会产生歧义,会造成编译错误。
5.3.2 使用别名
还可利用using指令为命名空间或类型取一个别名。别名是在using指令起作用的范围内可以使用的替代名称。别名的两个最常见的用途是消除两个同名类型的歧义和缩写长名称。例如在代码清单5.10中,CountDownTimer别名引用了System.Timers.Timer类型。仅添加using System.Timers指令不足以完全限定Timer类型,原因是System.Threading也包含Timer类型,所以在代码中直接用Timer会产生歧义。
代码清单5.10 声明类型别名
代码清单5.10将全新的名称CountDownTimer作为别名,但也可将别名指定为Timer,如代码清单5.11所示。
代码清单5.11 声明同名的类型别名
由于Timer现在是别名,所以“Timer”引用没有歧义。这时如果要引用System.Threading.Timer类型,必须完全限定或定义不同的别名。
[1] 我更喜欢使用string关键字,但无论选择哪一种表示方法,都应在项目中保持一致。