C#6.0语言规范(九) 命名空间
C#程序使用命名空间进行组织。命名空间既可以用作程序的“内部”组织系统,也可以用作“外部”组织系统 - 一种呈现暴露给其他程序的程序元素的方式。
提供了使用指令(使用指令)以便于使用命名空间。
编译单位
compilation_unit定义了源文件的总体结构。编译单元由零个或多个using_directive组成,后跟零个或多个global_attributes,后跟零个或多个namespace_member_declaration。
1 compilation_unit 2 : extern_alias_directive* using_directive* global_attributes? namespace_member_declaration* 3 ;
C#程序由一个或多个编译单元组成,每个编译单元都包含在一个单独的源文件中。编译C#程序时,将一起处理所有编译单元。因此,编译单元可以以循环方式彼此依赖。
该using_directive编译单元号第影响global_attributes和namespace_member_declaration该编译单元的S,但对其他的编译单元没有影响。
编译单元的global_attributes(Attributes)允许指定目标程序集和模块的属性。程序集和模块充当类型的物理容器。组件可以由几个物理上分开的模块组成。
程序的每个编译单元的namespace_member_declaration将成员贡献给称为全局名称空间的单个声明空间。例如:
文件A.cs
:
class A {}
文件B.cs
:
class B {}
这两个编译单元对单个全局命名空间有贡献,在这种情况下,使用完全限定名称A
和声明两个类B
。因为两个编译单元对同一个声明空间有贡献,所以如果每个编译单元都包含一个具有相同名称的成员的声明,则会出错。
命名空间声明
namespace_declaration由关键字namespace
,随后是命名空间名称和体,任选地接着用分号。
1 namespace_declaration 2 : 'namespace' qualified_identifier namespace_body ';'? 3 ; 4 5 qualified_identifier 6 : identifier ('.' identifier)* 7 ; 8 9 namespace_body 10 : '{' extern_alias_directive* using_directive* namespace_member_declaration* '}' 11 ;
namespace_declaration可以如在顶层声明发生compilation_unit或作为另一个内的成员声明namespace_declaration。当namespace_declaration作为compilation_unit中的顶级声明出现时,该命名空间将成为全局命名空间的成员。当namespace_declaration另一个内发生namespace_declaration,内部命名空间成为外部命名空间的成员。在任何一种情况下,命名空间的名称在包含的命名空间中必须是唯一的。
命名空间是隐式的public
,命名空间的声明不能包含任何访问修饰符。
在namespace_body中,可选的using_directive导入其他名称空间,类型和成员的名称,允许直接引用它们而不是通过限定名称。可选的namespace_member_declaration将成员提供给命名空间的声明空间。请注意,所有using_directive必须出现在任何成员声明之前。
所述qualified_identifier一个的namespace_declaration可以是单个标识符或通过“分开的标识符序列.
”令牌。后一种形式允许程序定义嵌套的命名空间,而不用词法嵌套几个命名空间声明。例如,
1 namespace N1.N2 2 { 3 class A {} 4 5 class B {} 6 }
在语义上等同于
1 namespace N1 2 { 3 namespace N2 4 { 5 class A {} 6 7 class B {} 8 } 9 }
命名空间是开放式的,具有相同完全限定名称的两个命名空间声明对同一声明空间(声明)有贡献。在这个例子中
1 namespace N1.N2 2 { 3 class A {} 4 } 5 6 namespace N1.N2 7 { 8 class B {} 9 }
以上两个命名空间声明提供相同的声明空间,在这种情况下,声明两个具有完全限定名N1.N2.A
和N1.N2.B
。因为这两个声明对同一个声明空间有贡献,如果每个声明包含一个具有相同名称的成员的声明,那将是一个错误。
外部别名
一个extern_alias_directive引入了用作一个命名空间的别名的标识符。别名命名空间的规范是程序源代码的外部,也适用于别名命名空间的嵌套命名空间。
1 extern_alias_directive 2 : 'extern' 'alias' identifier ';' 3 ;
extern_alias_directive的范围扩展到其直接包含的编译单元或命名空间体的using_directive,global_attributes和namespace_member_declaration。
在包含extern_alias_directive的编译单元或命名空间体中,extern_alias_directive引入的标识符可用于引用别名命名空间。标识符是单词的编译时错误global
。
一个extern_alias_directive使得一个特定的编译单元或命名空间体中可用的别名,但它并没有任何新成员的基础声明空间。换句话说,extern_alias_directive不是传递性的,而是仅影响它出现的编译单元或命名空间体。
以下程序声明并使用两个extern别名,X
并且Y
每个别名代表不同命名空间层次结构的根:
1 extern alias X; 2 extern alias Y; 3 4 class Test 5 { 6 X::N.A a; 7 X::N.B b1; 8 Y::N.B b2; 9 Y::N.C c; 10 }
该计划声明的extern别名的存在X
和Y
,但别名的实际定义外部程序。在同名的N.B
类可以作为现在被引用X.N.B
和Y.N.B
,或者使用命名空间别名限定符,X::N.B
和Y::N.B
。如果程序声明了一个没有提供外部定义的extern别名,则会发生错误。
使用指令
使用指令有助于使用在其他命名空间中定义的命名空间和类型。使用指令会影响namespace_or_type_name s(名称空间和类型名称)和 simple_name s(简单名称)的名称解析过程,但与声明不同,使用指令不会将新成员提供给编译单元或命名空间的基础声明空间。用过的。
1 using_directive 2 : using_alias_directive 3 | using_namespace_directive 4 | using_static_directive 5 ;
using_alias_directive(使用别名指令)介绍了一个命名空间或类型的别名。
using_namespace_directive(使用命名空间指令)导入一个命名空间的类型成员。
using_static_directive(使用静态指令)导入嵌套类型和类型的静态成员。
一个的范围using_directive在延伸namespace_member_declaration直接包含它的编译单元或命名空间体的第 using_directive的范围特别不包括其对等的using_directive。因此,peer using_directive不会相互影响,并且它们的写入顺序是无关紧要的。
使用别名指令
using_alias_directive引入了用作用于直接包含编译单元或命名主体内的命名空间或类型的别名的标识符。
1 using_alias_directive 2 : 'using' identifier '=' namespace_or_type_name ';' 3 ;
在包含using_alias_directive的编译单元或命名空间体中的成员声明中,using_alias_directive引入的标识符可用于引用给定的命名空间或类型。例如:
1 namespace N1.N2 2 { 3 class A {} 4 } 5 6 namespace N3 7 { 8 using A = N1.N2.A; 9 10 class B: A {} 11 }
上面,在N3
命名空间中的成员声明中,A
是一个别名N1.N2.A
,因此类N3.B
派生自类N1.N2.A
。相同的效果可以通过创建别名来获得R
用于N1.N2
然后引用R.A
:
1 namespace N3 2 { 3 using R = N1.N2; 4 5 class B: R.A {} 6 }
所述标识符一个的using_alias_directive必须是编译单元或命名直接包含的声明空间内是唯一的using_alias_directive。例如:
1 namespace N3 2 { 3 class A {} 4 } 5 6 namespace N3 7 { 8 using A = N1.N2.A; // Error, A already exists 9 }
上面N3
已经包含一个成员A
,因此using_alias_directive使用该标识符是编译时错误。同样,对于同一编译单元或命名空间体中的两个或多个using_alias_directive来说,使用相同名称声明别名是编译时错误。
一个using_alias_directive使得一个特定的编译单元或命名空间体中可用的别名,但它并没有任何新成员的基础声明空间。换句话说,using_alias_directive不是可传递的,而是仅影响它出现的编译单元或命名空间体。在这个例子中
1 namespace N3 2 { 3 using R = N1.N2; 4 } 5 6 namespace N3 7 { 8 class B: R.A {} // Error, R unknown 9 }
引入的using_alias_directive的范围R仅扩展到包含它的名称空间主体中的成员声明,因此R在第二个名称空间声明中是未知的。但是,将using_alias_directive放在包含的编译单元中会导致别名在两个名称空间声明中都可用:
1 using R = N1.N2; 2 3 namespace N3 4 { 5 class B: R.A {} 6 } 7 8 namespace N3 9 { 10 class C: R.A {} 11 }
与常规成员一样,using_alias_directive引入的名称在嵌套作用域中由类似命名的成员隐藏。在这个例子中
1 using R = N1.N2; 2 3 namespace N3 4 { 5 class R {} 6 7 class B: R.A {} // Error, R has no member A 8 }
R.A
在声明B
原因中引用编译时错误,因为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_directives在直接包含编译单元或命名空间体中。在这个例子中
1 namespace N1.N2 {} 2 3 namespace N3 4 { 5 extern alias E; 6 7 using R1 = E.N; // OK 8 9 using R2 = N1; // OK 10 11 using R3 = N1.N2; // OK 12 13 using R4 = R2.N2; // Error, R2 unknown 14 }
最后一次using_alias_directive导致编译时错误,因为它不受第一个using_alias_directive的影响。第一个using_alias_directive不会导致错误,因为extern别名的范围E
包括using_alias_directive。
using_alias_directive可以为任何命名空间或类型创建别名,包括在其内出现的任何空间或类型嵌套在该命名空间中的命名空间。
通过别名访问命名空间或类型会产生与访问该命名空间或通过其声明的名称键入完全相同的结果。例如,给定
1 namespace N1.N2 2 { 3 class A {} 4 } 5 6 namespace N3 7 { 8 using R1 = N1; 9 using R2 = N1.N2; 10 11 class B 12 { 13 N1.N2.A a; // refers to N1.N2.A 14 R1.N2.A b; // refers to N1.N2.A 15 R2.A c; // refers to N1.N2.A 16 } 17 }
名称N1.N2.A
,R1.N2.A
和,R2.A
是等价的,都是指完全限定名称的类N1.N2.A
。
使用别名可以命名一个封闭的构造类型,但不能在不提供类型参数的情况下命名未绑定的泛型类型声明。例如:
1 namespace N1 2 { 3 class A<T> 4 { 5 class B {} 6 } 7 } 8 9 namespace N2 10 { 11 using W = N1.A; // Error, cannot name unbound generic type 12 13 using X = N1.A.B; // Error, cannot name unbound generic type 14 15 using Y = N1.A<int>; // Ok, can name closed constructed type 16 17 using Z<T> = N1.A<T>; // Error, using alias cannot have type parameters 18 }
使用命名空间指令
using_namespace_directive出口包含在一个命名空间到直接包含编译单元或命名空间体的种类,使每个类型的标识符无资格使用。
1 using_namespace_directive 2 : 'using' namespace_name ';' 3 ;
在包含using_namespace_directive的编译单元或命名空间体中的成员声明中,可以直接引用给定命名空间中包含的类型。例如:
1 namespace N1.N2 2 { 3 class A {} 4 } 5 6 namespace N3 7 { 8 using N1.N2; 9 10 class B: A {} 11 }
上面,在N3
命名空间中的成员声明中,类型成员N1.N2
直接可用,因此类N3.B
派生自类N1.N2.A
。
一个using_namespace_directive进口包含在给定命名空间中的类型,但不导入嵌套的命名空间。在这个例子中
1 namespace N1.N2 2 { 3 class A {} 4 } 5 6 namespace N3 7 { 8 using N1; 9 10 class B: N2.A {} // Error, N2 unknown 11 }
该using_namespace_directive进口类型包含在N1
,但嵌套在没有命名空间N1
。因此,在结果N2.A
声明中引用B
编译时错误,因为没有命名N2
的成员在范围内。
与using_alias_directive不同,using_namespace_directive可以导入其标识符已在封闭编译单元或命名空间体中定义的类型。实际上,using_namespace_directive导入的名称由封闭编译单元或命名空间体中的类似命名成员隐藏。例如:
1 namespace N1.N2 2 { 3 class A {} 4 5 class B {} 6 } 7 8 namespace N3 9 { 10 using N1.N2; 11 12 class A {} 13 }
这里,在N3
命名空间中的成员声明中,A
引用N3.A
而不是N1.N2.A
。
当在同一编译单元或命名空间体中使用usespace_directive或using_static_directive s 导入的多个名称空间或类型包含相同名称的类型时,对该名称作为type_name的引用被认为是不明确的。在这个例子中
1 namespace N1 2 { 3 class A {} 4 } 5 6 namespace N2 7 { 8 class A {} 9 } 10 11 namespace N3 12 { 13 using N1; 14 15 using N2; 16 17 class B: A {} // Error, A is ambiguous 18 }
双方N1
并N2
包含一个成员A
,而且由于N3
进口两种,引用A
的N3
是一个编译时错误。在这种情况下,冲突可以通过引用的限定来解决A
,或者通过引入选择特定的using_alias_directive来解决A
。例如:
1 namespace N3 2 { 3 using N1; 4 5 using N2; 6 7 using A = N1.A; 8 9 class B: A {} // A means N1.A 10 }
此外,当在同一编译单元或命名空间体中使用definespace_directive或using_static_directive s 导入的多个名称空间或类型包含相同名称的类型或成员时,对该名称作为simple_name的引用被认为是不明确的。在这个例子中
1 namespace N1 2 { 3 class A {} 4 } 5 6 class C 7 { 8 public static int A; 9 } 10 11 namespace N2 12 { 13 using N1; 14 using static C; 15 16 class B 17 { 18 void M() 19 { 20 A a = new A(); // Ok, A is unambiguous as a type-name 21 A.Equals(2); // Error, A is ambiguous as a simple-name 22 } 23 } 24 }
N1
包含一个类型成员A
,并C
包含一个静态方法A
,并且因为N2
两者都是导入,所以A
作为simple_name引用是不明确的并且是编译时错误。
与using_alias_directive类似,using_namespace_directive不会向编译单元或命名空间的基础声明空间提供任何新成员,而只会影响它出现的编译单元或命名空间主体。
该namespace_name通过引用using_namespace_directive以同样的方式作为解决namespace_or_type_name通过引用using_alias_directive。因此,在同一编译单元或命名空间体中使用definespace_directive不会相互影响,并且可以按任何顺序编写。
使用静态指令
using_static_directive导入嵌套类型和静态成员的类型声明到直接包含编译单元或命名空间体直接包含,使得每个部件和类型的标识符无资格使用。
1 using_static_directive 2 : 'using' 'static' type_name ';' 3 ;
在包含using_static_directive的编译单元或命名空间体中的成员声明中,可以直接引用直接包含在给定类型的声明中的可访问嵌套类型和静态成员(扩展方法除外)。例如:
1 namespace N1 2 { 3 class A 4 { 5 public class B{} 6 public static B M(){ return new B(); } 7 } 8 } 9 10 namespace N2 11 { 12 using static N1.A; 13 class C 14 { 15 void N() { B b = M(); } 16 } 17 }
上面,在N2
命名空间中的成员声明中,静态成员和嵌套类型N1.A
是直接可用的,因此该方法N
能够引用它们B
和M
成员N1.A
。
一个using_static_directive特别是不直接导入扩展方法为静态方法,但使它们可用于扩展方法调用(扩展方法调用)。在这个例子中
1 namespace N1 2 { 3 static class A 4 { 5 public static void M(this string s){} 6 } 7 } 8 9 namespace N2 10 { 11 using static N1.A; 12 13 class B 14 { 15 void N() 16 { 17 M("A"); // Error, M unknown 18 "B".M(); // Ok, M known as extension method 19 N1.A.M("C"); // Ok, fully qualified 20 } 21 } 22 }
该using_static_directive进口扩展方法M
包含在N1.A
,但只作为一个扩展方法。因此,在结果M
体中的第一个引用B.N
导致编译时错误,因为没有命名M
的成员在范围内。
using_static_directive只有出口构件和直接在给定类型声明类型,而不是成员和在基类声明类型。
ALL:示例
使用命名空间指令中讨论了多个using_namespace_directives和using_static_directives之间的歧义。
命名空间成员
namespace_member_declaration或者是namespace_declaration(命名空间声明)或type_declaration(类型声明)。
1 namespace_member_declaration 2 : namespace_declaration 3 | type_declaration 4 ;
编译单元或命名空间体可以包含namespace_member_declaration,并且此类声明将新成员提供给包含编译单元或命名空间体的基础声明空间。
输入声明
type_declaration是class_declaration(类声明),一个struct_declaration(struct声明),一个interface_declaration(接口声明),一个enum_declaration(enum声明),或一个delegate_declaration(委托声明)。
1 type_declaration 2 : class_declaration 3 | struct_declaration 4 | interface_declaration 5 | enum_declaration 6 | delegate_declaration 7 ;
type_declaration可发生在编译单元或作为命名空间,类,或结构内一个成员声明的顶层声明。
当类型的类型声明T
作为编译单元中的顶级声明出现时,新声明的类型的完全限定名称就是简单的T
。当一个类型的类型声明T
一个命名空间,类或结构内时,新声明的类型的完全限定名N.T
,其中N
是包含命名空间,类或结构的完全合格的名称。
在类或结构中声明的类型称为嵌套类型(嵌套类型)。
允许的访问修饰符和类型声明的默认访问权限取决于声明发生的上下文(声明的可访问性):
- 在编译单元或命名空间中声明的类型可以具有
public
或internal
访问。默认为internal
访问。 - 在类中声明的类型可以有
public
,protected internal
,protected
,internal
,或private
访问。默认为private
访问。 - 在结构中声明的类型可以有
public
,internal
或private
访问。默认为private
访问。
命名空间别名限定符
命名空间别名限定符 ::
能够保证类型名称查找是通过引入新的类型和成员的影响。命名空间别名限定符始终出现在两个标识符之间,称为左侧和右侧标识符。与常规.
限定符不同,限定符的左侧标识符::
仅作为extern或使用别名查找。
qualified_alias_member定义如下:
1 qualified_alias_member 2 : identifier '::' identifier type_argument_list? 3 ;
qualified_alias_member可以用作一个namespace_or_type_name(命名空间和类型名称),或者如在左操作数member_access(成员访问)。
一个qualified_alias_member有两种形式:
N::I<A1, ..., Ak>
,whereN
和I
表示标识符,<A1, ..., Ak>
是一个类型参数列表。(K
总是至少有一个。)N::I
其中N
和I
表示标识符。(在这种情况下,K
被认为是零。)
使用此表示法,qualified_alias_member的含义确定如下:
-
如果
N
是标识符global
,则搜索全局命名空间I
:- 如果全局命名空间包含名为的命名空间
I
且K
为零,则qualified_alias_member引用该命名空间。 - 否则,如果全局命名空间包含一个名为的非泛型类型
I
且K
为零,则qualified_alias_member引用该类型。 - 否则,如果全局命名空间包含名为
I
具有K
类型参数的类型,则qualified_alias_member引用使用给定类型参数构造的类型。 - 否则,qualified_alias_member未定义,并发生编译时错误。
- 如果全局命名空间包含名为的命名空间
-
否则,从直接包含qualified_alias_member(如果有)的名称空间声明(Namespace声明)开始,继续每个封闭的名称空间声明(如果有的话),并以包含qualified_alias_member的编译单元结束,评估以下步骤,直到实体为位于:
- 如果名称空间声明或编译单元包含与类型关联的using_alias_directive
N
,则qualified_alias_member未定义,并发生编译时错误。 - 否则,如果名称空间声明或编译单元包含与命名空间关联的extern_alias_directive或using_alias_directive
N
,则:- 如果与之关联的命名空间
N
包含名为的命名空间I
且K
为零,则qualified_alias_member引用该命名空间。 - 否则,如果与之关联的名称空间
N
包含名为的非泛型类型I
且K
为零,则qualified_alias_member将引用该类型。 - 否则,如果与之关联的名称空间
N
包含名为I
具有K
类型参数的类型,则qualified_alias_member引用使用给定类型参数构造的类型。 - 否则,qualified_alias_member未定义,并发生编译时错误。
- 如果与之关联的命名空间
- 如果名称空间声明或编译单元包含与类型关联的using_alias_directive
- 否则,qualified_alias_member未定义,并发生编译时错误。
请注意,使用带有引用类型的别名的命名空间别名限定符会导致编译时错误。另请注意,如果标识符N
为global
,则在全局命名空间中执行查找,即使存在global
与类型或命名空间关联的using别名也是如此。
别名的唯一性
每个编译单元和命名空间体都有一个单独的声明空间,用于外部别名和使用别名。因此,虽然extern别名或使用别名的名称在extern别名集合中必须是唯一的,并且使用在包含直接包含的编译单元或命名空间体中声明的别名,但允许别名与类型或名称空间具有相同的名称。只要它与::
限定符一起使用。
在这个例子中
1 namespace N 2 { 3 public class A {} 4 5 public class B {} 6 } 7 8 namespace N 9 { 10 using A = System.IO; 11 12 class X 13 { 14 A.Stream s1; // Error, A is ambiguous 15 16 A::Stream s2; // Ok 17 } 18 }
该名称A
在第二个名称空间体中有两种可能的含义,因为类A
和使用别名A
都在范围内。因此,A
限定名称A.Stream
中的使用不明确并导致发生编译时错误。然而,使用A
与::
预选赛并不是因为一个错误A
的抬头只能作为一个命名空间别名。