14 个命名空间
14.1 常规
C# 程序使用命名空间进行组织。 命名空间既用作程序的“内部”组织系统,又用作“外部”组织系统,即呈现向其他程序公开的程序元素的方法。
提供了 Using 指令(§14.5),以方便命名空间的使用。
14.2 编译单元
compilation_unit包含零个或多个extern_alias_directive后跟零个或多个using_directive后跟零或一个global_attributes后跟零个或多个namespace_member_declarations。 compilation_unit定义输入的整体结构。
compilation_unit
: extern_alias_directive* using_directive* global_attributes?
namespace_member_declaration*
;
C# 程序由一个或多个编译单元组成。 编译 C# 程序后,所有编译单元将一起处理。 因此,编译单元可以相互依赖,可能以循环方式。
编译 单元extern_alias_directive会影响 该编译单元的using_directive、 global_attributes 和 namespace_member_declaration,但对其他编译单元没有影响。
编译 单元using_directive会影响 该编译单元的 global_attributes和 namespace_member_declaration,但对其他编译单元没有影响。
编译单元的global_attributes(§22.3)允许指定目标程序集和模块的属性。 程序集和模块充当类型的物理容器。 程序集可能包含多个物理上独立的模块。
程序的每个编译单元的 namespace_member_declaration为名为全局命名空间的单个声明空间贡献成员。
示例:
// File A.cs: class A {} // File B.cs: class B {}
这两个编译单元有助于单个全局命名空间,在本例中声明两个具有完全限定名称
A
的类和B
。 由于这两个编译单元对同一声明空间的贡献,因此如果每个编译单元都包含同名成员的声明,则这将是一个错误。end 示例
14.3 命名空间声明
namespace_declaration由关键字命名空间组成,后跟命名空间名称和正文,(可选)后跟分号。
namespace_declaration
: 'namespace' qualified_identifier namespace_body ';'?
;
qualified_identifier
: identifier ('.' identifier)*
;
namespace_body
: '{' extern_alias_directive* using_directive*
namespace_member_declaration* '}'
;
namespace_declaration可能作为compilation_unit中的顶级声明或作为另一个namespace_declaration中的成员声明发生。 当namespace_declaration作为compilation_unit中的顶级声明发生时,该命名空间将成为全局命名空间的成员。 当namespace_declaration在另一个namespace_declaration中发生时,内部命名空间将成为外部命名空间的成员。 在任一情况下,命名空间的名称在包含的命名空间中应是唯一的。
命名空间是 public
隐式的,命名空间的声明不能包含任何访问修饰符。
在namespace_body中,可选using_directive导入其他命名空间、类型和成员的名称,从而允许直接引用它们,而不是通过限定的名称进行引用。 可选 namespace_member_declaration为命名空间的声明空间贡献成员。 请注意,所有 using_directive应显示在任何成员声明之前。
namespace_declaration的qualified_identifier可以是单个标识符或由“”.
标记分隔的标识符序列。 后一种形式允许程序定义嵌套命名空间,而无需词法嵌套多个命名空间声明。
示例:
namespace N1.N2 { class A {} class B {} }
在语义上等效于
namespace N1 { namespace N2 { class A {} class B {} } }
end 示例
命名空间是开放式的,具有相同完全限定名称(§7.8.2)的两个命名空间声明为相同的声明空间(§7.3)。
示例:在以下代码中
namespace N1.N2 { class A {} } namespace N1.N2 { class B {} }
上述两个命名空间声明有助于相同的声明空间,在本例中声明两个具有完全限定名称
N1.N2.A
的类和N1.N2.B
。 由于这两个声明对同一声明空间的贡献,因此如果每个声明都包含同名成员的声明,则这将是一个错误。end 示例
14.4 Extern 别名指令
extern_alias_directive引入了一个标识符,该标识符用作命名空间的别名。 别名命名空间的规范在程序的源代码外部,也适用于别名命名空间的嵌套命名空间。
extern_alias_directive
: 'extern' 'alias' identifier ';'
;
extern_alias_directive的范围扩展到using_directive、global_attributes和其立即包含compilation_unit或namespace_body的namespace_member_declaration。
在包含extern_alias_directive的编译单元或命名空间正文中,extern_alias_directive引入的标识符可用于引用别名命名空间。 标识符是单词global
的编译时错误。
extern_alias_directive引入的别名与using_alias_directive引入的别名非常相似。 有关 extern_alias_directive 和 using_alias_directive的更详细讨论,请参阅 §14.5.2。
alias
是上下文关键字(§6.4.4),仅在它紧跟extern
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; }
程序声明存在外部别名
X
,Y
但别名的实际定义在程序外部。 现在,同名N.B
类可以引用为X.N.B
命名空间别名限定符,Y.N.B
或者使用命名空间别名限定符X::N.B
Y::N.B
。 end 示例
14.5 Using 指令
14.5.1 常规
Using 指令 有助于使用在其他命名空间中定义的命名空间和类型。 使用指令会影响 namespace_or_type_names(§7.8)和 simple_names(§12.8.4)的名称解析过程,但与声明不同,using_directive不会为使用它们的编译单元或命名空间的基础声明空间贡献新成员。
using_directive
: using_alias_directive
| using_namespace_directive
| using_static_directive
;
using_alias_directive(§14.5.2)引入了命名空间或类型的别名。
using_namespace_directive(§14.5.3)导入命名空间的类型成员。
using_static_directive(§14.5.4)导入类型的嵌套类型和静态成员。
using_directive的范围扩展到其立即包含编译单元或命名空间正文的namespace_member_declarations。 具体来说,using_directive的范围不包括其对等using_directive。 因此,对等 using_directive不影响彼此,写入顺序微不足道。 相比之下,extern_alias_directive的范围包括在同一编译单元或命名空间正文中定义的using_directive。
14.5.2 使用别名指令
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
。 可以通过创建别名R
N1.N2
来获取相同的效果,然后引用R.A
:namespace N3 { using R = N1.N2; class B : R.A {} }
end 示例
在编译单元或命名空间正文中包含extern_alias_directive的指令、全局属性和成员声明内,extern_alias_directive引入的标识符可用于引用关联的命名空间。
示例:例如:
namespace N1 { extern alias N2; class B : N2::A {} }
上面,在命名空间中的成员声明中,是某些命名空间的
N1
别名,N2
其定义在程序源代码外部。 类N1.B
派生自类N2.A
。 可以通过创建别名A
N2.A
来获取相同的效果,然后引用A
:namespace N1 { extern alias N2; using A = N2::A; class B : A {} }
end 示例
extern_alias_directive或using_alias_directive使别名在特定编译单元或命名空间正文中可用,但它不会为基础声明空间提供任何新成员。 换句话说,别名指令不是可传递的,而是只影响发生它的编译单元或命名空间正文。
示例:在以下代码中
namespace N3 { extern alias R1; using R2 = N1.N2; } namespace N3 { class B : R1::A, R2.I {} // Error, R1 and R2 unknown }
引入
R1
和R2
仅扩展到包含它们的命名空间正文中的成员声明的别名指令的范围,因此,第R1
R2
二个命名空间声明中未知。 但是,将别名指令置于包含编译单元中会导致别名在两个命名空间声明中都可用:extern alias R1; using R2 = N1.N2; namespace N3 { class B : R1::A, R2.I {} } namespace N3 { class C : R1::A, R2.I {} }
end 示例
compilation_unit或namespace_body中的每个extern_alias_directive或using_alias_directive都为compilation_unit或namespace_body的别名声明空间(§7.3)提供名称。 别名指令的标识符应在相应的别名声明空间中是唯一的。 别名标识符在全局声明空间或相应命名空间的声明空间中不需要唯一。
示例:
extern alias X; extern alias Y; using X = N1.N2; // Error: alias X already exists class Y {} // Ok
命名
X
的 using 别名会导致错误,因为同一编译单元中已经有一个别名X
。 命名的类与命名Y
Y
的前别名不冲突,因为这些名称被添加到不同的声明空间。 前者将添加到全局声明空间,后者将添加到此编译单元的别名声明空间。当别名与命名空间成员的名称匹配时,应适当限定两种名称的用法:
namespace N1.N2 { class B {} } namespace N3 { class A {} class B : A {} } namespace N3 { using A = N1.N2; using B = N1.N2.B; class W : B {} // Error: B is ambiguous class X : A.B {} // Error: A is ambiguous class Y : A::B {} // Ok: uses N1.N2.B class Z : N3.B {} // Ok: uses N3.B }
在第二个命名空间正文
N3
中,未限定使用B
结果会导致错误,因为N3
包含一个命名B
成员和命名空间正文,该成员也声明了一个名称B
的别名;同样。A
N3.B
类可以引用为N3.B
或global::N3.B
。 该别名A
可用于 限定的别名成员 (§14.8),例如A::B
。 别名B
本质上是无用的。 它不能用于 qualified_alias_member ,因为只有命名空间别名可用于 qualified_alias_member 和B
类型别名。end 示例
与常规成员一样,alias_directives引入的名称被嵌套作用域中类似命名的成员隐藏。
示例:在以下代码中
using R = N1.N2; namespace N3 { class R {} class B: R.A {} // Error, R has no member A }
在声明中引用
R.A
导致编译时错误,因为R
引用N3.R
,而不是N1.N2
。B
end 示例
写入extern_alias_directive的顺序没有意义。 同样,写入using_alias_directive的顺序没有意义,但所有using_alias_directives都应当位于同一编译单元或命名空间正文中的所有extern_alias_directive之后。 由using_alias_directive引用的namespace_or_type_name的解析不受using_alias_directive本身或立即包含编译单元或命名空间正文中的其他using_directive的影响,但可能会受到立即包含编译单元或命名空间正文中的extern_alias_directive的影响。 换句话说,解析using_alias_directive的namespace_or_type_name,就像立即包含编译单元或命名空间正文的编译单元或命名空间正文没有using_directive,但具有正确的extern_alias_directive集。
示例:在以下代码中
namespace N1.N2 {} namespace N3 { extern alias X; using R1 = X::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 不会导致错误,因为外部别名 X 的范围包括 using_alias_directive。
end 示例
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
的类声明。end 示例
虽然分部类型(§15.2.7)的每个部分都在同一命名空间中声明,但部分通常在不同的命名空间声明中编写。 因此,每个部分可以存在不同的 extern_alias_directives 和 using_directives。 在一个部分内解释简单名称(§12.8.4)时,只考虑将部分括起来的命名空间主体和编译单元extern_alias_directive s 和 using_directives。 这可能会导致同一标识符在不同部分具有不同含义。
示例:
namespace N { using List = System.Collections.ArrayList; partial class A { List x; // x has type System.Collections.ArrayList } } namespace N { using List = Widgets.LinkedList; partial class A { List y; // y has type Widgets.LinkedList } }
end 示例
使用别名可以命名封闭的构造类型,但不能在没有提供类型参数的情况下为未绑定泛型类型声明命名。
示例:
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 }
end 示例
14.5.3 使用命名空间指令
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
。end 示例
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
的成员在范围内。end 示例
与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
。end 示例
由于当多个导入的命名空间引入相同的类型名称时,名称可能不明确, 因此using_alias_directive 有助于消除引用的歧义。
示例:在以下代码中
namespace N1 { class A {} } namespace N2 { class A {} } namespace N3 { using N1; using N2; class B : A {} // Error, A is ambiguous }
同时
N1
包含N2
成员,并且由于N3
导入这两者A
,因此引用A
是N3
编译时错误。 在这种情况下,可以通过引用A
资格或通过引入选取特定A
对象的using_alias_directive来解决冲突。 例如:namespace N3 { using N1; using N2; using A = N1.A; class B : A {} // A means N1.A }
end 示例
此外,在同一编译单元或命名空间正文中由 using_namespace_directives 或 using_static_directive导入的多个命名空间或类型包含同一名称的类型或成员时,将该名称引用视为simple_name不明确。
示例:
namespace N1 { class A {} } class C { public static int A; } namespace N2 { using N1; using static C; class B { void M() { A a = new A(); // Ok, A is unambiguous as a type-name A.Equals(2); // Error, A is ambiguous as a simple-name } } }
N1
包含类型成员A
,并且C
包含静态字段A
,由于N2
导入两者,因此引用A
为simple_name是模棱两可和编译时错误。end 示例
与using_alias_directive一样,using_namespace_directive不会为编译单元或命名空间的基础声明空间提供任何新成员,而是只影响其中显示的编译单元或命名空间正文。
using_namespace_directive引用的namespace_name与using_alias_directive引用的namespace_or_type_name解析方式相同。 因此, 同一编译单元或命名空间正文中的using_namespace_directive不会影响彼此,并且可以按任意顺序编写。
14.5.4 使用静态指令
using_static_directive将直接包含在类型声明中的嵌套类型和静态成员导入到立即封闭的编译单元或命名空间正文中,从而使每个成员和类型的标识符无需限定即可使用。
using_static_directive
: 'using' 'static' type_name ';'
;
在包含 using_static_directive的编译单元或命名空间正文中的成员声明中,可以直接引用给定类型的声明中包含的可访问嵌套类型和静态成员(扩展方法除外)。
示例:
namespace N1 { class A { public class B {} public static B M() => new B(); } } namespace N2 { using static N1.A; class C { void N() { B b = M(); } } }
在前面的代码中,在命名空间的成员
N2
声明中,静态成员和嵌套类型的N1.A
直接可用,因此该方法N
能够引用B
和M
成员N1.A
。end 示例
using_static_directive专门不直接将扩展方法导入为静态方法,而是使它们可用于扩展方法调用(§12.8.9.3)。
示例:
namespace N1 { static class A { public static void M(this string s){} } } namespace N2 { using static N1.A; class B { void N() { M("A"); // Error, M unknown "B".M(); // Ok, M known as extension method N1.A.M("C"); // Ok, fully qualified } } }
using_static_directive导入包含
N1.A
的扩展方法M
,但仅作为扩展方法。 因此,在编译时错误的结果正文B.N
中对第一个引用M
,因为没有命名M
的成员在范围内。end 示例
using_static_directive仅导入直接在给定类型中声明的成员和类型,而不是在基类中声明的成员和类型。
示例:
namespace N1 { class A { public static void M(string s){} } class B : A { public static void M2(string s){} } } namespace N2 { using static N1.B; class C { void N() { M2("B"); // OK, calls B.M2 M("C"); // Error. M unknown } } }
using_static_directive导入包含
N1.B
的方法M2
,但不导入包含N1.A
的方法M
。 因此,在编译时错误的结果正文C.N
中引用M
,因为没有命名M
的成员在范围内。 开发人员必须添加第二using static
个指令,以指定还应导入方法N1.A
。end 示例
在 §14.5.3 中讨论了多个using_namespace_directives和using_static_directives之间的歧义。
14.6 命名空间成员声明
namespace_member_declaration是namespace_declaration(§14.3)或type_declaration(§14.7)。
namespace_member_declaration
: namespace_declaration
| type_declaration
;
编译单元或命名空间正文可以包含 namespace_member_declaration,此类声明为包含编译单元或命名空间正文的基础声明空间贡献新成员。
14.7 类型声明
type_declaration是class_declaration(§15.2)、struct_declaration(§16.2)、interface_declaration(§18.2)、enum_declaration(§19.2)或delegate_declaration(§20.2)。
type_declaration
: class_declaration
| struct_declaration
| interface_declaration
| enum_declaration
| delegate_declaration
;
type_declaration可以作为编译单元中的顶级声明或命名空间、类或结构中的成员声明发生。
当类型 T
的类型声明作为编译单元中的顶级声明发生时,类型声明的完全限定名称 (§7.8.2) 与声明的非限定名称 (§7.8.2) 相同。 当类型的T
类型声明在命名空间、类或结构声明中发生时,类型声明S.N
的完全限定名称(§7.8.3),其中S
包含命名空间、类或结构声明的完全限定名称,并且N
是声明的不限定名称。
在类或结构中声明的类型称为嵌套类型(§15.3.9)。
允许的访问修饰符和类型声明的默认访问取决于声明的发生上下文(§7.5.2):
- 编译单元或命名空间中声明的类型可以具有
public
或internal
访问权限。 默认值为internal
访问。 - 类中声明的类型可以具有
public
、、protected internal
protected
、private protected
、或internal
private
访问。 默认值为private
访问。 - 在结构中声明的类型可以具有
public
或internal
private
访问权限。 默认值为private
访问。
14.8 限定别名成员
14.8.1 常规
命名空间别名限定符::
可以保证类型名称查找不受新类型和成员的引入影响。 命名空间别名限定符始终出现在两个标识符之间,称为左侧标识符和右侧标识符。 与常规 .
限定符不同,限定符的 ::
左侧标识符仅查找为 extern 或使用别名。
qualified_alias_member提供对全局命名空间的显式访问和使用其他实体可能隐藏的别名。
qualified_alias_member
: identifier '::' identifier type_argument_list?
;
qualified_alias_member可用作namespace_or_type_name(§7.8)或member_access(§12.8.7)中的左操作数。
qualified_alias_member由两个标识符组成,称为左撇子和右手标识符,由令牌分隔::
,可以选择后跟type_argument_list。 当左侧标识符是全局标识符时,将搜索全局命名空间以查找右侧标识符。 对于任何其他左撇子标识符,该标识符将查找为 extern 或使用别名(§14.4 和 §14.5.2)。 如果没有此类别名或别名引用类型,则会发生编译时错误。 如果别名引用了命名空间,则会搜索该命名空间的右侧标识符。
qualified_alias_member有两种形式之一:
N::I<A₁, ..., Aₑ>
,其中N
和I
表示标识符,并且<A₁, ..., Aₑ>
是类型参数列表。 (e
始终是至少一个。N::I
,其中N
和I
表示标识符。 (在本例中,e
被视为零。
使用此表示法,将确定qualified_alias_member的含义,如下所示:
- 如果
N
为标识符global
,则搜索I
全局命名空间:- 如果全局命名空间包含名为
I
零e
的命名空间,则 qualified_alias_member 引用该命名空间。 - 否则,如果全局命名空间包含名为
I
e
零的非泛型类型,则qualified_alias_member引用该类型。 - 否则,如果全局命名空间包含具有类型参数的命名
I
e
类型,则qualified_alias_member引用使用给定类型参数构造的类型。 - 否则, qualified_alias_member 未定义且发生编译时错误。
- 如果全局命名空间包含名为
- 否则,从命名空间声明(§14.3)开始,立即包含 qualified_alias_member (如果有),继续每个封闭命名空间声明(如果有),以包含 qualified_alias_member的编译单元结尾,将评估以下步骤,直到实体位于:
- 如果命名空间声明或编译单元包含将 N 与类型关联的using_alias_directive ,则 未 定义qualified_alias_member并发生编译时错误。
- 否则,如果命名空间声明或编译单元包含与命名空间关联的
N
extern_alias_directive或using_alias_directive,则:- 如果与关联的
N
命名空间包含名为I
e
零的命名空间,则qualified_alias_member引用该命名空间。 - 否则,如果与关联的
N
命名空间包含名为I
e
零的非泛型类型,则qualified_alias_member引用该类型。 - 否则,如果与关联的
N
命名空间包含具有类型参数的类型I
e
,则qualified_alias_member引用使用给定类型参数构造的类型。 - 否则, qualified_alias_member 未定义且发生编译时错误。
- 如果与关联的
- 否则, qualified_alias_member 未定义且发生编译时错误。
示例:在代码中:
using S = System.Net.Sockets; class A { public static int x; } class C { public void F(int A, object S) { // Use global::A.x instead of A.x global::A.x += A; // Use S::Socket instead of S.Socket S::Socket s = S as S::Socket; } }
该类引用了该类
A
,并且该类型System.Net.Sockets.Socket
是使用S::Socket
.global::A
使用A.x
并S.Socket
改为会导致编译时错误,因为A
并S
会解析为参数。end 示例
注意:仅当用作qualified_alias_name的左侧标识符时,标识符
global
才具有特殊含义。 它不是关键字,也不是别名;它是上下文关键字(§6.4.4)。 在代码中:class A { } class C { global.A x; // Error: global is not defined global::A y; // Valid: References A in the global namespace }
using
global.A
会导致编译时错误,因为作用域中没有命名global
的实体。 如果某个名为全局的实体在范围内,则global
该global.A
实体将解析为该实体。使用
global
作为qualified_alias_member的左侧标识符始终会导致命名空间中的global
查找,即使存在名为别名global
的别名也是如此。 在代码中:using global = MyGlobalTypes; class A { } class C { global.A x; // Valid: References MyGlobalTypes.A global::A y; // Valid: References A in the global namespace }
global.A
解析为MyGlobalTypes.A
全局命名空间中的类并global::A
解析为类A
。end note
14.8.2 别名的唯一性
每个编译单元和命名空间正文都有一个单独的声明空间,用于外部别名和使用别名。 因此,尽管外部别名或使用别名的名称在外部别名集内是唯一的,并且使用在立即包含编译单元或命名空间正文中声明的别名,但只要别名仅用于 ::
限定符,则允许别名与类型或命名空间具有相同的名称。
示例:在以下各项中:
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
在限定名称A.Stream
中使用不明确,导致出现编译时错误。 但是,与限定符一A
起使用::
不是错误,因为A
只查找为命名空间别名。end 示例