Every application begins with a namespace in C# .NET that has the same name as the project. Of course, you can change the namespace to anything you like in order to maintain compatibility with other projects.
For example we declared namespace Mycplus.CSharpBasics while taking an Overview of C#. We can write the whole application with in one namespace or we can declare multiple namespaces as needed in our project. The general syntax of declaring a namespace is
namespace Mycplus.CSharpBasics { //Namespace definition goes here }
Why use namespaces?
Nothing in the C# standard says you must use namespaces. You can create classes by themselves and use them as needed. The point behind using namespaces is organization. Using a namespace enables you to gather all of the classes you create in one place, even if they exist in separate files. In addition, using namespaces helps avoid potential name clashes between vendors. If two vendors choose the same name for a class, then C# will constantly flag ambiguous references in your code.
The namespace isn’t a one-level phenomenon. You can use several levels of namespaces to organize your code. In fact, Microsoft followed this procedure when creating the .NET Framework. Look at the System namespace and you’ll see it contains several levels of other namespaces.
Creating multiple levels of namespaces in your own code is relatively easy. All you need to do is define a namespace within the confines of another namespace as shown here:
namespace Sample { //sample class with in the namepsace public class Class0 { public Class0() { } } //nested name space //it can contain classes with the same functionality set. namespace MyFirstNamespace { public class Class1 { public Class1() { } } } //another namespace with in the main namespace //you can make different files for different classes as well as //you can make different files for different namespaces namespace MySecondNamespace { public class Class2 { public Class2() { } } } }
Notice that the Sample namespace contains a combination of classes and namespaces. You can mix and match classes and namespaces as needed to achieve your organizational goals. The important consideration is keeping the classes organized so they’re easy to find.
Constructors & Destructors
Any class you create should include a constructor, even if the constructor is empty. A constructor enables a client to create an instance of the object that the class defines. The constructor is the starting point for your class. We’ve already looked at several constructors in the chapter. As you’ve seen, constructors never provide a return value, but they can accept one or more input parameters.
One of the issues we haven’t discussed is the use of multiple constructors. Classes with multiple constructors are quite common because the multiple constructors enable a client to create objects in more than one way. In addition, many classes use overrides as a means for handling optional parameters. One of the better examples of this second form of constructor is the MessageBox.Show() method, which has 12 overrides that enable you to use different types of optional parameters. Here’s an example of an application that relies on a class with multiple constructors.
public TestForm() { // Call the Form Designer code. InitializeComponent(); } public TestForm(String FormName) { // Call the Form Designer code. InitializeComponent(); // Set the form name. Text = FormName; } public TestForm(String FormName, String WelcomeLabel) { // Call the Form Designer code. InitializeComponent(); // Set the form name. Text = FormName; // Set the welcomd text. lblWelcome.Text = WelcomeLabel; }
The example application uses these three constructors to open three versions of a secondary form. As you can see, each constructor adds another optional parameter. The example works equally well with any of the constructors. However, adding more information results in a more functional display.
Working with Destructors
Given the way that C# applications work, you’ll almost never need to add a destructor to any code. A destructor works differently in the .NET environment than in previous Windows environments in that you can’t be sure when the destructor will get called. The Garbage Collector frees class resources, at some point, after the class is no longer needed.
The C# specification does allow for a destructor. The destructor can’t have any return value, nor can it accept any input values. This means you can’t override the destructor, so there’s at most one destructor for any given class. In addition, a class can’t inherit a destructor, which means you must create a unique destructor for each class you create (if one is desired).
Destructors should avoid making time-critical changes to the user environment, and they should not use resources that might not be available at the time the destructor is called. For example, you wouldn’t want to display a completion message from the destructor because the user might be engaged in some other activity. Likewise, you wouldn’t want to attempt to close a file handle from the destructor because the file resource might not exist.
// A simple class that includes both constructor // and destructor. public class DoTest { public DoTest() { // Display a message when the class is destroyed. MessageBox.Show("The class has been created!", "Destructor Test Message", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); } ~DoTest() { // Display a message when the class is destroyed. MessageBox.Show("The class has been destroyed!", "Destructor Test Message", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); } }
Garbage Collector (GC)
Every C# program has access to the GC (Garbage Collector) object. You can tell the Garbage Collector to run when it’s convenient for you. However, the garbage collection process doesn’t just involve your application. If you start the garbage collection process, the Garbage Collector will examine the resources for every running application. A huge performance penalty in some situations. The garbage collection could also occur at an inconvenient time. Time-critical processes could get interrupted when you force a garbage collection at the wrong time. The following code shows how to force a garbage collection, but you should use this feature with extreme care.
private void btnGC_Click(object sender, System.EventArgs e) { // Create the test class. DoTest MyTest; MyTest = new DoTest(); // Free the resource. MyTest = null; // Force a garbage collection. GC.Collect(); GC.WaitForPendingFinalizers(); // Display a success message. MessageBox.Show("Garbage collection has succeeded.", "Garbage Collection Test", MessageBoxButtons.OK, MessageBoxIcon.Information); // Exit the application. Close(); }
Using Preprocessing Directives
Anyone who’s used Visual C++ will know the value of preprocessing directives. C# provides a limited number of preprocessing directives that control the flow of compilation, but don’t provide the same level of functionality that Visual C++ provides because C# lacks a true preprocessor. In short, you can use the preprocessor to add or subtract features based on the current configuration, but you can’t perform some of the other tasks that Visual C++ can perform.
Preprocessing Directives Supported by C# | ||
---|---|---|
Directive | Description | Example |
#define | Creates a symbol used for conditional statements such as #if. Never use a variable name for a #define | #define DEBUG |
#elif | Enables conditional compilation if the selected condition is true and the associated #if condition is false. Use this directive in place of #else if you need to test more than one condition. Uses the same operators as the #if directive. | #elif (DEBUG) |
#else | Enables conditional compilation if the associated #if condition is false. | #else |
#endif | Ends conditional compilation started with an #if directive. | #endif |
#endregion | Ends a region created with the #region directive. | #endregion |
#error | Generates an error message. This directive is normally associated with the #if directive to turn the error generation on and off. | #error This is an error message. |
#if | Enables conditional compilation if the selected condition is true. You can test for equality (==) and inequality (!=). The #if directive also supports both and (&&) and or (||). Using #if without operators tests for the existence of the target symbol. | #if (DEBUG) |
#line | Modifies the current line number. You can also add an optional filename. Using default in place of a number returns the embedded numbering system to its default state. | #line 200 or # line default or #line 20 “MyFilename” |
#region | Creates a region block within the IDE. Each region block has an associated collapse feature so you can hide of display the region. You can’t overlap a region with an #if directive. However, the #if can contain the region or the region can contain the #if. | #region MyRegionName |
#undef | Deletes a symbol created with the #define directive. | #undef DEBUG |
#warning | Generates a warning message. This directive is normally associated with the #if directive to turn the error generation on and off. | #warning This is a warning message. |
#define MyDefine #undef MyUndef using System; namespace Preprocess { class Class1 { [STAThread] static void Main(string[] args) { #region MyDefine area of code #if (MyDefine) #error MyDefine is defined #else #error MyDefine isn't defined #endif #endregion #line 1 "Sample Filename" #region MyUndef area of code #if (!MyDefine && !MyUndef) #warning Neither MyDefine nor MyUndef are defined. #elif (MyDefine && !MyUndef) #warning MyDefine is defined, but MyUndef is undefined. #elif (!MyDefine && MyUndef) #warning MyDefine is not defined, but MyUndef is defined. #else #warning Both MyDefine and MyUndef are defined. #endif #endregion #line default } } }