Chapter 16: Namespaces, the Preprocessor, and Assemblies


This chapter discusses three C# features that give you greater control over the organization and accessibility of a program. These features are namespaces, the preprocessor, and assemblies.

Namespaces

The namespace was mentioned briefly in Chapter 2 because it is a concept fundamental to C#. In fact, every C# program makes use of a namespace in one way or another. We didn’t need to examine namespaces in detail before now because C# automatically provides a default, global namespace for your program. Thus, the programs in earlier chapters simply used the default namespace. In the real world, however, many programs will need to create their own namespaces or interact with other namespaces. Here, they are examined in detail.

A namespace defines a declarative region that provides a way to keep one set of names separate from another. In essence, names declared in one namespace will not conflict with the same names declared in another. The namespace used by the .NET Framework library (which is the C# library) is System. This is why you have included

 using System;

near the top of every program. As explained in Chapter 14, the I/O classes are defined within a namespace subordinate to System called System.IO. There are many other namespaces subordinate to System that hold other parts of the C# library.

Namespaces are important because there has been an explosion of variable, method, property, and class names over the past few years. These include library routines, third-party code, and your own code. Without namespaces, all of these names would compete for slots in the global namespace and conflicts would arise. For example, if your program defined a class called Finder, it could conflict with another class called Finder supplied by a third-party library that your program uses. Fortunately, namespaces prevent this type of problem, because a namespace localizes the visibility of names declared within it.

Declaring a Namespace

A namespace is declared using the namespace keyword. The general form of namespace is shown here:

 namespace name {    // members }

Here, name is the name of the namespace. Anything defined within a namespace is within the scope of that namespace. Thus, namespace defines a scope. Within a namespace you can declare classes, structures, delegates, enumerations, interfaces, or another namespace.

Here is an example of a namespace that creates a namespace called Counter. It localizes the name used to implement a simple countdown counter class called CountDown.

 // Declare a namespace for counters. namespace Counter {   // A simple countdown counter.   class CountDown {     int val;     public CountDown(int n) {       val = n;    }     public void reset(int n) {       val = n;     }     public int count() {       if(val > 0) return val--;       else return 0;     }   } }

Notice how the class CountDown is declared within the scope defined by the Counter namespace.

Here is a program that demonstrates the use of the Counter namespace:

 // Demonstrate a namespace. using System; // Declare a namespace for counters. namespace Counter {   // A simple countdown counter.   class CountDown {     int val;     public CountDown(int n) { val = n; }     public void reset(int n) {       val = n;     }     public int count() {       if(val > 0) return val--;       else return 0;     }   } } class NSDemo {   public static void Main() {     // Notice how CountDown is qualified by Counter.     Counter.CountDown cd1 = new Counter.CountDown(10);     int i;     do {       i = cd1.count();       Console.Write(i + " ");     } while(i > 0);     Console.WriteLine();     // Again, notice how CountDown is qualified by Counter.     Counter.CountDown cd2 = new Counter.CountDown(20);     do {       i = cd2.count();       Console.Write(i + " ");     } while(i > 0);     Console.WriteLine();     cd2.reset(4);     do {       i = cd2.count();       Console.Write(i + " ");     } while(i > 0);     Console.WriteLine();   } }

The output from the program is shown here:

 10 9 8 7 6 5 4 3 2 1 0 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 4 3 2 1 0

There are some important aspects of this program that warrant close examination. First, since CountDown is declared within the Counter namespace, when an object is created, CountDown must be qualified with Counter, as shown here:

 Counter.CountDown cd1 = new Counter.CountDown(10);

However, once an object of type Counter has been created, it is not necessary to further qualify it or any of its members with the namespace. Thus, cd1.count( ) can be called directly without namespace qualification, as this line shows:

 i = cd1.count();

Namespaces Prevent Name Conflicts

The key point about a namespace is that names declared within it won’t conflict with similar names declared outside of it. For example, in the following program, another class called CountDown is created, but this one is in a namespace called Counter2:

 // Namespaces prevent name conflicts. using System; // Declare a namespace for counters. namespace Counter {   // A simple countdown counter.   class CountDown {     int val;     public CountDown(int n) {       val = n;     }     public void reset(int n) {       val = n;     }     public int count() {       if(val > 0) return val--;       else return 0;     }   } } // Declare another namespace. namespace Counter2 {   /* This CountDown is in the Counter2 namespace and      does not conflict with the one in Counter. */   class CountDown {     public void count() {       Console.WriteLine("This is count() in the " +                         "Counter2 namespace.");     }   } } class NSDemo2 {   public static void Main() {     // This is CountDown in the Counter namespace.     Counter.CountDown cd1 = new Counter.CountDown(10);     // This is CountDown in the Counter2 namespace.     Counter2.CountDown cd2 = new Counter2.CountDown();     int i;     do {       i = cd1.count();       Console.Write(i + " ");     } while(i > 0);     Console.WriteLine();     cd2.count();   } }

The output is shown here:

 10 9 8 7 6 5 4 3 2 1 0 This is count() in the Counter2 namespace.

As the output confirms, the CountDown class inside Counter is separate from the CountDown class in the Counter2 namespace, and no name conflicts arise. Although this example is quite simple, it is easy to see how putting classes that you write in a namespace helps prevent name conflicts between your code and code written by others.

using

If your program includes frequent references to the members of a namespace, having to specify the namespace each time you need to refer to one quickly becomes tedious. The using directive alleviates this problem. Throughout this book you have been using using to bring the C# System namespace into view, so you are already familiar with it. As you would expect, using can also be used to bring namespaces that you create into view.

There are two forms of the using directive. The first is shown here:

 using name;

Here, name specifies the name of the namespace you want to access. This is the form of using that you have already seen. All of the members defined within the specified namespace are brought into view (that is, they become part of the current namespace) and can be used without qualification. A using directive must be specified at the top of each file, prior to any other declarations.

The following program reworks the counter example from the previous section to show how you can employ using to bring a namespace that you create into view:

 // Demonstrate the using directive. using System; // Bring Counter into view. using Counter; // Declare a namespace for counters. namespace Counter {   // A simple countdown counter.   class CountDown {     int val;     public CountDown(int n) {       val = n;     }     public void reset(int n) {       val = n;     }     public int count() {       if(val > 0) return val--;       else return 0;     }   } } class NSDemo3 {   public static void Main() {     // Now, CountDown can be used directly.     CountDown cd1 = new CountDown(10);     int i;     do {       i = cd1.count();       Console.Write(i + " ");     } while(i > 0);     Console.WriteLine();     CountDown cd2 = new CountDown(20);     do {       i = cd2.count();       Console.Write(i + " ");     } while(i > 0);     Console.WriteLine();     cd2.reset(4);     do {       i = cd2.count();       Console.Write(i + " ");     } while(i > 0);     Console.WriteLine();   } }

This version of the program contains two important changes. The first is this using statement, near the top of the program:

 using Counter;

This brings the Counter namespace into view. The second is that it is no longer necessary to qualify CountDown with Counter, as this statement in Main( ) shows:

 CountDown cd1 = new CountDown(10);

Because Counter is now in view, CountDown can be used directly.

The program illustrates one other important point: using one namespace does not override another. When you bring a namespace into view, it simply adds its names to whatever other namespaces are currently in effect. Thus, both System and Counter have been brought into view.

A Second Form of using

The using directive has a second form, which is shown here:

 using alias = name;

Here, alias becomes another name for the class or namespace specified by name. The counting program is reworked once again so that an alias for Counter.CountDown called Count is created.

 // Demonstrate a using alias. using System; // Create an alias for Counter.CountDown. using Count = Counter.CountDown; // Declare a namespace for counters. namespace Counter {   // A simple countdown counter.   class CountDown {     int val;     public CountDown(int n) {       val = n;     }     public void reset(int n) {       val = n;     }     public int count() {       if(val > 0) return val--;       else return 0;     }   } } class NSDemo4 {   public static void Main() {     // Here, Count is used as a name for Counter.CountDown.     Count cd1 = new Count(10);     int i;     do {       i = cd1.count();       Console.Write(i + " ");     } while(i > 0);     Console.WriteLine();     Count cd2 = new Count(20);     do {       i = cd2.count();       Console.Write(i + " ");     } while(i > 0);     Console.WriteLine();     cd2.reset(4);     do {       i = cd2.count();       Console.Write(i + " ");     } while(i > 0);     Console.WriteLine();   } }

The Count alias is created using this statement:

 using Count = Counter.CountDown; 

Once Count has been specified as another name for Counter.CountDown, it can be used to declare objects without any further namespace qualification. For example, in the program, this line:

     Count cd1 = new Count(10);

creates a CountDown object.

Namespaces Are Additive

There can be more than one namespace declaration of the same name. This allows a namespace to be split over several files or even separated within the same file. For example, the following program defines two Counter namespaces. One contains the CountDown class. The other contains the CountUp class. When compiled, the contents of both Counter namespaces are added together.

 // Namespaces are additive. using System; // Bring Counter into view. using Counter; // Here is one Counter namespace. namespace Counter {   // A simple countdown counter.   class CountDown {     int val;     public CountDown(int n) {       val = n;     }     public void reset(int n) {       val = n;     }     public int count() {       if(val > 0) return val--;       else return 0;     }   } } // Here is another Counter namespace. namespace Counter {   // A simple count-up counter.   class CountUp {     int val;     int target;     public int Target {       get{         return target;       }     }     public CountUp(int n) {       target = n;       val = 0;     }     public void reset(int n) {       target = n;       val = 0;     }     public int count() {       if(val < target) return val++;       else return target;     }   } } class NSDemo5 {   public static void Main() {     CountDown cd = new CountDown(10);     CountUp cu = new CountUp(8);     int i;     do {       i = cd.count();       Console.Write(i + " ");     } while(i > 0);     Console.WriteLine();     do {       i = cu.count();       Console.Write(i + " ");     } while(i < cu.Target);   } }

This program produces the following output:

 10 9 8 7 6 5 4 3 2 1 0 0 1 2 3 4 5 6 7 8

Notice one other thing: The statement

 using Counter;

brings into view the entire contents of the Counter namespace. Thus, both CountDown and CountUp can be referred to directly, without namespace qualification. It doesn’t matter that the Counter namespace was split into two parts.

Namespaces Can Be Nested

One namespace can be nested within another. Consider this program:

 // Namespaces can be nested. using System; namespace NS1 {   class ClassA {      public ClassA() {        Console.WriteLine("constructing ClassA");     }   }   namespace NS2 { // a nested namespace     class ClassB {        public ClassB() {          Console.WriteLine("constructing ClassB");       }     }   } } class NestedNSDemo {   public static void Main() {     NS1.ClassA a = new NS1.ClassA();  // NS2.ClassB b = new NS2.ClassB(); // Error!!! NS2 is not in view     NS1.NS2.ClassB b = new NS1.NS2.ClassB(); // this is right   } }

This program produces the following output:

 constructing ClassA constructing ClassB

In the program, the namespace NS2 is nested within NS1. Thus, to refer to ClassB, you must qualify it with both the NS1 and NS2 namespaces. NS2 by itself is insufficient. As shown, the namespace names are separated by a period.

You can specify a nested namespace using a single namespace statement by separating each namespace with a period. For example,

 namespace OuterNS {   namespace InnerNS {     // ...   } }

can also be specified like this:

 namespace OuterNS.InnerNS {   // ... }

The Default Namespace

If you don’t declare a namespace for your program, then the default namespace is used. (The default namespace is also called the global namespace.) This is why you have not needed to use namespace for the programs in the preceding chapters. While the default namespace is convenient for the short, sample programs found in this book, most real-world code will be contained within a named namespace. The main reason for encapsulating your code within a namespace is that it helps prevent name conflicts. Namespaces are another tool that you have to help you organize programs and make them viable in today’s complex, networked environment.

Using the :: Namespace Alias Qualifier

Although namespaces help prevent name conflicts, they do not completely eliminate them. One way that a conflict can still occur is when the same name is declared within two different namespaces, and you then try to bring both namespaces into view. For example, assume that two different namespaces contain a class called MyClass. If you attempt to bring these two namespaces into view via using statements, MyClass in the first namespace will conflict with MyClass in the second namespace, causing an ambiguity error. In this situation, you can use the :: namespace alias qualifier to explicitly specify which namespace is intended. The :: namespace alias qualifier is a new feature added by C# 2.0.

The :: operator has this general form:

 namespace-alias::identifier 

Here, namespace-alias is the name of a namespace alias, and identifier is the name of a member of that namespace.

To understand why the namespace alias qualifier is needed, consider the following program. It creates two namespaces, Counter and AnotherCounter, and both declare a class called CountDown. Furthermore, both namespaces are brought into view by using statements. Finally, in Main( ), an attempt is made to instantiate an object of type CountDown.

 // Demonstrate why the :: qualifier is needed. // // This program will not compile. using System; // Use both the Counter and AnotherCounter namespace. using Counter; using AnotherCounter; // Declare a namespace for counters. namespace Counter {   // A simple countdown counter.   class CountDown {     int val;     public CountDown(int n) {       val = n;     }     // ...   } } // Declare another namespace for counters. namespace AnotherCounter {   // Declare another class called CountDown, which   // is in the AnotherCounter namespace.   class CountDown {     int val;     public CountDown(int n) {       val = n;     }     // ...   } } class WhyAliasQualifier {   public static void Main() {     int i;     // The following line is inherently ambiguous!     // Does it refer to CountDown in Counter or     // to CountDown in AnotherCounter?     CountDown cd1 = new CountDown(10); // Error! ! !     // ...   } }

If you try to compile this program, you will receive an error message that states that this line in Main( ) is ambiguous:

 CountDown cd1 = new CountDown(10); // Error! ! !

The trouble is that both namespaces, Counter and AnotherCounter, declare a class called CountDown, and both namespaces have been brought into view. Thus, to which version of CountDown does the preceding declaration refer? The :: qualifier was designed to handle this type of problem.

To use the ::, you must first define an alias for namespace that you want to qualify. Then, simply qualify the ambiguous element with the alias. For example, here is one way to fix the preceding program:

 // Demonstrate the :: qualifier. using System; using Counter; using AnotherCounter; // Give Counter an alias called Ctr. using Ctr = Counter; // Declare a namespace for counters. namespace Counter {   // A simple countdown counter.   class CountDown {     int val;     public CountDown(int n) {       val = n;     }     // ...   } } // Another counter namespace. namespace AnotherCounter {   // Declare another class called CountDown, which   // is in the AnotherCounter namespace.   class CountDown {     int val;     public CountDown(int n) {       val = n;     }     // ...   } } class AliasQualifierDemo {   public static void Main() {     // Here, the :: operator tells     // the compiler to use the CountDown     // that is in the Counter namespace.     Ctr::CountDown cd1 = new Ctr::CountDown(10);     // ...   } }

In this version, the alias Ctr is specified for Counter by the following line:

 using Ctr = Counter;

Then, inside Main( ), this alias is used to qualify CountDown, as shown here:

 Ctr::CountDown cd1 = new Ctr::CountDown(10);

The use of the :: qualifier removes the ambiguity because it specifies that it is the CountDown in Ctr (which stands for Counter) that is desired, and the program now compiles.

You can use the :: qualifier to refer to the global namespace by using the predefined identifier global. For example, in the following program, a class called CountDown is declared in both the Counter namespace and in the global namespace. To access the version of CountDown in the global namespace, the predefined alias global is used.

 // Use the global alias. using System; // Give Counter an alias called Ctr. using Ctr = Counter; // Declare a namespace for counters. namespace Counter {   // A simple countdown counter.   class CountDown {     int val;     public CountDown(int n) {       val = n;     }     // ...   } } // Declare another class called CountDown, which // is in the global namespace. class CountDown {   int val;   public CountDown(int n) {     val = n;   }   // ... } class GlobalAliasQualifierDemo {   public static void Main() {     // Here, the :: qualifier tells the compiler     // to use CountDown in the Counter namespace.     Ctr::CountDown cd1 = new Ctr::CountDown(10);     // Next, create CountDown object from global namespace.     global::CountDown cd2 = new global::CountDown(10);     // ...   } }

Notice how the global identifier is used to access the version of CountDown in the default namespace:

 global::CountDown cd2 = new global::CountDown(10);

This same basic approach can be generalized to any situation in which you need to specify the default namespace.

One final point: you can also use the namespace alias qualifier with extern aliases, which are described in Chapter 19.




C# 2.0(c) The Complete Reference
C# 2.0: The Complete Reference (Complete Reference Series)
ISBN: 0072262095
EAN: 2147483647
Year: 2006
Pages: 300

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net