Namespaces

Namespaces

Namespaces provide hierarchical clarity and organization of types and other members. A container of hundreds of classes, the .NET Framework Class Library (FCL) is an example of effective use of namespaces. The Framework Class Library would sacrifice clarity if planned as a single namespace with a flat hierarchy. Instead, the Framework Class Library organizes its members into numerous namespaces. System, which is the root namespace of the FCL, contains the classes ubiquitous to .NET, such as the Console class. Types related to data services are grouped in the System.Data namespace. Data services are further delineated in the System.Data.SqlClient namespace, which contains types specific to Microsoft SQL. The remaining types are organized similarly in other namespaces.

A namespace identifier must be unique within the namespace declaration space, which contains the current namespace but not a nested namespace. A nested namespace is considered a member of the containing namespace. Use the dot punctuator (.) to access members of the namespace.

A namespace at file scope, not nested within another namespace, is considered part of the compilation unit and included in the global namespace. A compilation unit is a source code file. A program partitioned into several source files has multiple compilation units—one compilation unit for each source file. Any namespace, including the global namespace, can span multiple compilation units. For example, all types defined at file scope are included into a single global namespace that spans separate source files.

The following code has two compilation units and three namespaces. Because of identical identifiers sharing the same scope, errors occur when the program is compiled.

// file1.cs

public class ClassA {
}

public class ClassB {
}

namespace NamespaceZ {
   public class ClassC {
   }
}

// file2.cs

public class ClassB {
}

namespace NamespaceY {
    public class ClassA {
    }
}

namespace NamespaceZ {

    public class ClassC {
    }

    public class ClassD {
    }
}

Compile the code into a library:

csc /t:library file1.cs file2.cs

In the preceding code, the global namespace has four members. NamespaceY and NamespaceZ are members. The classes ClassA and ClassB are also members of the global namespaces. The members span the File1.cs and File2.cs compilation units, which both contribute to the global namespace.

ClassB and ClassC are ambiguous. ClassB is ambiguous because it is defined twice in the global namespace, once in each compilation unit. ClassC is defined in the NamespaceZ namespace in both compilation units. Because NamespaceZ is one cohesive namespace, ClassC is also ambiguous.

The relationship between compilation units, the global namespace, and nonglobal namespaces are illustrated in the Figure 1-3.

Image from book
Figure 1-3: Global vs. non-global namespaces

The using directive makes a namespace implicit. You can access members of the named namespace directly without the fully qualified name. Do you refer to members of your family by their fully qualified names or just their first names? Unless your mother is the queen, you probably refer to everyone directly by simply using their first names, for the sake of convenience. The using directive means that you can treat members of a namespace like family members.

The using directive must precede any members in the namespace where it is defined. The following code defines the namespace member ClassA. The fully qualified name is NamespaceZ.NamespaceY.ClassA. Imagine having to type that several times in a program!

using System;

namespace NamespaceZ {

    namespace NamespaceY {
        class ClassA {
            public static void FunctionM() {
                Console.WriteLine("FunctionM");
            }
        }
    }
}

namespace Application {
    class Starter {
        public static void Main() {
            NamespaceZ.NamespaceY.ClassA.FunctionM();
        }
    }
}

The following using directive makes NamespaceZ.NamespaceY implicit. Now you can directly access ClassA.

namespace Application {
    using NamespaceZ.NamespaceY;
    class Starter {
        public static void Main() {
            ClassA.FunctionM();
        }
    }
}

Ambiguities can occur when separate namespaces with identically named members are made implicit. When this occurs, the affected members can be assessed only with their fully qualified names.

The using directive can also define an alias for a namespace or type. Aliases are typically created to resolve ambiguity or as a convenience. The scope of the alias is the declaration space where it is declared. The alias must be unique within that declaration space. In this source code, an alias is created for the fully qualified name of ClassA:

namespace Application {
    using A=NamespaceZ.NamespaceY.ClassA;
    class Starter {
        public static void Main() {
            A.FunctionM();
        }
    }
}

In this code, A is a nickname for NamespaceZ.NamespaceY.ClassA and can be used synonymously.

Using directive statements are not cumulative and are evaluated independently.