C# Features



C# Features

C# is an object-oriented language created by Microsoft that incorporates many features that may be new to experienced C, C++, and Visual Basic programmers. If you are not familiar with the C# programming language at all, I suggest you purchase a book on C# programming, such as Sybex’s Mastering Visual C# .NET by Jason Price and Michael Gunderloy, or Visual C# .NET Programming by Harold Davis, also from Sybex. Texts like these fully explore the features of this exciting language. The following sections provide a brief synopsis of some unique C# features that are crucial to understand before you begin network programming.

C# Namespaces

With all of the classes provided in the .NET Framework, it’s easy to get confused about which classes perform which functions and the methods that should be used from particular classes. To help simplify things, Microsoft uses namespaces in classifying .NET Framework classes.

What Are Namespaces?

As shown in the SampleClass program, each C# application consists of one or more classes. Each class defines an object that can contain data and methods to manipulate the data. At least one class in each application must contain a program interface method called Main(). The Main() method lets the C# compiler know where to begin execution of the program. Other classes can be defined within the program (such as the DataClass), or can even be shared with other programs.

Sharing classes among programs is the goal of object-oriented programming. One of the issues involved in class sharing is the importance of unique and meaningful class names. If you are working alone and on simple projects, it is unlikely that you will run into the problem of calling two (or more) classes by the same name. However, on a large development team that needs to create hundreds of classes, having a class naming structure in place is critical to success.

C# namespaces are used to identify a higher-level hierarchy of class names, allowing you to group similar classes together within a single namespace. The namespace is defined in the source code file before the class definition, using the namespace directive:

namespace Test1;
class testProgram
{
}
namespace Test2;
class testProgram
{
}

For programs that do not declare a namespace (such as the SampleClass program) the defined classes become part of a global namespace. These classes are globally available to any application in the CLR.

Each namespace uniquely identifies the programs within it. Notice that both of the sample namespaces just shown contain a class called testProgram; most likely they perform separate functions. If your program needs to use one or both of the testProgram classes, you must specify which class you mean to use by referencing the namespace.

The .NET Framework contains many classes separated into namespaces, which help classify the various classes into groups of common functions. You must know how to properly declare the classes you use so that there is no confusion by the compiler when your program is compiled. Let’s examine the specific namespaces used in the .NET Framework.

.NET Framework Namespaces

The .NET Framework uses namespaces to help categorize library classes used in the CLR. This helps programmers determine the location of various classes and how to define them in their programs.

Many .NET Framework namespaces make up the core CLR classes. Figure lists some of the common namespaces you will encounter in your C# network applications.

Figure: .NET Framework Class Namespaces

Namespace

Description of Classes

Microsoft.Win32

Handles events raised by the OS and Registry handling classes

System

Base .NET classes that define commonly used data types and data conversions

System.Collections

Defines lists, queues, bit arrays, and string collections

System.Data

Defines the ADO.NET database structure

System.Data.OleDb

Encapsulates the OLE DB .NET database structure

System.Drawing

Provides access to basic graphics functionality

System.IO

Allows reading and writing on data streams and files

System.Management

Provides access to the Windows Management Instrumentation (WMI) infrastructure

System.Net

Provides access to the Windows network functions

System.Net.Sockets

Provides access to the Windows sockets (Winsock) interface

System.Runtime.Remoting

Provides access to the Windows distributed computing platform

System.Security

Provides access to the CLR security permissions system

System.Text

Represents ACSII, Unicode, UTF-7, and UTF-8 character encodings

System.Threading

Enables multi-threading programming

System.Timers

Allows you to raise an event on a specified interval

System.Web

Enables browser and web server functionality

System.Web.Mail

Enables sending mail messages

System.Windows.Forms

Creates Windows-based application using the standard Windows graphical interface

System.XML

Provides support for processing XML documents

Using Namespaces in Programs

As explained, each namespace provides support for a specific group of classes. Once you have located the namespaces that contain the classes you need for your program, you must define them in your program to access the classes. There are two methods of identifying .NET Framework classes in your programs.

The first method was demonstrated in the SampleClass program:

System.Console.WriteLine("The result is {0}", sample.addem());

This command uses the WriteLine() method of the Console class, found in the System namespace. Notice the hierarchy used in referencing the method. First the namespace is declared, followed by the class name, and finally the method name. You can reference all of the .NET Framework classes in your program using this method, but you might quickly get tired of typing.

An easier way is to declare the namespace with the C# using directive at the beginning of the program. Any classes contained within a namespace declared with using do not have to be referenced by their namespace name:

using System;
Console.WriteLine("The result is {0}", sample.addem());

The C# compiler searches all declared namespaces for the Console class and automatically references the proper namespace.

Warning 

Be careful using this method of declaring namespaces, because you can run into situations where two declared namespaces have classes with the same name (such as the Timer class). In that case, you must supply the full namespace name when referencing the class. If you don’t, the C# compiler will complain that it cannot determine which class you are trying to reference.

After the namespaces have been declared and you use the namespace classes in your program, you must ensure that the C# compiler can find the proper class libraries when you compile your program. The next section explains how this is done.

Compiling Programs with Namespaces

The .NET Framework implements the CLR class library as a set of DLL files. Each DLL file contains a specific subset of classes from one or more namespaces. Not only must the DLLs be available when you run your .NET program, they must also be referenced on the command line when you compile the program.

You must reference each DLL that is necessary to support all of the namespaces declared in your program. To reference a DLL, you use the /resource command-line switch for the csc compiler:

C:\> csc /r:System.dll SampleClass.cs

(The /resource switch can be abbreviated /r.) Here, the classes for the System namespace are contained in the System.dll file, so you need to reference the System.dll file on the command line for the SampleClass.cs program to compile properly. You may be wondering why you didn’t have to do this when you compiled the program earlier. There is a trick involved.

The csc.exe compiler program uses a configuration file that sets a few standard command-line parameters, including default DLL files to reference. The configuration file is called csc.rsp and is located in the same directory as the csc.exe program file.

You can examine the csc.rsp file with any text editor, such as Microsoft Notepad. Listing 1.3 shows the default csc.rsp file that was installed with my version of the .NET Framework.

Listing 1.3: Default csc.rsp file
Start example
# This file contains command-line options that the C#
# command line compiler (CSC) will process as part
# of every compilation, unless the "/noconfig" option
# is specified.
# Reference the common Framework libraries
/r:Accessibility.dll
/r:Microsoft.Vsa.dll
/r:System.Configuration.Install.dll
/r:System.Data.dll
/r:System.Design.dll
/r:System.DirectoryServices.dll
/r:System.dll
/r:System.Drawing.Design.dll
/r:System.Drawing.dll
/r:System.EnterpriseServices.dll
/r:System.Management.dll
/r:System.Messaging.dll
/r:System.Runtime.Remoting.dll
/r:System.Runtime.Serialization.Formatters.Soap.dll
/r:System.Security.dll
/r:System.ServiceProcess.dll
/r:System.Web.dll
/r:System.Web.RegularExpressions.dll
/r:System.Web.Services.dll
/r:System.Windows.Forms.Dll
/r:System.XML.dll
End example

Notice that the majority of the prevalent namespace DLLs are referenced in the csc.rsp file. This is a handy feature that keeps you from having to reference lots of files on the command line if you are using classes from several namespaces. As shown in the comment text from the csc.rsp file, you can also override the csc.rsp values by using the /noconfig command-line switch:

C:\>csc /noconfig /r:System.dll SampleClass.cs

This command will compile the SampleClass program with just the reference to the System.dll file.

Note 

Adding references to additional DLL files does not increase the size of the resulting executable file. The references are only for the purpose of telling the compiler where to find the namespace definitions, not for compiling in the DLL code. The library class code is still run from the DLL. That is why the .NET Framework must be installed on the target workstation or server.

Using Strings in C# Programs

One of the most difficult parts of C programming is dealing with strings. Many program security holes develop from string buffer overflows, in which programmers have used character arrays for strings, and hackers place more characters than memory bytes allocated for the string. To alleviate some of the problems of dealing with strings in C# programs, Microsoft has incorporated two string handling classes into the C# language. Because many network protocols are concerned with sending and receiving text strings, it’s a good idea to get a handle on using strings properly in C# network programs. This section will help you do that by discussing the use of .NET string classes in the C# language.

The String Class

The basic part of string support in C# is the String class. The String class allows you to assign a series of characters to a variable and handle the variable in your program as a single unit. The String class also contains several methods that can be used to perform operations on string objects, such as determining the length of the string and comparing two strings.

The String constructor is overloaded, providing several ways to create a string variable. Figure describes the string constructors.

Figure: String Constructors

Constructor

Description

string(char[])

Creates a string from a specified character array

string(char, int)

Creates a string from a specified character repeated int number of times

string(char[], int1, int2)

Creates a string from a specified character array, starting at position int1 with a length of int2 bytes

In one of the few quirks of C#, you can define strings using either a capital S or a lowercase s in the String declaration. The following are a few examples of declaring string variables:

string test = "This is a test string";
string test2 = test;
string anotherTest = new string('a', 10);

The first technique just listed is the most common way to create new strings. After the string is created, several other methods are available for manipulating and operating on the string. Figure shows some of the more popular methods.

Figure: String Methods

Method

Description

Clone

Returns a reference to the string

Compare

Compares two specified strings

CompareTo

Compares a string with another object

Concat

Concatenates two strings

Copy

Creates a new string with the value of an existing string

CopyTo

Copies a specified number of characters from one string, starting at a specified location, to another string

EndsWith

Determines if a string ends with a specified string

Equals

Determines if two strings have the same value

IndexOf

Returns the first occurrence of a specified string within the string

Insert

Inserts a specified string at a specified location of the string

Intern

Retrieves the system reference for the string

Join

Concatenates a specified string between each element of the string array

LastIndexOf

Returns the index location of the last occurrence of the specified string in the string

PadLeft

Right-aligns the characters of the string and sets the left-most characters to spaces

PadRight

Left-aligns the characters of the string and sets the right-most characters to spaces

Remove

Deletes a specified number of characters from the string

Replace

Replaces all occurrences of a specified character or string with another specified character or string

Split

Identifies substrings in the string based on a specified separation character

StartsWith

Determines if a string starts with a specified string

ToCharArray

Copies the characters in the string to a character array

ToLower

Returns a copy of the string, setting all characters to lowercase

ToString

Converts the value of the object to a string

ToUpper

Returns a copy of the string, setting all characters to uppercase

Trim

Removes all occurrences of a set of specified characters from the beginning and end of a string

TrimEnd

Removes all occurrences of a set of specified characters from the end of a string

TrimStart

Removes all occurrences of a set of specified characters from the beginning of a string

With all of these string methods at your disposal, it is easy to work with strings in C# programs. Much of the hard work of manipulating and comparing strings has been done for you. Listing 1.4 shows a sample string program to illustrate some of these features.

Listing 1.4: Sample string program StringTest.cs
Start example
using System;
class StringTest
{
  public static void Main ()
  {
    string test1 = "This is a test string";
    string test2, test3;
    test2 = test1.Insert(15, "application ");
    test3 = test1.ToUpper();
    Console.WriteLine("test1: '{0}'", test1);
    Console.WriteLine("test2: '{0}'", test2);
    Console.WriteLine("test3: '{0}'", test3);
    if (test1 == test3)
      Console.WriteLine("test1 is equal to test3");
    else
      Console.WriteLine("test1 is not equal to test3");
    
    test2 = test1.Replace("test", "sample");
    Console.WriteLine("the new test2: '{0}'", test2);
  }
}

The output from this program should look like this:

C:\>StringTest
test1: 'This is a test string'
test2: 'This is a test application string'
test3: 'THIS IS A TEST STRING'
test1 is not equal to test3
the new test2: 'This is a sample string'
C:\>

C# creates a set amount of memory for each new string created. Because of this, strings are immutable, that is, they cannot be changed. That said, you may see C# code like the following:

string newString = new string("test");
string newString += "ing";

The resulting value for the variable newString is testing.

If strings are immutable, how can you modify an existing string? The answer is, you don’t; C# just does some trickery. Instead of modifying the existing string, C# creates a brand new string with the new value. The memory area reserved for the old string is now unused and will be cleaned up on the next garbage collection cycle ( CLR’s automatic recovery of lost memory). If you do a lot of string manipulation in your programs, these operations can create additional memory overhead. To compensate for this, Microsoft has created another type of string class just for modifying string objects.

End example

The StringBuilder Class

As its name suggests, the StringBuilder class allows you to create and modify strings without the overhead of recreating new strings each time. It generates a mutable sequence of characters that can change size dynamically as the string is modified, allocating more memory as required.

The amount of memory used by the string is called the capacity. The default capacity of a StringBuilder string is currently set to 16 bytes (StringBuilder documentation indicates that this value may change in the future). If you create a string larger than 16 bytes, StringBuilder will automatically attempt to allocate more memory. When you want to control exactly how much memory StringBuilder can use, you can manually increase or decrease the string capacity using StringBuilder methods, as well as various StringBuilder constructors when the instance is initially created.

Six constructors can be used to create a StringBuilder instance, as shown in Figure.

Figure: The StringBuilder Class Constructors

Constructor

Description

StringBuilder()

Initializes a new default instance with a size of 16

StringBuilder(int)

Initializes a new instance with a capacity of int

StringBuilder(string)

Initializes a new instance with a default value of string

StringBuilder(int1, int2)

Initializes a new instance with a default capacity of int1 and a maximum capacity of int2

StringBuilder(string, int)

Initializes a new instance with a default value of string and a capacity of int

StringBuilder(string, int1, int2, int3)

Initializes a new instance with a default value starting at position int1 of string, int2 characters long, with a capacity of int3

Once the StringBuilder instance is created, you have access to several properties, methods, and operations for modifying and checking properties of the string. One of the most useful properties is Length, which allows you to dynamically change the capacity of the string. Listing 1.5 shows an example of changing the capacity of the StringBuilder string using the Length property.

Listing 1.5: The SampleBuilder.cs program
Start example
using System;
using System.Text;
class SampleBuilder
{
  public static void Main ()
  {
    StringBuilder sb = new StringBuilder("test string");
    int length = 0;
    length = sb.Length;
    Console.WriteLine("The result is: '{0}'", sb);
    Console.WriteLine("The length is: {0}", length);
    sb.Length = 4;
    length = sb.Length;
    Console.WriteLine("The result is: '{0}'", sb);
    Console.WriteLine("The length is: {0}", length);
    sb.Length = 20;
    length = sb.Length;
    Console.WriteLine("The result is: '{0}'", sb);
    Console.WriteLine("The length is: {0}", length);
  }
}

The output from the StringSample program should look like this:

C:\>SampleBuilder
The result is: 'test string'
The length is: 11
The result is: 'test'
The length is: 4
The result is: 'test        '
The length is: 20
C:\>

The original string is 11 bytes long, but after setting the string length to 4, the resulting string is only the first 4 bytes of the original string. After setting the string length to 20, the string becomes 20 bytes long, but the data originally located after the fourth byte has been lost, with spaces used to pad the extra bytes.

After the final string is built using a StringBuilder object, you may need to convert it to a string object to send it to a network stream. This is a simple process using the ToString() method:

string outbound = sb.ToString();

Now the string outbound can be used as a normal string object. Just remember that it is now an immutable string, and as such should not be modified (or be aware that you may suffer additional overhead if it is).

Streams, mentioned in the preceding paragraph, are another feature of C# that you should know intimately for network programming. The next section describes C# streams and their uses.

End example

C# Streams

Data handling is one of the most important jobs of programs. There are many methods for storing and retrieving data in the C# world—files, memory, input/output devices, interprocess communication pipes, and networks. There are also many ways of reading data to and writing it from objects. Most objects allow data to be read or written on a byte-by-byte basis. This method transfers one byte of data into or out of the data object at a time. Certainly this works, but it is not the most efficient manner of handling data.

The C# language supplies an interface to assist programmers in moving large chunks of data to and from data objects. The data stream allows multiple bytes of data to be transferred simultaneously to a data object so that programs can work on blocks of data instead of having to build data elements one byte at a time.

Streams can support three fundamental operations:

  • Transferring data from a stream to a memory buffer (reading)

  • Transferring data from a memory buffer to a stream (writing)

  • Searching the stream for a specific byte pattern (seeking)

Not all streams support all of these functions. Obviously, a CD-ROM device cannot support streams that write data, and network connections do not support streams that seek data patterns.

The .NET System.IO namespace contains various stream classes that can be used to combine the bytes from a data source into manageable blocks that are easier to manipulate.

The FileStream class is a good example of using a stream to simplify reading and writing data. This class provides a stream interface to easily read and write data to a disk file. Let’s look at an example.

If you were writing a program that logged messages to a log file, you most likely would be logging (writing out) one text line of information at a time. You would write the code to place each string in the file byte-by-byte, and ensure that the proper carriage return was added to each text line as it was written. Then, when you wanted to read the log file with a program, you would have to create the code to read the file byte-by-byte. As the file was read, you would have to know that each log file entry ended with the carriage return and that a new entry would start. Each byte read would have to be examined to determine if it was a carriage return.

Instead of this tedious process, you can take advantage of the FileStream class, along with the StreamWriter class, to easily write and read lines of text in a log file. Listing 1.6 shows a sample program that uses streams to simplify file access.

Listing 1.6: Sample log writing program TestLog.cs
Start example
using System;
using System.IO;
class TestLog
{
   public static void Main ()
  {
    string logFile = "LOGFILE.TXT";
    FileStream fs = new FileStream(logFile,
       FileMode.OpenOrCreate, FileAccess.Write);
    
    StreamWriter sw = new StreamWriter(fs);
    StreamReader sr = new StreamReader(fs);
    sw.WriteLine("First log entry");
    sw.WriteLine("Second log entry");
    while(sr.Peek() > -1)
    {
      Console.WriteLine(sr.ReadLine());
    }
    sw.Close();
    sr.Close();
    fs.Close();
  }
}

Take note of the following things in this example:

  • The FileStream object can be used for both reading data from and writing data to the stream.

  • Both the StreamReader and StreamWriter objects reference the same FileStream, but they perform different functions.

  • Each stream object has its own pointer in the stream. After the StreamWriter object inserts two new lines in its stream, the StreamReader object reads the first object in its stream, which is the first line in the file.

  • Each stream opened must be explicitly closed, including the base FileStream object. Many novice programmers forget to close the base stream and inadvertently leave it hanging.

The most common stream technique is to create two separate streams for reading and writing:

StreamWriter sw = new StreamWriter(fs);
StreamReader sr = new StreamReader(fs);

This enables you to have complete control over data access to and from the stream using separate streams.

One thing this program doesn’t do is error checking on the file open attempt. If the program is not able to open the log file, it will produce an ugly error message to the user. The next section shows how you can gracefully handle error conditions within C# programs.

End example

C# Exception Programming

One of the biggest problems for programmers is dealing with abnormal conditions in a program. Inexperienced programmers often forget to compensate for error conditions such as dividing by zero. This results in ugly and annoying error messages and programs blowing up in customers’ faces. To ensure that your code is user-friendly, try to compensate for most types of error conditions. Such error conditions, or other unexpected behavior occurring when a program executes, are called exceptions. Listing 1.7 shows an example.

Listing 1.7: The BadError.cs program
Start example
using System;
class BadError
{
  public static void Main ()
  {
    int var1 = 1000, var2 = 0, var3;
    var3 = var1 / var2;
    Console.WriteLine("The result is: {0}", var3);
  }
}

As you can see, this program is doomed from the start. The arithmetic function in line 9 is destined for disaster because of a divide-by-zero error. Compile and run this example and watch what happens:

C:\>csc BadError.cs
Microsoft (R) Visual C# .NET Compiler version 7.00.9466
for Microsoft (R) .NET Framework version 1.0.3705
Copyright (C) Microsoft Corporation 2001. All rights reserved.
C:\>BadError
Unhandled Exception: System.DivideByZeroException: Attempted to divide by zero.
  at BadError.Main()
C:\>

The csc compiler had no problem compiling this code. It was oblivious to the impending error. When the program runs, a pop-up window indicates that an error occurred and asks you if you want to debug the application. This is exactly the kind of thing you do not want your customers to see. After clicking the OK button, the text error message is produced on the console, indicating the error that was encountered. The program halts at the line of code that produced the error, and no other lines are executed.

C# helps programmers code for exceptions by providing a way to watch and capture exceptions as they occur. By catching exceptions, programmers can efficiently provide for readable error messages and the continuation of the program, or they can stop the program gracefully if necessary. In C#, the try-catch block accomplishes this.

The try-catch block tries to execute one or more statements. If any of the statements generates an exception, the catch block of code is executed instead of the program stopping. Depending on what you include in the catch block, the program can either be terminated gracefully, or allowed to continue as if nothing were wrong. Here is the format of a try-catch block:

try
{
  // one or more lines of code
}
catch ()
{
  // one or more lines of code to execute if an error
}
finally
{
  // one or more lines of code to execute at all times
}
End example

The statements in the try block are executed as normal within the course of the program. If an error condition occurs within the try block, program execution moves to the catch block. After the code in the catch block is executed, control passes back to the main program statements that follow the try-catch block, as if no error had occurred (unless of course the code in the catch block stops or redirects the program execution).

Optionally, you can add a finally block, which will be executed after the try or the catch blocks are finished. Sometimes the finally block is used for clean-up functions that must run whether the functions succeed or fail; most often, however, finally is not used.

Note 

Notice the parentheses in the catch statement. This is a filter, allowing you to define what types of exceptions you want to attempt to catch. You can define a specific exception to watch for, depending on the type of actions being done in the try block; or you can define the generic Exception class, which will catch any exception that occurs. You can even define more than one catch block. Catch blocks are evaluated in order, so specific exceptions must be listed before general ones.

Listing 1.8 shows an example of a simple try-catch block.

Listing 1.8: The CatchError.cs program
Start example

End example
using System;
class CatchError
{
  public static void Main ()
  {
    int var1 = 1000, var2 = 0, var3;
    try
    {
      var3 = var1 / var2;
    }
    catch (ArithmeticException e)
    {
      Console.WriteLine("Exception: {0}",
           e.ToString());
      var3 = -1;
    }
    catch (Exception e)
    {
      Console.WriteLine("Exception: {0}",
           e.ToString());
      var3 = -2;
    }
    Console.WriteLine("The result is: {0}", var3);
  }
}

In CatchError, the original program is modified by adding a try-catch block. There are two catch statements: one to watch specifically for ArithmeticExceptions, and one to watch for any general Exception. Notice that the specific ArithmeticException was listed first, otherwise the general Exception block would handle all of the exceptions before the more detailed exception appeared. Also note that the catch blocks set a value for the result variable that can then be checked later on in the program. The result of this program is as follows:

C:\>csc CatchError.cs
Microsoft (R) Visual C# .NET Compiler version 7.00.9466
for Microsoft (R) .NET Framework version 1.0.3705
Copyright (C) Microsoft Corporation 2001. All rights reserved.
C:\>CatchError
Exception: System.DivideByZeroException: Attempted to divide by zero.
  at CatchError.Main()
The result is: -1
C:\>

The try-catch block gracefully catches the arithmetic exception, displays the error as instructed in the catch block, and sets the variable to a special value that can later be checked in the program logic.

Note 

Network functions are frequently used within try-catch blocks. In network programming, it is often impossible to determine whether an action will succeed. For example, packets sent out on the network may not get to their intended destination because of a down router or destination host. Instead of the program blowing up because of an unexpected network issue, the problem can be reported to the user.

 Python   SQL   Java   php   Perl 
 game development   web development   internet   *nix   graphics   hardware 
 telecommunications   C++ 
 Flash   Active Directory   Windows