Compile Units, Namespaces, and Assemblies

   


This section brings the assembly into our discussion that so far has concentrated on compile units and namespaces. The following discussion is built around a few simple examples whose goal is to give you an idea of the many ways you can configure and structure your classes (and other types) and programs around these three important elements.

Before beginning, you might want to refresh your memory about some of the more theoretical assembly aspects by looking at the "The Assembly, the Basic Unit of Code Reuse in .NET" section in Chapter 2, "Your First C# Program."

Compiling Several Compile Units into One Assembly

The routine of creating and running a program has so far consisted of the following steps:

  1. Write the source code in a text editor, such as Notepad.

  2. Save the text as a source code file (a compilation unit) with the .cs extension.

  3. Compile the .cs file with the csc command to create an executable file with the .exe extension (also called a Portable Executable (PE) assembly).

  4. Run the PE assembly by typing its name (without the exe extension).

In effect, we have written one source file and created one PE assembly in each example, as illustrated in Figure 15.3.

Figure 15.3. Compiling one compilation unit into one PE assembly.
graphics/15fig03.gif

This process has purposefully been kept simple and works well when focusing on and learning about most of C#'s language elements. However, for real-life projects that often contain many hundred of pages of code with a myriad of classes and other user-defined types involving several developers, this way of writing software is too rigid. The thought of one source code file with hundreds of pages surrounded by several developers pasting code in and out from here is like looking straight down into a pot of spaghetti. Instead, we need the ability to write many physically independent source files but, at the same time, let all source files contribute with type definitions to the same namespace hierarchy if needed. Figure 15.4 shows three compilation units being turned into one PE assembly by the compiler; C# allows us to combine an unlimited number of compilation units.

Figure 15.4. Compiling three compilation units into one PE assembly.
graphics/15fig04.gif

The following example shows how you can use the compiler to create an assembly from three source code files. Suppose that we are writing the elevator simulation program for this book that is going to be part of the Sams namespace presented earlier in Listing 15.3. For simplicity, we assume that there are only three classes in this program called Elevator, Person, and ElevatorSimulation (containing the Main method), and that all classes are cut down to the bones only. Conventionally, each class is written in its own source file that is named after the class. In this case, then, you need to create the three source code files shown in the next three listings (Listing 15.4,15.5 and 15.6). Each source code file is created in the usual manner with the .cs extension. To indicate that the three source code files are related, you should insert them into a folder called ElevatorSimulation.

Tip

graphics/bulb.gif

Conventionally, each class has its own source file. The source file is then usually named after the class it represents. If you are constructing a file called Elevator, save it in a file called Elevator.cs.


Listing 15.4 Elevator.cs
01: using System; 02: 03: namespace Sams.CSharpPrimerPlus.ElevatorSimulation 04: { 05:     internal class Elevator 06:     { 07:         public static void PrintClassName() 08:         { 09:             Console.WriteLine("This class is called Elevator"); 10:         } 11:     } 12: } 
Listing 15.5 Person.cs
01: using System; 02: 03: namespace Sams.CSharpPrimerPlus.ElevatorSimulation 04: { 05:     public class Person 06:     { 07:         public void Talk() 08:         { 09:             Console.WriteLine("Blah blah"); 10:         } 11:     } 12: } 
Listing 15.6 ElevatorSimulator.cs
01: using System; 02: 03: namespace Sams.CSharpPrimerPlus.ElevatorSimulation 04: { 05:     public class ElevatorSimulator 06:     { 07:         public static void Main() 08:         { 09:             Person somePerson = new Person(); 10:             somePerson.Talk(); 11:             Elevator.PrintClassName(); 12:         } 13:     } 14: } 

You should now have three source code files named Elevator.cs, Person.cs, and ElevatorSimulator.cs in the ElevatorSimulation folder.

All three source code files specify the same namespace Sams.CSharpPrimerPlus.ElevatorSimulation (see line 3 in all three listings) so, in effect, each source file is contributing with one class definition to this same namespace.

The ElevatorSimulator.cs source file can refer to the Person and Elevator classes (see lines 9 11) without providing their long qualified names, because the ElevatorSimulator class is being specified inside the same namespace as Person and Elevator.

Notice that the Elevator class has been declared internal (line 5 of Listing 15.4), and Person has been declared public (line 5 Listing 15.5). Both internal and public are access modifiers but, whereas public allows a class to be used outside the assembly where it resides, internal restricts the use of a class to its own assembly. In a moment, when our source code has been turned into an assembly and reused by another program, we will experience the difference between the two access modifiers. There is no direct motive for declaring Elevator internal in this example other than to demonstrate its effect.

The internal Access Modifier

graphics/common.gif

A class that does not have any access modifier is, by default, declared internal and cannot be used outside of its own assembly. You can also declare a class member to be internal. This allows you to let part of a class be accessible outside the assembly by declaring the class public but then restrict access to some of the members by declaring them internal.


Before we compile these three source files into the same assembly, it is worth introducing the few compiler commands shown in Table 15.1. They are all optional and, if used, must be positioned between the csc command and the source file names you provide. I will only present the /out: command now, but examples will be provided of all the commands as we continue our story about the three source files presented before. Don't worry if parts of Table 15.1 do not make sense to you now.

By default, the compiler uses the name of your source code file to name the assembly it created. To specify a different name for the assembly and to avoid any confusion as to which of the names the compiler chooses when several source files are compiled simultaneously, you can include the /out: compiler command followed by a filename of your choice, as shown in Table 15.1.

Table 15.1. A Few Selected Compiler Options
Compiler command Description
/out: <File_name> You can specify the name of the output file (the assembly) by writing this name after the /out: command.
/t[arget]: exe This command creates a PE (portable executable) assembly with the .exe extension, which constitutes an executable console application that will automatically open the console. This is the default setting of the compiler. (Tip: For Windows-based GUI applications, use the /t[arget]: winexe command instead. This will prevent the console window from opening.)
/t[arget]: library This will create a class library that cannot be executed on its own but that is only meant for reuse in other applications. Theassembly created will have the extension .dll.
/r[eference]:<Assembly_list> This references one or more assemblies (with extension .exe or .dll) by specifying the name of each referenced assembly separated by semicolons. By referencing an assembly, its namespaces and their types are made available for the assembly we are creating. Note: Each assembly included must be separated from other included assemblies with semicolons (see the example in the notes).

Notes:

  • All the mentioned commands are optional.

  • /t[arget]: means that arget is optional, so /t: and /target: have the same meaning. The same logic applies to /r[eference]:.

  • The commands are part of the csc command given at the command line in the console window. The commands must be positioned in between the csc command and the C# source code files as specified next with our familiar syntax notation.

     csc [/out: <File_name>] [/t[arget]: exe | /t[arget]: library] [ /r[eference]:  graphics/ccc.gif<Assembly_list>] <Source_file1> [<Source_file2> <Source_file3>...] 

    To create a .dll assembly called MyClassLibrary.dll that references ClassLib1.dll and ClassLib2.dll and compiles the source files Planet.cs and Rocket.cs, we need to write the following after the command prompt:

     csc /out:MyClassLibrary.dll /target:library /reference:ClassLib1; ClassLib2 Planet.cs  graphics/ccc.gifRocket.cs 

It is time to compile the three source code files from Listings 15.4,15.5 and 15.6. We decide to create an executable application called ElevatorSimulation.exe, which is done by giving the following command:

 csc /out:ElevatorSimulation.exe /t:exe Elevator.cs Person.cs ElevatorSimulator.cs 

Note that we could have omitted the /t:exe command because this command specifies the default option.

You can now run the ElevatorSimulation.exe program in the usual manner by typing its name, to create the following output:

 Blah blah This class is called Elevator 

This example is continued in the next section, so please keep the source code in your ElevatorSimulation folder handy.

Reusing the Namespaces Contained in an Assembly

A couple of programmers are working on a SpaceShuttle class and need a Person class with the ability to say Blah blah (to presumably test the acoustics inside a SpaceShuttle). They want to reuse our Sams.CSharpPrimerPlus.ElevatorSimulation.Person class that has this ability. To give them this privilege, they need to know two fundamental things (apart from how to use the class, which we assume has been taken care of):

  • The long, fully-qualified name of the class, so they can refer to the class in their source code. In other words, the short name of the class and the namespace where it resides.

  • The name and location of the assembly where the namespace of the class resides, so they can reference this assembly with the /reference: command (see Table 15.1 shown earlier) during the compilation of their source code.

The programmers initially write a quick piece of source code, shown in Listing 15.7, to verify that they can, in fact, reuse our Sams.CSharpPrimerPlus.ElevatorSimulation.Person class. If you want to compile the SpaceShuttle.cs program, please insert this source file in the same folder as the ElevatorSimulation.exe assembly and call it SpaceShuttle.cs. I will provide instructions on how to compile it after the analysis of the code.

Listing 15.7 SpaceShuttle.cs
01: using Sams.CSharpPrimerPlus.ElevatorSimulation; 02: 03: namespace SpaceShuttleSimulation 04: { 05:     class SpaceShuttle 06:     { 07:         private static Person astronaut; 08: 09:         public static void Main() 10:         { 11:             astronaut = new Person(); 12:             astronaut.Talk(); 13:         } 14:     } 15: } 

Just like we include using System; in the source code to let us write convenient shortcut names of the classes contained in the System namespace of the .NET Framework class library, the programmers insert line 1 in SpaceShuttle.cs to get convenient access to the classes of the Sams.CSharpPrimerPlus.ElevatorSimulation namespace that we created with the source code in Listings 15.4,15.5 and 15.6.

Because of line 1, we can use the short name Person in line 7 and 11 to refer to the class with the long qualified name Sams.CSharpPrimerPlus.ElevatorSimulation.Person.

When you compile the SpaceShuttle.cs source file, you need to specify to the compiler in which assemblies it can look for the namespaces you refer to in the source code. This is done with the /r[eference]: command. In our case, the namespace resides in the single assembly we called ElevatorSimulation.exe. This leads us to the following command that compiles the SpaceShuttle.cs source file into the executable assembly SpaceShuttle.exe:

 csc /r:ElevatorSimulation.exe SpaceShuttle.cs 

When you run the program, by writing the name of the program as usual, you should see the following output:

 Blah blah 

Note

graphics/common.gif

You can reference several assemblies in the compiler command if the namespaces you use in your source code exist in several assemblies. You will see a demonstration of this in a moment.


Note

graphics/common.gif

You might have been wondering why you do not need to reference any assemblies to use the classes of the System namespace. This is not necessary because the compiler, by default, references the mscorlib.dll assembly where the core namespaces, such as System, exist.


Note

graphics/common.gif

The .NET Framework class library is spread over many different DLL assemblies. To find out which particular DLL you need to reference to access a specific class, look at the .NET Framework Documentation page for this class. At the bottom of this page, you will see the following information, which is provided for the System.Windows.Forms.Form class in this case (used to create Windows GUI applications):

Requirements:

Namespace: System.Windows.Forms

Assembly: System.Windows.Forms.dll

It clearly states that the fully qualified name of the Form class is System.Windows.Forms.Form, and that you have to reference the System.Windows.Forms.dll assembly if you are using the Form class in your program.


Recall that the Sams.CSharpPrimerPlus.ElevatorSimulation.Elevator class in Listing 15.4 is declared with the internal keyword. The SpaceShuttle.exe assembly we have just created is clearly outside the confines of the ElevatorSimulation.exe assembly where Sams.CSharpPrimerPlus.ElevatorSimulation.Elevator now resides. Consequently, if we tried to use the Sams.CSharpPrimerPlus.ElevatorSimulation.Elevator class in the SpaceShuttle.cs to create the SpaceShuttle.exe, we would see an error message. Let's confirm this theory. Insert the following line after line 12 in Listing 15.7

 Elevator.PrintClassName(); 

and attempt to compile the SpaceShuttle.cs yet again with the same command as before:

 csc /r:ElevatorSimulation.exe SpaceShuttle.cs 

In response, we now get the following compiler error:

 SpaceShuttle.cs(13,13): error CS0122: 'Sams.CSharpPrimerPlus.ElevatorSimulation. Elevator' is inaccessible due to its protection level 

which clearly confirms our speculation.

Note

graphics/common.gif

The SpaceShuttle.exe depends on the existence of the ElevatorSimulation.exe to be executed. Removing ElevatorSimulation.exe from its folder causes the .NET runtime to generate a System.TypeLoadException whenever you try to run SpaceShuttle.exe.


Separating Namespaces into Several Assemblies

From the SpaceShuttle.cs code, our story returns to the source files Elevator.cs, Person.cs, and ElevatorSimulator.cs in Listings 15.4, 15.5 and 15.6. Suppose these source code listings grow to become more than just demonstration tools. Suppose we work hard to develop the Elevator and Person classes to feature state-of-the-art simulation capabilities, and that a whole namespace hierarchy of other related classes appear that surrounds the Elevator class and a similarly large but separate set of classes appear around the Person class. As a result, the source files representing the classes and namespaces grow to hundreds of pages. However, despite this large mass of classes and namespaces, we are still combining all our compilation units into one single assembly called ElevatorSimulation.exe. Further, let's assume that the programming community is becoming as interested in reusing our Elevator and Person (and associated) classes as elevator specialists are in running ElevatorSimulation.exe as a normal program. As a result, we find ourselves selling a third of the ElevatorSimulation.exe assemblies to programmers only interested in reusing the Elevator-related classes (see Figure 15.5), another third to programmers interested in reusing Person-related classes, and the last third to the simulation experts who are interested in running the full-blown application.

However, this rigid distribution system, which sells the same ElevatorSimulation.exe product to all our customers, contains at least three problems, all related to our programmer customers who are only interested in reusing either the Person part or the Elevator part. Their main complaints are as follows:

  • Even though they only use a part of the ElevatorSimulation.exe assembly, they have to store all parts of this large program and occupy scarce memory.

  • Even though they only use part of ElevatorSimulation.exe assembly, they have to pay as much for the package as the simulation experts who get the full-blown benefits from this assembly.

  • New versions of the Person and Elevator parts of the program are likely generated at a faster pace than versions of the whole ElevatorSimulation.exe assembly. Consequently, our programmer customers have to wait longer to get new versions than if they had been sold the Person or the Elevator parts independently.

The pressure mounts on us to change the much criticized distribution system. Fortunately, the way we can organize our compilation units, namespaces, and assemblies in C# and .NET is very flexible. To satisfy all parties, we decide to break our large ElevatorSimulation.exe into three smaller assemblies, as illustrated in Figure 15.6 a lean Elevator.dll representing the Elevator-related classes, a lean Person.dll, and a lean ElevatorSimulation.exe that does not itself carry the Elevator- and Person-related classes on which it depends. Instead, it references the Elevator.dll and Person.dll to run exactly as before. Let's see how this can be done.

Figure 15.6. Distributing leaner DLLs to programmers for reuse purposes.
graphics/15fig06.gif

To keep matters simple, we won't try to make Elevator.cs, Person.cs, or ElevatorSimulator.cs any larger. You just have to imagine that each represents a large number of source files. Before we begin, you need to ensure that the Sams.CSharpPrimerPlus.ElevatorSimulation.Elevator class from Listing 15.4 can be used outside its assembly by changing it from being an internal class to a public class. Not only does this allow other programmers to reuse this class, it also allows the ElevatorSimulation.exe, which now will be in a separate assembly, to reuse it.

To keep this venture separate from the previous source files created in this chapter, create a new folder called ElevatorSimulationPro and copy the three source files (Elevator.cs, Person.cs, and ElevatorSimulator.cs) over into this folder. It's now time to put the compiler to work. According to our plans, we need to create one EXE and two DLL assemblies. A DLL assembly is created by including the /t[arget]: library command, shown earlier in Table 15.1. To create the two Elevator.dll and Person.dll assemblies, you need to give the following two compiler commands:

 csc /out:Elevator.dll /t:library Elevator.cs csc /out:Person.dll /t:library Person.cs 

The previous version we created of ElevatorSimulation.exe included all three source files. This time, we only include ElevatorSimulator.cs and instruct the ElevatorSimulation.exe assembly to look for the Elevator and Person classes in the Elevator.dll and Person.dll assemblies. The latter is done by including /r[eference]:<Assembly_list> as shown in the following command:

 csc /out:ElevatorSimulation.exe /r:Elevator.dll;Person.dll ElevatorSimulator.cs 

By running ElevatorSimulation.exe in the usual manner, you should see this output once again confirming that nothing has changed in terms of its functionality.

 Blah blah This class is called Elevator 

Note

graphics/common.gif

Somewhere in its namespace hierarchy, an executable assembly must have one Main method from where the program commences. A DLL does not need a Main method because it cannot be executed on its own; it is only created for reuse purposes.


The programmers developing the SpaceShuttle.exe who are among the group of programmers only interested in using the Same.CSharpPrimerPlus.ElevatorSimulation.Person class related functionality, now only need to reference and carry around the much leaner Person.dll when creating and running their assembly. To create a new SpaceShuttle.exe by referencing the Person.dll, copy the SpaceShuttle.cs source file over into the ElevatorSimulationPro folder and write the following command:

 csc /r:Person.dll SpaceShuttle.cs 

Running the SpaceShuttle.exe program in the usual manner confirms its unchanged capability to write

 Blah blah 

   


C# Primer Plus
C Primer Plus (5th Edition)
ISBN: 0672326965
EAN: 2147483647
Year: 2000
Pages: 286
Authors: Stephen Prata

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