- 1 A First C# Program
- 2 Namespaces
- 3 Alternative Forms of the Main() Function
- 4 Making a Statement
- 5 Opening a Text File for Reading and Writing
- 6 Formatting Output
- 7 The string Type
- 8 Local Objects
- 9 Value and Reference Types
- 10 The C# Array
- 11 The new Expression
- 12 Garbage Collection
- 13 Dynamic Arrays: The ArrayList Collection Class
- 14 The Unified Type System
- 15 Jagged Arrays
- 16 The Hashtable Container
- 17 Exception Handling
- 18 A Basic Language Handbook for C#
1.2 Namespaces
Namespaces are a mechanism for controlling the visibility of names within a program. They are intended to help facilitate the combination of program components from various sources by minimizing name conflicts between identifiers. Before we look at the namespace mechanism, let's make sure we understand the problem that namespaces were invented to solve.
Names not placed within a namespace are automatically placed in a single unnamed global declaration space. These names are visible throughout the program, regardless of whether they occur in the same or a separate program file. Each name in the global declaration space must be unique for the program to build. Global names make it difficult to incorporate independent components into our programs.
For example, imagine that you develop a two-dimensional (2D) graphics component and name one of your global classes Point. You use your component, and everything works fine. You tell some of your friends about it, and they naturally want to use it as well.
Meanwhile, I develop a three-dimensional (3D) graphics component and in turn name one of my global classes Point. I use my component, and everything also works fine. I show it to some of my friends. They're excited about it and wish to use it as well. So far, everyone is happywell, at least about our coding projects.
Now imagine that we have a friend in common. She's implementing a 2D/3D game engine and would like use our two components, both of which come highly praised. Unfortunately, when she includes both within her application, the two independent uses of the Point identifier result in a compile-time error. Her game engine fails to build. Because she does not own either component, there is no easy fix for the two components to work together.
Namespaces provide a general solution to the problem of global name collision. A namespace is given a name within which the classes and other types we define are encapsulated.2 That is, the names placed within a namespace are not visible within the general program. We say that a namespace represents an independent declaration space or scope.
Let's help our mutual friend by providing separate namespaces for our two Point class instances:
namespace DisneyAnimation_2DGraphics { public class Point { ... } // ... } namespace DreamWorksAnimation_3DGraphics { public class Point { ... } // ... }
The keyword namespace introduces the namespace definition. Following that is a name that uniquely identifies the namespace. (If we reuse the name of an existing namespace, the compiler assumes that we wish to add additional declarations to the existing namespace. The fact that the two uses of the namespace name do not collide allows us to spread the namespace declaration across files.)
The contents of each namespace are placed within a pair of curly braces. Our two Point classes are no longer visible to the general program; each is nested within its respective namespace. We say that each is a member of its respective namespace.
The using directive in this case is too much of a solution. If our friend opens both namespaces to the program
using DisneyAnimation_2DGraphics; using DreamWorksAnimation_3DGraphics;
the unqualified use of the Point identifier still results in a compile-time error. To unambiguously reference this or that Point class, we must use the fully qualified namefor example,
DreamWorksAnimation_3DGraphics.Point origin;
If we read it from right to left, this declares origin to be an instance of class Point defined within the DreamWorksAnimation_3DGraphics namespace.
The ambiguity within and between namespaces is handled differently depending on the perceived amount of control we have over the name conflict. In the simplest case, two uses of the same name occur within a single declaration space, triggering an immediate compile-time error when the second use of the name is encountered. The assumption is that the affected programmer has the ability to modify or rename identifiers within the working declaration space where the name conflict occurs.
It becomes less clear what should happen when the conflict occurs across namespaces. In one case, we open two independent namespaces, each of which contains a use of the same name, such as the two instances of Point. If we make explicit use of the multiply-defined Point identifier, an error is generated; the compiler does not try to prioritize one use over the other or otherwise disambiguate the reference. One solution, as we did earlier, is to qualify each identifier's access. Alternatively, we can define an alias for either one or all of the multiply-defined instances. We do this with a variant of the using directive, as follows:
namespace GameApp { // exposes the two instances of Point using DisneyAnimation_2DGraphics; using DreamWorksAnimation_3DGraphics; // OK: create unique identifiers for each instance using Point2D = DisneyAnimation_2DGraphics.Point; using Point3D = DreamWorksAnimation_3DGraphics.Point; class myClass { Point2D thisPoint; Point3D thatPoint; } }
The alias is valid only within the current declaration space. That is, it doesn't introduce an additional permanent type name associated with the class. If we try to use it across namespaces, such as in the following:
namespace GameEngine { class App { // error: not recognized private GameApp.Point2D origin; } }
the compiler wants nothing to do with it, generating the following message:
When we use a namespace, we generally have no idea how many names are defined within it. It would be very disruptive if each time we added an additional namespace, we had to hold our breath while we recompiled to see if anything would now break. The language, therefore, minimizes the disruption that opening a namespace can cause to our program.
If two or more instances of an identifier, such as our two Point classes, are made visible within our working declaration space through multiple using directives, an error is triggered only with an unqualified use of the identifier. If we don't access the identifier, the ambiguity remains latent, and neither an error nor a warning is issued.
If an identifier is made visible through a using directive that duplicates an identifier we have defined, our identifier has precedence. An unqualified use of the identifier always resolves to our defined instance. In effect, our instance hides the visibility of the identifier contained within the namespace. The program continues to work exactly as it had prior to our use of the additional namespace.
What sorts of names should be given to our namespaces? Generic names such as Drawing, Data, Math, and so on are unlikely to be unique. A recommended strategy is to add a prefix that identifies your organization or project group.
Namespaces are a necessary element of component development. As we've seen, they facilitate the reuse of our software in other program environments. For less ambitious programs, however, such as the Hello program at the start of this chapter, the use of a namespace is unnecessary.