命名空间是一系列类型名称的领域。通常情况下,类型组织在分层的命名空间里,既避免了命名冲突又更容易查找。例如,处理公钥加密的 RSA 类型就定义在如下的命名空间下:
命名空间组成了类型名的基本部分。下面代码调用了 RSA 类型的 Create 方法:
命名空间是独立于程序集的,程序集是像 .dll 文件一样的部署单元(参见第17章)。
命名空间并不影响 public 、 internal 、 private 等成员的可见性。
namespace 关键字为其中的类型定义了命名空间,例如:
命名空间中的“ . ”表明了嵌套命名空间的层次结构,下面的代码在语义上和上一个例子是等价的:
类型可以用完全限定名称(fully qualified name),也就是包含从外到内的所有命名空间的名称,来指定。例如,上述例子中,可以使用 Outer.Middle.Inner.Class1 来指代 Class1 。
如果类型没有在任何命名空间中定义,则它存在于全局命名空间(global namespace)中。全局命名空间也包含了顶级命名空间,就像前面例子中的 Outer 命名空间。
通常,一个文件中的所有类型都定义在同一个命名空间中:
从C# 10开始,我们可以使用文件范围命名空间来达到相同的目的:
文件范围命名空间不但令代码更简洁,还能消除不必要的缩进。
using 指令用于导入命名空间,这是避免使用完全限定名称来指代某种类型的快捷方法。以下例子导入了前一个例子的 Outer.Middle.Inner 命名空间:
在不同命名空间中定义相同类型名称是合法的(而且通常是必要的)。然而,这种做法通常在开发者不会同时导入两个命名空间时使用,例如, TextBox 类,这个名称在 System.Windows.Controls (WPF)和 System.Windows.Forms (Windows Forms)命名空间中都有定义。
using 指令可以在命名空间中嵌套使用,这样可以限制 using 指令的作用范围。
在C# 10中,若在 using 指令前添加 global 关键字,则该指令将在整个工程或相应编译单元中的所有文件中生效:
因此,我们可以使用上述指令将公共的命名空间引用集中起来以避免在每一个文件中都进行重复书写。
global using 指令必须出现在非 global using 指令之前,并且不能够在namespace声明之内使用。全局指令也可以和 using static 合并使用。
从.NET 6开始,我们可以在工程文件中配置隐式 global using 指令。如果工程文件中的 ImplicitUsings 元素的值为true(新工程默认为true),则将隐式引入如下的命名空间:
不同工程使用的SDK(Web、Windows Forms、WPF等)不同,因此除上述命名空间外还会引入其他命名空间。
我们不仅可以使用 using 指令导入命名空间,还可以使用 using static 指令导入特定的类型,这样就可以使用类型静态成员而无须指定类型的名称了。在下面例子中,我们在不指定类型的情况下调用 Console 类的静态方法 WriteLine :
using static 指令将类型的可访问的静态成员,包括字段、属性以及嵌套类型(参见第3章),全部导入进来。同时,该指令也支持导入枚举类型的成员(参见第3章)。因此如果导入了以下的枚举类型:
我们就可以直接使用 Hidden 而不是 Visibility.Hidden 了:
C#编译器还没有聪明到可以基于上下文来推断出正确的类型,因此在导入多个静态类型导致二义性时会发生编译错误。
外层命名空间中声明的名称能够直接在内层命名空间中使用。以下示例中的 Class1 在 Inner 中不需要限定名称:
在使用命名空间树形结构的不同分支中的类型时,需要使用部分限定名称。在下面的例子中, SalesReport 类继承 Common.ReportBase :
如果相同类型名称同时出现在内层和外层命名空间中,则内层名称优先。如果要使用外层命名空间中的类型,则必须使用它的完全限定名称:
所有的类型名在编译时都会转换为完全限定名称。中间语言(IL)代码不包含非限定名称和部分限定名称。
只要命名空间内的类型名称不冲突,就可以重复声明同一个命名空间:
上述例子也可以分为两个不同的源文件,并将每一个类都编译到不同的程序集中。
源文件1:
源文件2:
我们能够在命名空间中嵌套使用 using 指令,这样可以控制 using 指令在命名空间声明中的作用范围。在以下例子中, Class1 在一个命名空间中可见,但是在另一个命名空间中不可见:
导入命名空间可能导致类型名称的冲突,因此可以只导入需要的特定类型而不是整个命名空间,并给它们创建别名。例如:
下面代码为整个命名空间创建别名:
使用外部别名就可以引用两个完全限定名称相同的类型(例如,命名空间和类型名称都相同)。这种特殊情况只在两种类型来自不同的程序集时才会出现。请考虑下面的例子:
程序库1,编译为 Widgets1.dll :
程序库2,编译为 Widgets2.dll :
当应用程序同时引用 Widgets1.dll 和 Widgets2.dll 时:
以上程序是无法编译的,因为 Widget 类型是有二义性的。外部别名则可以消除应用程序中的二义性。第一步需要更改应用程序的 .csproj 工程文件,为每一个引用赋予一个唯一的别名:
接下来就可以使用 extern alias 指令使用这些别名了:
之前提到,内层命名空间中的名称隐藏外层命名空间中的名称。但是,有时即使使用类型的完全限定名也无法解决冲突。请考虑下面的例子:
Main 方法将会实例化嵌套类B或命名空间A中的类B。编译器总是给当前命名空间中的标识符以更高的优先级,在这种情况下,将会实例化嵌套类B。
要解决这样的冲突,可以使用如下的方式限定命名空间中的名称:
· 全局命名空间,即所有命名空间的根命名空间(由上下文关键字 global 指定)
· 一系列的外部别名
“ :: ”用于限定命名空间别名。下面的例子中,我们使用了全局命名空间(这通常出现在自动生成的代码中,以避免名称冲突):
以下例子使用了别名限定符(2.12.7.1节中例子的修改版本):
[1] 一个小警告,将一个非常大的 long 转换为 double 时,有可能造成精度丢失。
[2] 从技术上说, decimal 也是一种浮点类型,但在C#语言规范中并没有将其定义为浮点类型。
[3] 可选参数可以以 in 修饰符修饰。