9. Namespaces

C# programs are organized using namespaces. Namespaces are used both as an “internal” organization system for a program and as an “external” organization system—a way of presenting program elements that are exposed to other programs.

Using directives (§9.4) are provided to facilitate the use of namespaces.

9.1 Compilation Units

A compilation-unit defines the overall structure of a source file. A compilation unit consists of zero or more using-directives followed by zero or more global-attributes followed by zero or more namespace-member-declarations.

        compilation-unit:
                extern-alias-directivesopt using-directivesopt global-attributesopt
                        namespace-member-declarationsopt

A C# program consists of one or more compilation units, each contained in a separate source file. When a C# program is compiled, all of the compilation units are processed together. As a result, compilation units can depend on each other, possibly in a circular fashion.

The using-directives of a compilation unit affect the global-attributes and namespace-member-declarations of that compilation unit, but have no effect on other compilation units.

The global-attributes (§17) of a compilation unit permit the specification of attributes for the target assembly and module. Assemblies and modules act as physical containers for types. An assembly may consist of several physically separate modules.

The namespace-member-declarations of each compilation unit of a program contribute members to a single declaration space called the global namespace. For example,

File A.cs:

        class A {}

File B.cs:

        class B {}

The two compilation units contribute to the single global namespace, in this case declaring two classes with the fully qualified names A and B. Because the two compilation units contribute to the same declaration space, it would have been an error if each contained a declaration of a member with the same name.

9.2 Namespace Declarations

A namespace-declaration consists of the keyword namespace, followed by a namespace name and body, optionally followed by a semicolon.

        namespace-declaration:
                namespace qualified-identifier namespace-body ;opt

        qualified-identifier:
                identifier
                qualified-identifier  .  identifier

        namespace-body:
                { extern-alias-directivesopt using-directivesopt namespace-member-declarationsopt }

A namespace-declaration may occur as a top-level declaration in a compilation-unit or as a member declaration within another namespace-declaration. When a namespace-declaration occurs as a top-level declaration in a compilation-unit, the namespace becomes a member of the global namespace. When a namespace-declaration occurs within another namespace-declaration, the inner namespace becomes a member of the outer namespace. In either case, the name of a namespace must be unique within the containing namespace.

Namespaces are implicitly public, and the declaration of a namespace cannot include any access modifiers.

Within a namespace-body, the optional using-directives import the names of other namespaces and types, allowing them to be referenced directly instead of through qualified names. The optional namespace-member-declarations contribute members to the declaration space of the namespace. Note that all using-directives must appear before any member declarations.

The qualified-identifier of a namespace-declaration may be a single identifier or a sequence of identifiers separated by “.” tokens. The latter form permits a program to define a nested namespace without lexically nesting several namespace declarations. For example,

        namespace N1.N2
        {
                class A {}
                class B {}
        }

is semantically equivalent to

        namespace N1
        {
                namespace N2
                {
                        class A {}
                        class B {}
                }
        }

Namespaces are open-ended, and two namespace declarations with the same fully qualified name contribute to the same declaration space (§3.3). In the example

        namespace N1.N2
        {
                class A {}
        }
        namespace N1.N2
        {
                class B {}
        }

the two namespace declarations contribute to the same declaration space, in this case declaring two classes with the fully qualified names N1.N2.A and N1.N2.B. Because the two declarations contribute to the same declaration space, it would have been an error if each contained a declaration of a member with the same name.

9.3 Extern Aliases

An extern-alias-directive introduces an identifier that serves as an alias for a namespace. The specification of the aliased namespace is external to the source code of the program; it also applies to nested namespaces of the aliased namespace.

        extern-alias-directives:
                extern-alias-directive
                extern-alias-directives  extern-alias-directive
                extern-alias-directive:
                extern alias identifier ;

The scope of an extern-alias-directive extends over the using-directives, global-attributes, and namespace-member-declarations of its immediately containing compilation unit or namespace body.

Within a compilation unit or namespace body that contains an extern-alias-directive, the identifier introduced by the extern-alias-directive can be used to reference the aliased namespace. It is a compile-time error for the identifier to be the word global.

An extern-alias-directive makes an alias available within a particular compilation unit or namespace body, but it does not contribute any new members to the underlying declaration space. In other words, an extern-alias-directive is not transitive, but rather affects only the compilation unit or namespace body in which it occurs.

The following program declares and uses two extern aliases, X and Y, each of which represents the root of a distinct namespace hierarchy:

        extern alias X;
        extern alias Y;
        class Test
        {
                X::N.A a;
                X::N.B b1;
                Y::N.B b2;
                Y::N.C c;
        }

The program declares the existence of the extern aliases X and Y, but the actual definitions of the aliases are external to the program. The identically named N.B classes can now be referenced as X.N.B and Y.N.B, or, using the namespace alias qualifier, as X::N.B and Y::N.B. An error occurs if a program declares an extern alias for which no external definition is provided.

9.4 Using Directives

Using directives facilitate the use of namespaces and types defined in other namespaces. Using directives affect the name resolution process of namespace-or-type-names (§3.8) and simple-names (§7.5.2). Unlike declarations, however, using directives do not contribute new members to the underlying declaration spaces of the compilation units or namespaces within which they are used.

        using-directives:
                using-directive
                using-directives using-directive

        using-directive:
                using-alias-directive
                using-namespace-directive

A using-alias-directive9.4.1) introduces an alias for a namespace or type.

A using-namespace-directive9.4.2) imports the type members of a namespace.

The scope of a using-directive extends over the namespace-member-declarations of its immediately containing compilation unit or namespace body. The scope of a using-directive specifically does not include its peer using-directives. Thus peer using-directives do not affect each other, and the order in which they are written is insignificant.

9.4.1 Using Alias Directives

A using-alias-directive introduces an identifier that serves as an alias for a namespace or type within the immediately enclosing compilation unit or namespace body.

        using-alias-directive:
                using identifier = namespace-or-type-name ;

Within member declarations in a compilation unit or namespace body that contains a using-alias-directive, the identifier introduced by the using-alias-directive can be used to reference the given namespace or type. For example,

        namespace N1.N2
        {
                class A {}
        }
        namespace N3
        {
                using A = N1.N2.A;
                class B: A {}
        }

In this example, within member declarations in the N3 namespace, A is an alias for N1.N2.A and, therefore, class N3.B derives from class N1.N2.A. The same effect can be obtained by creating an alias R for N1.N2 and then referencing R.A:

        namespace N3
        {
                using R = N1.N2;
                class B: R.A {}
        }

The identifier of a using-alias-directive must be unique within the declaration space of the compilation unit or namespace that immediately contains the using-alias-directive. For example,

        namespace N3
        {
                class A {}
        }
        namespace N3
        {
                using A = N1.N2.A;                // Error, A already exists
        }

In this example, N3 already contains a member A, so it is a compile-time error for a using-alias-directive to use that identifier. Likewise, it is a compile-time error for two or more using-alias-directives in the same compilation unit or namespace body to declare aliases by the same name.

A using-alias-directive makes an alias available within a particular compilation unit or namespace body, but it does not contribute any new members to the underlying declaration space. In other words, a using-alias-directive is not transitive, but rather affects only the compilation unit or namespace body in which it occurs. In the example

        namespace N3
        {
                using R = N1.N2;
        }
        namespace N3
        {
                class B: R.A {}                        // Error, R unknown
        }

the scope of the using-alias-directive that introduces R extends only to member declarations in the namespace body in which it is contained, so R is unknown in the second namespace declaration. Placing the using-alias-directive in the containing compilation unit causes the alias to become available within both namespace declarations:

        using R = N1.N2;
        namespace N3
        {
                class B: R.A {}
        }
        namespace N3
        {
                class C: R.A {}
        }

Just like regular members, names introduced by using-alias-directives are hidden by similarly named members in nested scopes. In the example

        using R = N1.N2;
        namespace N3
        {
                class R {}
                class B: R.A {}                // Error, R has no member A
        }

the reference to R.A in the declaration of B causes a compile-time error because R refers to N3.R, not N1.N2.

The order in which using-alias-directives are written has no significance, and resolution of the namespace-or-type-name referenced by a using-alias-directive is not affected by the using-alias-directive itself or by other using-directives in the immediately containing compilation unit or namespace body. In other words, the namespace-or-type-name of a using-alias-directive is resolved as if the immediately containing compilation unit or namespace body had no using-directives. A using-alias-directive may, however, be affected by extern-alias-directives in the immediately containing compilation unit or namespace body. In the example

        namespace N1.N2 {}
        namespace N3
        {
                extern alias E;
                using R1 = E.N;                // Okay
                using R2 = N1;                 // Okay
                using R3 = N1.N2;         // Okay
                using R4 = R2.N2;         // Error, R2 unknown
        }

the last using-alias-directive results in a compile-time error because it is not affected by the first using-alias-directive. The first using-alias-directive does not result in an error because the scope of the extern alias E includes the using-alias-directive.

A using-alias-directive can create an alias for any namespace or type, including the namespace within which it appears and any namespace or type nested within that namespace.

Accessing a namespace or type through an alias yields exactly the same result as accessing that namespace or type through its declared name. For example, given

        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
                }
        }

the names N1.N2.A, R1.N2.A, and R2.A are equivalent—all refer to the class whose fully qualified name is N1.N2.A.

Using aliases can name a closed constructed type, but cannot name an unbound generic type declaration without supplying type arguments. For example,

        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>          // Okay, can name closed constructed type
                using Z<T> = N1.A<T>     // Error, using alias cannot have type parameters
        }

9.4.2 Using Namespace Directives

A using-namespace-directive imports the types contained in a namespace into the immediately enclosing compilation unit or namespace body, enabling the identifier of each type to be used without qualification.

        using-namespace-directive:
                using namespace-name ;

Within member declarations in a compilation unit or namespace body that contains a using-namespace-directive, the types contained in the given namespace can be referenced directly. For example,

        namespace N1.N2
        {
                class A {}
        }
        namespace N3
        {
                using N1.N2;
                class B: A {}
        }

In the preceding example, within member declarations in the N3 namespace, the type members of N1.N2 are directly available and, therefore, class N3.B derives from class N1.N2.A.

A using-namespace-directive imports the types contained in the given namespace, but specifically does not import nested namespaces. In the example

        namespace N1.N2
        {
                class A {}
        }
        namespace N3
        {
                using N1;
                class B: N2.A {}                // Error, N2 unknown
        }

the using-namespace-directive imports the types contained in N1, but not the namespaces nested in N1. Thus the reference to N2.A in the declaration of B results in a compile-time error because no members named N2 are in scope.

Unlike a using-alias-directive, a using-namespace-directive may import types whose identifiers are already defined within the enclosing compilation unit or namespace body. In effect, names imported by a using-namespace-directive are hidden by similarly named members in the enclosing compilation unit or namespace body. In the example,

        namespace N1.N2
        {
                class A {}
                class B {}
        }
        namespace N3
        {
                using N1.N2;
                class A {}
        }

within member declarations in the N3 namespace, A refers to N3.A rather than N1.N2.A.

When more than one namespace imported by using-namespace-directives in the same compilation unit or namespace body contain types by the same name, references to that name are considered ambiguous. In the example

        namespace N1
        {
                class A {}
        }
        namespace N2
        {
                class A {}
        }
        namespace N3
        {
                using N1;
                using N2;
                class B: A {}                                // Error, A is ambiguous
        }

both N1 and N2 contain a member A, and because N3 imports both, referencing A in N3 is a compile-time error. In this situation, the conflict can be resolved either through qualification of references to A, or by introducing a using-alias-directive that picks a particular A. For example,

        namespace N3
        {
                using N1;
                using N2;
                using A = N1.A;
                class B: A {}                                // A means N1.A
        }

Like a using-alias-directive, a using-namespace-directive does not contribute any new members to the underlying declaration space of the compilation unit or namespace, but rather affects only the compilation unit or namespace body in which it appears.

The namespace-name referenced by a using-namespace-directive is resolved in the same way as the namespace-or-type-name referenced by a using-alias-directive. Thus using-namespace-directives in the same compilation unit or namespace body do not affect one another and can be written in any order.

9.5 Namespace Members

A namespace-member-declaration is either a namespace-declaration9.2) or a type-declaration9.6).

        namespace-member-declarations:
                namespace-member-declaration
                namespace-member-declarations  namespace-member-declaration

        namespace-member-declaration:
                namespace-declaration
                type-declaration

A compilation unit or a namespace body can contain namespace-member-declarations, and such declarations contribute new members to the underlying declaration space of the containing compilation unit or namespace body.

9.6 Type Declarations

A type-declaration is a class-declaration10.1), a struct-declaration11.1), an interface-declaration13.1), an enum-declaration14.1), or a delegate-declaration15.1).

        type-declaration:
                class-declaration
                struct-declaration
                interface-declaration
                enum-declaration
                delegate-declaration

A type-declaration can occur as a top-level declaration in a compilation unit or as a member declaration within a namespace, class, or struct.

When a type declaration for a type T occurs as a top-level declaration in a compilation unit, the fully qualified name of the newly declared type is simply T. When a type declaration for a type T occurs within a namespace, class, or struct, the fully qualified name of the newly declared type is N.T, where N is the fully qualified name of the containing namespace, class, or struct.

A type declared within a class or struct is called a nested type (§10.3.8).

The permitted access modifiers and the default access for a type declaration depend on the context in which the declaration takes place (§3.5.1):

•  Types declared in compilation units or namespaces can have public or internal access. The default is internal access.

•  Types declared in classes can have public, protected internal, protected, internal, or private access. The default is private access.

•  Types declared in structs can have public, internal, or private access. The default is private access.

9.7 Namespace Alias Qualifiers

The namespace alias qualifier :: makes it possible to guarantee that type name lookups are unaffected by the introduction of new types and members. The namespace alias qualifier always appears between two identifiers referred to as the left-hand and right-hand identifiers. Unlike the regular . qualifier, the left-hand identifier of the :: qualifier is looked up only as an extern or using alias.

A qualified-alias-member is defined as follows:

        qualified-alias-member:
                identifier :: identifier type-argument-listopt

A qualified-alias-member can be used as a namespace-or-type-name3.8) or as the left operand in a member-access7.5.4).

A qualified-alias-member has one of two forms:

•  N::I<A1,, AK>, where N and I represent identifiers, and <A1,, AK> is a type argument list. (K is always at least 1.)

•  N::I, where N and I represent identifiers. (In this case, K is considered to be zero.)

Using this notation, the meaning of a qualified-alias-member is determined as follows:

•  If N is the identifier global, then the global namespace is searched for I:

-  If the global namespace contains a namespace named I and K is zero, then the qualified-alias-member refers to that namespace.

-  Otherwise, if the global namespace contains a nongeneric type named I and K is zero, then the qualified-alias-member refers to that type.

-  Otherwise, if the global namespace contains a type named I that has K type parameters, then the qualified-alias-member refers to that type constructed with the given type arguments.

-  Otherwise, the qualified-alias-member is undefined and a compile-time error occurs.

•  Otherwise, starting with the namespace declaration (§9.2) immediately containing the qualified-alias-member (if any), continuing with each enclosing namespace declaration (if any), and ending with the compilation unit containing the qualified-alias-member, the following steps are evaluated until an entity is located:

-  If the namespace declaration or compilation unit contains a using-alias-directive that associates N with a type, then the qualified-alias-member is undefined and a compile-time error occurs.

-  Otherwise, if the namespace declaration or compilation unit contains an extern-alias-directive or using-alias-directive that associates N with a namespace, then:

•  If the namespace associated with N contains a namespace named I and K is zero, then the qualified-alias-member refers to that namespace.

•  Otherwise, if the namespace associated with N contains a nongeneric type named I and K is zero, then the qualified-alias-member refers to that type.

•  Otherwise, if the namespace associated with N contains a type named I that has K type parameters, then the qualified-alias-member refers to that type constructed with the given type arguments.

•  Otherwise, the qualified-alias-member is undefined and a compile-time error occurs.

•  Otherwise, the qualified-alias-member is undefined and a compile-time error occurs.

Using the namespace alias qualifier with an alias that references a type causes a compile-time error. Also, if the identifier N is global, then lookup is performed in the global namespace, even if there is a using alias associating global with a type or namespace.

9.7.1 Uniqueness of Aliases

Each compilation unit and namespace body has a separate declaration space for extern aliases and using aliases. Thus, while the name of an extern alias or using alias must be unique within the set of extern aliases and using aliases declared in the immediately containing compilation unit or namespace body, an alias is permitted to have the same name as a type or namespace as long as it is used only with the :: qualifier.

In the example

        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;                        // Okay
                }
        }

the name A has two possible meanings in the second namespace body because both the class A and the using alias A are in scope. For this reason, use of A in the qualified name A.Stream is ambiguous and causes a compile-time error to occur. However, use of A with the :: qualifier is not an error because A is looked up only as a namespace alias.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset