1. 命名空间
C# 程序是利用命名空间组织起来的。命名空间既用作程序的“内部”组织系统,也用作“外部”组织系统(一种向其他程序公开自己拥有的程序元素的方法)。
using 指令(第 9.4 节)用来为命名空间的使用提供方便。
1.1 编译单元
compilation-unit 定义了源文件的总体结构。编译单元的组成方式如下:先是零个或多个 using-directive,后接零个或多个 global-attributes,然后是零个或多个 namespace-member-declaration。
compilation-unit:
extern-alias-directivesopt
using-directivesopt
global-attributesopt
namespace-member-declarationsopt
一个 C# 程序由一个或多个编译单元组成,每个编译单元都用一个单独的源文件来保存。编译 C# 程序时,所有这些编译单元一起进行处理。因此,这些编译单元间可以互相依赖,甚至以循环方式互相依赖。
编译单元的 using-directives 影响该编译单元内的 global-attributes 和 namespace-member-declarations,但是不会影响其他编译单元。
编译单元的 global-attributes(第 17 章)允许指定目标程序集和模块的特性。程序集和模块充当类型的物理容器。程序集可以包含若干个在物理上分离的模块。
程序中各编译单元中的 namespace-member-declarations 用于为一个称为“全局命名空间”的单个声明空间提供成员。例如:
文件 A.cs:
class A {}
文件 B.cs:
class B {}
这两个编译单元是为该全局命名空间提供成员的,在本例中它们分别声明了具有完全限定名 A 和 B 的两个类。由于这两个编译单元为同一声明空间提供成员,因此如果它们分别包含了一个同名成员的声明,将会是个错误。
1.2 命名空间声明
namespace-declaration 的组成方式如下:先是关键字 namespace,后接一个命名空间名称和体,然后加一个分号(可选)。
namespace-declaration:
namespace
qualified-identifier
namespace-body ;opt
qualified-identifier:
identifier
qualified-identifier . identifier
namespace-body:
{
extern-alias-directivesopt
using-directivesopt
namespace-member-declarationsopt }
namespace-declaration 可以作为*声明出现在 compilation-unit 中,或是作为成员声明出现在另一个 namespace-declaration 内。当 namespace-declaration 作为*声明出现在 compilation-unit 中时,该命名空间即成为全局命名空间的一个成员。当一个 namespace-declaration 出现在另一个 namespace-declaration 内时,该内部命名空间就成为包含着它的外部命名空间的一个成员。无论是何种情况,一个命名空间的名称在它所属的命名空间内必须是唯一的。
命名空间隐式地为 public,而且在命名空间的声明中不能包含任何访问修饰符。
在 namespace-body 内,可选用 using-directives 来导入其他命名空间和类型的名称,这样,就可以直接地而不是通过限定名来引用它们。可选的 namespace-member-declarations 用于为命名空间的声明空间提供成员。请注意,所有的 using-directives 都必须出现在任何成员声明之前。
namespace-declaration 中的 qualified-identifier 可以是单个标识符,也可以是由“.”标记分隔的标识符序列。后一种形式允许一个程序直接定义一个嵌套命名空间,而不必按词法嵌套若干个命名空间声明。例如,
namespace
N1.N2
{
class A {}
class B {}
}
在语义上等效于
namespace N1
{
namespace N2
{
class A {}
class B {}
}
}
命名空间是可扩充的,两个具有相同的完全限定名的命名空间声明是在为同一声明空间(第 3.3 节)提供成员。在下面的示例中
namespace N1.N2
{
class A {}
}
namespace
N1.N2
{
class B {}
}
上面的两个命名空间声明为同一声明空间提供了成员,在本例中它们分别声明了具有完全限定名 N1.N2.A 和 N1.N2.B
的两个类。由于两个声明为同一声明空间提供成员,因此如果它们分别包含一个同名成员的声明,就将出现错误。
1.3 Extern
别名
extern-alias-directive 引入了一个作为命名空间别名的标识符。对已有别名的命名空间的指定是在程序的源代码外部进行的,这种指定也应用于该已有别名的命名空间的嵌套命名空间。
extern-alias-directives:
extern-alias-directive
extern-alias-directives
extern-alias-directive
extern-alias-directive:
extern alias identifier
;
extern-alias-directive 范围定义的名称范围扩展到直接包含其的编译单元或命名空间体的 using-directives、global-attributes 和 namespace-member-declarations。
在包含 extern-alias-directive 的编译单元或命名空间体内,由 extern-alias-directive 引入的标识符可用于引用已有别名的命名空间。如果该 identifier 为单词 global,则会发生编译时错误。
extern-alias-directive 使别名可用在特定编译单元或命名空间体内,但是它不会向基础声明空间提供任何新成员。换言之,extern-alias-directive 不具传递性,它仅影响它在其中出现的编译单元或命名空间体。
下面的程序声明并使用两个外部别名(X 和 Y),每个别名都代表不同命名空间层次结构的根:
extern alias X;
extern alias Y;
class Test
{
X::N.A a;
X::N.B b1;
Y::N.B b2;
Y::N.C c;
}
该程序声明存在 extern 别名 X 和 Y,但这些别名的实际定义在该程序的外部。同名的 N.B 类现在可分别通过 X.N.B 和 Y.N.B 引用,或者使用命名空间别名限定符通过 X::N.B 和 Y::N.B 引用。如果没有为程序声明的 extern 别名提供外部定义,则会发生错误。
1.4 using
指令
Using 指令 (using directives) 方便了对在其他命名空间中定义的命名空间和类型的使用。using 指令影响 namespace-or-type-name(第 3.8 节)和 simple-name(第 7.6.2 节)的名称解析过程,与声明不同,using 指令不会向在其中使用它们的编译单元或命名空间的基础声明空间中提供新成员。
using-directives:
using-directive
using-directives using-directive
using-directive:
using-alias-directive
using-namespace-directive
using-alias-directive(第 9.4.1 节)用于为一个命名空间或类型引入一个别名。
using-namespace-directive(第 9.4.2 节)用于导入一个命名空间的类型成员。
using-directive 的范围扩展到直接包含它的编译单元或命名空间体内的所有 namespace-member-declarations。具体而言,using-directive 的范围不包括与它对等的 using-directive。因此,对等 using-directive 互不影响,而且按什么顺序编写它们也无关紧要。
1.4.1 using 别名指令
using-alias-directive 为一个命名空间或类型(在直接包容该指令的编译单元或命名空间体内)引入用作别名的标识符。
using-alias-directive:
using identifier = namespace-or-type-name ;
在包含 using-alias-directive 的编译单元或命名空间体内的成员声明中,由 using-alias-directive 引入的标识符可用于引用给定的命名空间或类型。例如:
namespace N1.N2
{
class A {}
}
namespace N3
{
using A = N1.N2.A;
class
B: A {}
}
上面的示例中,在 N3 命名空间中的声明成员内,A 是 N1.N2.A 的别名,因此类 N3.B 从类 N1.N2.A 派生。通过为 N1.N2 创建别名 R,然后再引用 R.A,可以获得相同效果:
namespace N3
{
using R = N1.N2;
class
B: R.A {}
}
using-alias-directive 中的 identifier 在直接包含该 using-alias-directive 的编译单元或命名空间的声明空间内必须是唯一的。例如:
namespace N3
{
class A {}
}
namespace N3
{
using A = N1.N2.A; // Error, A already exists
}
上例中,NN33 已包含了成员 A,因此 using-alias-directive 使用 A 作为标识符会导致一个编译时错误。同样,如果同一个编译单元或命名空间体中的两个或更多 using-alias-directive 用相同名称声明别名,也会导致一个编译时错误。
using-alias-directive 使别名可用在特定编译单元或命名空间体内,但是它不会向基础声明空间提供任何新成员。换句话说,using-alias-directive 不具传递性,它仅影响它在其中出现的编译单元或命名空间体。在下面的示例中
namespace N3
{
using R = N1.N2;
}
namespace N3
{
class B: R.A {} // Error, R unknown
}
引入 R 的 using-alias-directive 的范围只扩展到包含它的命名空间体中的成员声明,因此 R 在第二个命名空间声明中是未知的。但是,如果将 using-alias-directive 放置在包含它的编译单元中,则该别名在两个命名空间声明中都将可用:
using R = N1.N2;
namespace N3
{
class B: R.A {}
}
namespace N3
{
class C: R.A {}
}
和常规成员一样,using-alias-directive 引入的名称在嵌套范围中也可被具有相似名称的成员所隐藏。在下面的示例中
using R = N1.N2;
namespace N3
{
class R {}
class B: R.A {} // Error, R has no member A
}
B 的声明中对 R.A 的引用将导致编译时错误,原因是这里的 R 所引用的是 N3.R 而不是 N1.N2。
编写 using-alias-directive 的顺序并不重要,对由 using-alias-directive 引用的 namespace-or-type-name 的解析过程既不受 using-alias-directive 本身影响,也不受直接包含着该指令的编译单元或命名空间体中的其他 using-directive 影响。换句话说,对 using-alias-directive 的 namespace-or-type-name 的解析,就如同在直接包含该指令的编译单元或命名空间体中根本没有 using-directive 一样来处理。但是,using-alias-directive 可能会受直接包含该指令的编译单元或命名空间体中的 extern-alias-directive 影响。在下面的示例中
namespace N1.N2 {}
namespace N3
{
extern alias E;
using R1 = E.N; // OK
using R2 = N1; // OK
using R3 = N1.N2; // OK
using R4 = R2.N2; // Error,
R2 unknown
}
最后一个 using-alias-directive 导致编译时错误,原因是它不受第一个 using-alias-directive 影响。第一个 using-alias-directive 不会产生错误,因为 extern 别名 E 的范围包括 using-alias-directive。
using-alias-directive 可以为任何命名空间或类型创建别名,包括它在其中出现的命名空间本身,以及嵌套在该命名空间中的其他任何命名空间或类型。
对一个命名空间或类型进行访问时,无论用它的别名,还是用它的所声明的名称,结果是完全相同的。例如,给定
namespace N1.N2
{
class A {}
}
namespace N3
{
using R1 = N1;
using R2 = N1.N2;
class
B
{
N1.N2.A a; // refers to N1.N2.A
R1.N2.A b; // refers to N1.N2.A
R2.A c; // refers to N1.N2.A
}
}
名称 N1.N2.A、R1.N2.A 和 R2.A 是等效的,它们都引用完全限定名为 N1.N2.A 的类。
using 别名可以命名封闭构造类型,但是不能命名未提供类型实参的未绑定泛型类型声明。例如:
namespace N1
{
class A<T>
{
class B {}
}
}
namespace N2
{
using W = N1.A; //
Error, cannot name unbound generic type
using
X = N1.A.B; // Error, cannot name
unbound generic type
using
Y = N1.A<int>; // Ok, can name
closed constructed type
using
Z<T> = N1.A<T>; // Error,
using alias cannot have type parameters
}
1.4.2 Using 命名空间指令
using-namespace-directive 将一个命名空间中所包含的类型导入到直接包容该指令的编译单元或命名空间体中,从而可以直接使用每个被导入的类型的标识符而不必加上它们的限定名。
using-namespace-directive:
using namespace-name ;
在包含 using-namespace-directive 的编译单元或命名空间体中的成员声明内,可以直接引用包含在给定命名空间中的那些类型。例如:
namespace N1.N2
{
class A {}
}
namespace N3
{
using N1.N2;
class
B: A {}
}
上面的示例中,在 N3 命名空间中的成员声明内,N1.N2 的类型成员是直接可用的,所以类 N3.B 从类 N1.N2.A 派生。
using-namespace-directive 导入包含在给定命名空间中的类型,但要注意,它不导入嵌套的命名空间。在下面的示例中
namespace N1.N2
{
class A {}
}
namespace N3
{
using N1;
class
B: N2.A {} // Error, N2 unknown
}
using-namespace-directive 导入包含在 N1 中的类型,但是不导入嵌套在 N1 中的命名空间。因此,在 B 的声明中引用 N2.A 导致编译时错误,原因是在涉及的范围内没有名为 N2 的成员。
与 using-alias-directive 不同,using-namespace-directive 可能导入一些特定类型,它们的标识符已在包容编译单元或命名空间体中定义。事实上,using-namespace-directive 导入的名称会被包容编译单元或命名空间体中具有类似名称的成员所隐藏。例如:
namespace N1.N2
{
class A {}
class B {}
}
namespace N3
{
using N1.N2;
class
A {}
}
此处,在 N3 命名空间中的成员声明内,A 引用 N3.A 而不是 N1.N2.A。
当由同一编译单元或命名空间体中的 using-namespace-directive 导入多个命名空间时,如果它们所包含的类型中有重名的,则对该名称的引用被认为是歧义。在下面的示例中
namespace N1
{
class A {}
}
namespace N2
{
class A {}
}
namespace N3
{
using N1;
using
N2;
class
B: A {} // Error, A is
ambiguous
}
N1 和 N2 都包含一个成员 A,而由于 N3 将两者都导入,所以在 N3 中引用 A 会导致一个编译时错误。在这种情况下,可通过两种办法解决冲突:限定对 A 的引用;引入一个选取特定 A 的 using-alias-directive。例如:
namespace N3
{
using N1;
using
N2;
using
A = N1.A;
class
B: A {} // A means N1.A
}
同 using-alias-directive 一样,using-namespace-directive 不会向编译单元或命名空间的基础声明空间提供任何新成员,因而,它仅影响它出现在其中的编译单元或者命名空间体。
对 using-namespace-directive 所引用的 namespace-name 的解析方式,与对 using-alias-directive 所引用的 namespace-or-type-name 的解析方式相同。因此,同一编译单元或命名空间体中的 using-namespace-directive 互不影响,而且可以按照任何顺序编写。
1.5 命名空间成员
namespace-member-declaration 或是一个
namespace-declaration(第 9.2 节),或是一个 type-declaration(第 9.6 节)。
namespace-member-declarations:
namespace-member-declaration
namespace-member-declarations
namespace-member-declaration
namespace-member-declaration:
namespace-declaration
type-declaration
编译单元或命名空间体可以包含 namespace-member-declarations,而此类声明则为与包含它们的编译单元或命名空间体的基础声明空间提供新成员。
1.6 类型声明
type-declaration 是 class-declaration(第 10.1 节)、struct-declaration(第 11.1 节)、interface-declaration(第 13.1 节)、enum-declaration(第 14.1 节)或 delegate-declaration(第 15.1 节)。
type-declaration:
class-declaration
struct-declaration
interface-declaration
enum-declaration
delegate-declaration
type-declaration 可以作为*声明出现在编译单元中,或者作为成员声明出现在命名空间、类或结构内部。
当类型 T 的类型声明作为编译单元中的*声明出现时,新声明的类型的完全限定名正好是 T。当类型 T 的类型声明出现在命名空间、类或结构内时,新声明的类型的完全限定名是 N.T,其中 N 是包含它的命名空间、类或结构的完全限定名。
在类或结构内声明的类型称为嵌套类型(第 10.3.8 节)。
在一个类型声明中允许使用哪些访问修饰符以及具有何种默认访问属性,取决于进行了该声明的上下文(第 3.5.1 节):
- 在编译单元或命名空间中声明的类型可以具有 public 或 internal
访问属性。默认为 internal 访问权限。 - 在类中声明的类型可以具有 public、protected internal、protected、internal 或 private 访问权限。默认为 private 访问权限。
- 在结构中声明的类型可以具有 public、internal 或 private 访问权限。默认为 private 访问权限。
1.7 命名空间别名限定符
命名空间别名限定符 namespace alias qualifier :: 让类型名称的查找不受引入的新类型和新成员的影响成为可能。命名空间别名限定符总是出现在两个标识符之间,这两个标识符分别称为左标识符和右标识符。与普通的 . 标识符不同,:: 限定符的左标识符仅作为 extern 或 using 别名进行查找。
qualified-alias-member 定义如下:
qualified-alias-member:
identifier ::
identifier type-argument-listopt
qualified-alias-member 可用作 namespace-or-type-name(第 3.8 节)或用作 member-access(第 7.6.4 节)中的左操作数。
qualified-alias-member 具有下列两种形式之一:
- N::I<A1, ...,AK>,其中
N 和 I 表示标识符,<A1, ..., AK> 是类型实参列表。(K 始终至少为 1。) - N::I,,其中 N 和 I 表示标识符。(在此情况下,K 视作 0。)
如果使用此表示法,qualified-alias-member 的含义按下面的过程确定:
- 如果 N 为标识符 global,则在全局命名空间中搜索 I:
- 如果全局命名空间包含名为 I 的命名空间,并且 K 为 0,则 qualified-alias-member 即表示该命名空间。
- 否则,如果该全局命名空间包含名为 I 的非泛型类型,并且 K 为 0,则 qualified-alias-member 即表示该类型。
- 否则,如果该全局命名空间包含名为 I 的带有 K 个类型形参的类型,则 qualified-alias-member 即表示使用给定的类型实参构造的该类型。
- 否则,qualified-alias-member 是不确定的,并发生编译时错误。
- 否则,从直接包含 qualified-alias-member 的命名空间声明(第 9.2
节)(如果有)开始,继续处理每一个包含它的命名空间声明(如果有),最后在包含 qualified-alias-member 的编译单元结束,在找到实体之前,将计算下列步骤: - 如果命名空间声明或编译单元包含将 N 与某个类型相关联的 using-alias-directive,则qualified-alias-member是不确定的,并发生编译时错误。
- 否则,如果命名空间声明或编译单元包含将 N 与某个命名空间相关联的 extern-alias-directive 或 using-alias-directive,则:
- 如果与
N 关联的命名空间包含名为 I 的命名空间,并且 K 为 0,则 qualified-alias-member 即表示该命名空间。 - 否则,如果与 N 关联的命名空间包含名为 I 的非泛型类型,并且 K 为 0,则 qualified-alias-member 即表示该类型。
- 否则,如果与 N 关联的命名空间包含名为 I 的带有 K 个类型形参的类型,则 qualified-alias-member 即表示使用给定的类型实参构造的该类型。
- 否则,qualified-alias-member 是不确定的,并发生编译时错误。
- 否则,qualified-alias-member 是不确定的,并发生编译时错误。
- 如果与
注意,将命名空间别名限定符与引用某个类型的别名一起使用将导致编译时错误。另请注意,如果标识符 N 为 global,则在全局命名空间中执行查找,即使存在将 global 与某个类型或命名空间关联的 using 别名。
1.7.1 别名的唯一性
每个编译单元和命名空间体对于
extern 别名和 using 别名都有单独的声明空间。因此,虽然 extern 别名或 using 别名的名称在直接包含它们的编译单元或命名空间体中声明的 extern 别名和 using 别名集中必须唯一,但是允许别名与类型或命名空间同名,只要它仅与 :: 限定符连用。
在下面的示例中
namespace N
{
public class A {}
public class B {}
}
namespace N
{
using A = System.IO;
class X
{
A.Stream s1; // Error, A is ambiguous
A::Stream s2; // Ok
}
}
名称 A 在第二个命名空间体中有两种可能的含义,因为类 A 和 using 别名 A 都在范围中。因此,在限定名 A.Stream 中使用的 A 是不确定的,并会导致发生编译时错误。但是,将 A 与 :: 限定符连用则不是错误,因为将 A 只作为命名空间别名进行查找。