Professional Documents
Culture Documents
CH 04
CH 04
Composite DefaultAll-In-One
screenAll-In-One
/ MCAD/MCSD
/ MCSD Visual C# .NET Certification All-in-One Exam Guide / Rempel & Lind / 222443-6 / Chapter 4
Strings, Exceptions,
and Events
CHAPTER
4
In this chapter, you will
• Learn about the string class
• Explore arrays
• Manipulate collections
• Learn the purpose of exception handling
• Be introduced to the System.Exception class
• Get acquainted with try and catch blocks
• Learn how to use the finally statement
• Create your own exception classes
• Get to understand the event-handling mechanism in the .NET Framework
The information in this chapter is included for background. If you already understand
the subjects listed, you can go on to Chapter 5 and the Visual Studio .NET development
environment.
This chapter will explain three concepts that are used throughout the .NET develop-
ment environment. We’ll look at the string class and how to use it, along with collec-
tions and arrays. We will also look at the concept of exception handling, which takes
care of those pesky runtime errors. The final topic in this chapter is the event-handling
environment in the .NET Framework.
P:\010Comp\All-in-1\443-6\ch04.vp
Friday, August 23, 2002 4:57:23 PM
Color profile: Generic CMYK printer profile
Composite DefaultAll-In-One
screen / MCAD/MCSD Visual C# .NET Certification All-in-One Exam Guide / Rempel & Lind / 222443-6 / Chapter 4
Member Description
Chars Gets the character at a specified character position in this instance
Clone Returns a reference to this instance of String
Compare Compares two specified string objects
Concat Concatenates one or more instances of string, or the string
representations of the values of one or more instances of Object
Copy Creates a new instance of String with the same value as a
specified string
CopyTo Copies a specified number of characters from a specified position in this
instance to a specified position in an array of Unicode characters
EndsWith Determines whether the end of this instance matches the specified string
Equals Determines whether two string objects have the same value (overridden)
Format Replaces each format specification in a specified string with the textual
equivalent of a corresponding object’s value
GetEnumerator Retrieves an object that can iterate through the individual characters in
this instance
GetHashCode Returns the hash code for this instance
GetType Gets the Type of the current instance
Table 4-1 Members of System.String
P:\010Comp\All-in-1\443-6\ch04.vp
Friday, August 23, 2002 4:57:24 PM
Color profile: Generic CMYK printer profile
Composite DefaultAll-In-One
screen / MCAD/MCSD Visual C# .NET Certification All-in-One Exam Guide / Rempel & Lind / 222443-6 / Chapter 4
PART I
GetTypeCode Returns the TypeCode for class String
IndexOf Reports the index of the first occurrence of a string, or of one or more
characters, within this instance
IndexOfAny Reports the index of the first occurrence in this instance of any character
in a specified array of Unicode characters
Insert Inserts a specified instance of string at a specified index position in
this instance
Intern Retrieves the system’s reference to the specified string
IsInterned Retrieves a reference to a specified string
Join Concatenates a specified separator string between each element of a
specified string array, yielding a single concatenated string
LastIndexOf Reports the index position of the last occurrence of a specified Unicode
character or string within this instance
LastIndexOfAny Reports the index position of the last occurrence in this instance of one
or more characters specified in a Unicode array
Length Gets the number of characters in this instance
PadLeft Right-aligns the characters in this instance, padding on the left with spaces
or a specified Unicode character for a specified total length
PadRight Left-aligns the characters in this string, padding on the right with spaces
or a specified Unicode character, for a specified total length
Remove Deletes a specified number of characters from this instance, beginning at a
specified position
Replace Replaces all occurrences of a specified Unicode character or string in this
instance with another specified Unicode character or string
Split Identifies the substrings in this instance that are delimited by one or
more characters specified in an array, and then places the substrings
into a string array
StartsWith Determines whether the beginning of this instance matches the
specified string
Substring Retrieves a substring from this instance
ToCharArray Copies the characters in this instance to a Unicode character array
ToLower Returns a copy of this string in lowercase
ToString Converts the value of this instance to a string
ToUpper Returns a copy of this string in uppercase
Trim Removes all occurrences of a set of specified characters from the
beginning and end of this instance
TrimEnd Removes all occurrences of a set of characters specified in a Unicode
character array from the end of this instance
TrimStart Removes all occurrences of a set of characters specified in a Unicode
character array from the beginning of this instance
Table 4-1 Members of System.String (continued)
P:\010Comp\All-in-1\443-6\ch04.vp
Friday, August 23, 2002 4:57:24 PM
Color profile: Generic CMYK printer profile
Composite DefaultAll-In-One
screen / MCAD/MCSD Visual C# .NET Certification All-in-One Exam Guide / Rempel & Lind / 222443-6 / Chapter 4
The result of these two lines is that we have a string object on the heap containing
"This is the first string's data", and the reference on the stack (strOne) is
assigned to the string object. After the second line of code, there is a change: the origi-
nal string object is still on the heap, and it is unchanged. A new string object has
been created containing "This is the first string's data and this is
the second part!". The reference (strOne) is now assigned to the new string
object, and the first string object will be cleared away by the garbage collector.
Immutable strings are very efficient when it comes to manipulating strings of known
length, but they suffer when the length of the string changes. For example, let’s take our
earlier code and perform that old children’s cipher on the text by shifting every letter two
characters back in the alphabet; e becomes c, and so on. For example, “Hello World” will
be turned into “Fcjjm Umpjb”.
// String.cs
using System;
This program produces a large number of new string objects because each time we
call the Replace() method, a new object will be constructed. This example highlights
the need for another string-like class—the System.Text.StringBuilder class.
The System.Text.StringBuilder class produces a mutable object whose mem-
ory allocation we can control by adding and removing space from the object. Let’s start
by rewriting the preceding example using the System.Text.StringBuilder class:
P:\010Comp\All-in-1\443-6\ch04.vp
Friday, August 23, 2002 4:57:24 PM
Color profile: Generic CMYK printer profile
Composite DefaultAll-In-One
screen / MCAD/MCSD Visual C# .NET Certification All-in-One Exam Guide / Rempel & Lind / 222443-6 / Chapter 4
PART I
using System.Text;
With some very small changes, we have altered the processing to create only one object—
the StringBuilder object—constructed by using the new keyword and specifying an
initial length of 30 characters.
You will normally use the StringBuilder class to perform string manipulations
on strings that are used for display purposes. Table 4-2 lists the members of the
StringBuilder class.
Member Description
Append Appends the string representation of a specified object to the end of
this instance
Capacity Gets or sets the maximum number of characters that can be contained in
the memory allocated by the current instance
Chars Gets or sets the character at the specified character position in this instance
EnsureCapacity Ensures that the capacity of this instance of StringBuilder is at least
the specified value
Equals Returns a value indicating whether this instance is equal to a specified object
GetHashCode Serves as a hash function for a particular type, suitable for use in hashing
algorithms and data structures like a hash table
GetType Gets the Type of the current instance
Insert Inserts the string representation of a specified object into this instance at a
specified character position (this is an overloaded method)
Length Gets or sets the length of this instance
MaxCapacity Gets the maximum capacity of this instance
Remove Removes the specified range of characters from this instance
Replace Replaces all occurrences of a specified character or string in this instance
with another specified character or string
ToString Converts a StringBuilder to a string
Table 4-2 The Members of the StringBuilder Class
P:\010Comp\All-in-1\443-6\ch04.vp
Friday, August 23, 2002 4:57:24 PM
Color profile: Generic CMYK printer profile
Composite DefaultAll-In-One
screen / MCAD/MCSD Visual C# .NET Certification All-in-One Exam Guide / Rempel & Lind / 222443-6 / Chapter 4
// StringFormat.cs
using System;
Console.WriteLine("The double is {0,10:E} and the int contains {1}", PI, i);
}
}
The output from this program can be seen in Figure 4-1. The formatting inserted in
the string is used to make the numbers print in a predetermined way: {0,10:E} speci-
fies that the first (zero-based) parameter following the formatting string will be printed
in a field of 10 characters using scientific format. The second format, {1}, takes the sec-
ond parameter and prints it using the default settings.
Arrays
The array is one of the most common concepts used when storing a series of data points,
either in a one-dimensional or multidimensional format. C# implements arrays as a
class, and the arrays can hold data of one type (such as int, double, long, and so on).
It also lets you create arrays of any number of dimensions.
You were already introduced to arrays in Chapter 2, and here we are going to look
more closely at the Array class and see how we can take advantage of it.
P:\010Comp\All-in-1\443-6\ch04.vp
Friday, August 23, 2002 4:57:24 PM
Color profile: Generic CMYK printer profile
Composite DefaultAll-In-One
screen / MCAD/MCSD Visual C# .NET Certification All-in-One Exam Guide / Rempel & Lind / 222443-6 / Chapter 4
PART I
Figure 4-1 The output of the string formatting program
// Array.cs
using System;
class ArrayTest
{
static public void Main()
{
// Declare some arrays
int[] Ints;
Ints = new int[10]; //create an int array with 10 members
for (int i=0; i<Ints.Length; i++)
{
Ints[i] = i;
}
int[] Instr = new int[10];
//Copy Ints to Instr
Array.Copy( Ints, Ints.GetLowerBound(0), Instr,
Instr.GetLowerBound(0), 10 );
// Reverse the content of Instr
Array.Reverse(Instr);
P:\010Comp\All-in-1\443-6\ch04.vp
Friday, August 23, 2002 4:57:24 PM
Color profile: Generic CMYK printer profile
Composite DefaultAll-In-One
screen / MCAD/MCSD Visual C# .NET Certification All-in-One Exam Guide / Rempel & Lind / 222443-6 / Chapter 4
Member Description
Clear Sets a range of elements in the array to zero, to false, or to a null
reference, depending on the element type
Clone Creates a shallow copy of the array—a shallow copy will only copy the
values or references that are in the original array, not the physical objects
Copy Copies a section of one array to another array and performs type casting
and boxing as required
CopyTo Copies all the elements of the current one-dimensional array to the specified
one-dimensional array starting at the specified destination array index
Equals Determines whether two Object instances are equal
GetLength Gets the number of elements in the specified dimension of the array
GetLowerBound Gets the lower bound of the specified dimension in the array
GetType Gets the Type of the current instance
GetUpperBound Gets the upper bound of the specified dimension in the array
GetValue Gets the value of the specified element in the current array
IndexOf Returns the index of the first occurrence of a value in a one-dimensional
array or in a portion of the array
Initialize Initializes every element of the value-type array by calling the default
constructor of the value type
IsFixedSize Gets a value indicating whether the array has a fixed size
IsReadOnly Gets a value indicating whether the array is read-only
Length Gets the total number of elements in all the dimensions of the array
Rank Gets the rank (number of dimensions) of the array
Reverse Reverses the order of the elements in a one-dimensional array or in a
portion of the array
Sort Sorts the elements in one-dimensional array objects
ToString Returns a string that represents the current Object
Table 4-3 The Members of the System.Array Class
P:\010Comp\All-in-1\443-6\ch04.vp
Friday, August 23, 2002 4:57:25 PM
Color profile: Generic CMYK printer profile
Composite DefaultAll-In-One
screen / MCAD/MCSD Visual C# .NET Certification All-in-One Exam Guide / Rempel & Lind / 222443-6 / Chapter 4
PART I
The initial Array has the following members : 0123456789
The reverse Array has the following members : 9876543210
The reverse sorted Array has the following members : 0123456789
Collections
A different way of storing items is to use the OO concept of a collection, which you were
introduced to in Chapter 3. We will now look at the support for this OO concept in C#.
A collection is defined as a set of similarly typed objects that are grouped together,
similar to an array, but built to store objects. The .NET Framework support for collec-
tions is provided through the System.Collections namespace. Table 4-4 lists the
collections defined in that namespace.
Class Description
ArrayList Implements the IList interface using an array
whose size is dynamically increased as required
BitArray Manages a compact array of bit values, which are
represented as Booleans, where true indicates
that the bit is on (1), and false indicates the bit
is off (0)
CaseInsensitiveComparer Compares two objects for equivalence, ignoring
the case of strings
CaseInsensitiveHashCodeProvider Supplies a hash code for an object, using a hashing
algorithm that ignores the case of strings
CollectionBase Provides the abstract base class for a strongly
typed collection
Comparer Compares two objects for equivalence, where
string comparisons are case-sensitive
DictionaryBase Provides the abstract base class for a strongly
typed collection of key-and-value pairs
Hashtable Represents a collection of key-and-value pairs that
are organized based on the hash code of the key
Queue Represents a first-in, first-out collection of objects
ReadOnlyCollectionBase Provides the abstract base class for a strongly
typed read-only collection
SortedList Represents a collection of key-and-value pairs
that are sorted by the keys and are accessible by
key and by index
Stack Represents a simple last-in, first-out collection
of objects
Table 4-4 The Classes in the System.Collections Namespace
P:\010Comp\All-in-1\443-6\ch04.vp
Friday, August 23, 2002 4:57:25 PM
Color profile: Generic CMYK printer profile
Composite DefaultAll-In-One
screen / MCAD/MCSD Visual C# .NET Certification All-in-One Exam Guide / Rempel & Lind / 222443-6 / Chapter 4
using System;
using System.Collections;
public class ExampleSortedList
{
slOne
Count: 3
Capacity: 16
Keys and Values:
-KEY- -VALUE-
First: Hello
Second: World
Third: !
P:\010Comp\All-in-1\443-6\ch04.vp
Friday, August 23, 2002 4:57:25 PM
Color profile: Generic CMYK printer profile
Composite DefaultAll-In-One
screen / MCAD/MCSD Visual C# .NET Certification All-in-One Exam Guide / Rempel & Lind / 222443-6 / Chapter 4
PART I
Note that we are not talking about errors that can and should be avoided. You don’t
want to write complicated exception handling for an error such as division by zero,
which you shouldn’t let happen. Along the same lines, there is no point in “handling”
an error that occurs because your program tried to add an element to an array beyond
the array boundaries. These are program bugs, and they should be fixed before the pro-
gram is considered complete. Don’t make the mistake of creating error-handling rou-
tines for these kinds of errors.
An exception handler is a piece of code that handles the first kind of error—those that
cannot be avoided. When a program is asked to look for a file that is not there, the pro-
gram code that looks for files causes an exception object to be “thrown.” This means that
it creates an object (in this case, an object of type FileNotFoundException) and
then searches for code that will deal with the error. If it doesn’t find the code in the cur-
rent method, program execution returns to the caller of the method and searches there.
This search continues (in a process called unwinding the call stack) until either the error is
handled or the Main() method is entered. If the error flows back through all the calling
methods and is not handled anywhere (including in Main()), the error is “caught” by
the .NET runtime. This is not good news, since it means that the user of your program
will be presented with a dialog box that essentially says your program bombed out.
By using the techniques described in this chapter for handling errors, you will achieve
the following:
P:\010Comp\All-in-1\443-6\ch04.vp
Friday, August 23, 2002 4:57:25 PM
Color profile: Generic CMYK printer profile
Composite DefaultAll-In-One
screen / MCAD/MCSD Visual C# .NET Certification All-in-One Exam Guide / Rempel & Lind / 222443-6 / Chapter 4
• try block This tells the CLR that we know an exception might occur in
the code.
• catch block This is the block of code that tells the CLR what to do if a
specific exception has occurred.
• finally block This is the last piece of code to be executed, whether an
exception occurred or not.
Before we examine each of these blocks in detail, take a moment to look at the following
sequence of events that will take us through the steps of how the exception handling
flows:
1. Program execution enters a try block. Each line of code is executed. If no error
occurs, control is transferred to the nearest finally block (Step 4 in this list).
If no finally block exists, execution continues on the instruction following
the last catch block.
2. If an error occurs, the CLR creates an object to represent the error and transfers
execution control to the catch blocks. Each catch block is examined, in
Figure 4-2
The dialog box
presented to the
user before the
program closes
P:\010Comp\All-in-1\443-6\ch04.vp
Friday, August 23, 2002 4:57:25 PM
Color profile: Generic CMYK printer profile
Composite DefaultAll-In-One
screenAll-In-One
/ MCAD/MCSD
/ MCSD Visual C# .NET Certification All-in-One Exam Guide / Rempel & Lind / 222443-6 / Chapter 4
PART I
finally block (step 4 in this list).
3. If no catch block is found that handles the error object, the CLR passes the
error object to the caller of the current method. The process described in step 2
continues in the caller method. Again, if no catch block is found, the error is
passed back until it reaches the Main() method. Once in Main(), the error
is either “caught” or it causes the program to terminate prematurely and it
displays a dialog box like the one shown earlier in Figure 4-2.
4. The finally block is executed. Notice that we do not mention any conditions
on this block of code executing. This is because you are guaranteed (unless
some abnormal abort like a power failure or a kernel abort occurs) that the
finally block of code will run, whether there was an error or not.
In the following sections, we will examine the use of the try and catch blocks and
then look at the benefits of coding a good finally block. We will also explore the
System.Exception class and its children in order to understand the types of excep-
tions that may occur.
Here we’ve told the CLR to try to execute the code in the try block. Of course, we
know that the division by zero will cause an error. The CLR will then create an object to
represent the error. Later in this chapter, we will deal with the types of objects that it
P:\010Comp\All-in-1\443-6\ch04.vp
Friday, August 23, 2002 4:57:25 PM
Color profile: Generic CMYK printer profile
Composite DefaultAll-In-One
screen / MCAD/MCSD Visual C# .NET Certification All-in-One Exam Guide / Rempel & Lind / 222443-6 / Chapter 4
We caught an error!
We will now get to the end of the code!
The program did not abort in this instance. Rather, it is allowed to continue since, in
effect, we have “handled” the error. However, regardless of whether you follow this
approach or allow the CLR to terminate the program, the line after the attempted
divide-by-zero will never execute.
We mentioned earlier that the stack will unwind if the method containing the error
does not handle the error. The following simple example demonstrates this using our
DivideByZero code:
using System;
public class StackTest
{
public static void Main ()
{
try
{
MethodA();
} catch (DivideByZeroException d) {
System.Console.WriteLine ("Catching the appropriate exception");
} catch (Exception e) {
System.Console.WriteLine ("Here we are in Main()");
} finally {
System.Console.WriteLine ("This code will always execute");
}
}
public static void MethodA()
{
// this method simply calls MethodB
MethodB();
}
public static void MethodB()
{
int x = 0;
int y = 12;
int z = y / x; // LINE WITH THE ERROR
System.Console.WriteLine ("We never get here");
}
}
P:\010Comp\All-in-1\443-6\ch04.vp
Friday, August 23, 2002 4:57:25 PM
Color profile: Generic CMYK printer profile
Composite DefaultAll-In-One
screen / MCAD/MCSD Visual C# .NET Certification All-in-One Exam Guide / Rempel & Lind / 222443-6 / Chapter 4
PART I
The first issue is that the actual code that causes the error (displayed in bold) is not
enclosed in a try block directly. Instead, it has been called by a method that was called
by a method that was enclosed in a try block. When the line shown in bold is executed,
the CLR looks for exception handling in MethodB(). Of course, none is found, so it un-
winds the call stack looking back at MethodA() for a catch block, and then to
Main() where it finds the catch.
The next thing that you might notice about this code is that there are two catch
blocks in Main(). You may want to have the anticipated error caught (System.
DivideByZeroException) and then provide a default catch for any unanticipated
errors. You can code as many catches as you like, as long as you follow these rules:
• Order your catch blocks by the specificity of the exception that is handled.
This means that if you know a System.DivideByZeroException could
happen, code it first. In our example, if the catch blocks were in the reverse
order, the System.Exception block would be executed and our specific
handling of the division by zero ignored. Only one catch block will execute,
and control then passes to the finally block, if one is coded.
• You can place a general catch at the end of the catch blocks as follows:
} catch
{ // write some very general code here }
In this case, no actual object is caught, but the general catch block allows
you to handle completely unanticipated type exceptions. The compiler will
enforce the rule that this block must be at the end of the catch blocks.
• You cannot catch the same type of exception twice. For example, there
cannot be two catch blocks that start with:
} catch (DivideByZeroException d) {
• You cannot catch an exception of a type derived from the class of an
object from a previous catch. For example, if there was a class
System.DivideIntegersByZeroException that was a subclass of
System.DivideByZeroException, you could not code a catch block
for the subclass after the parent class, since one derives from the other.
P:\010Comp\All-in-1\443-6\ch04.vp
Friday, August 23, 2002 4:57:25 PM
Color profile: Generic CMYK printer profile
Composite DefaultAll-In-One
screen / MCAD/MCSD Visual C# .NET Certification All-in-One Exam Guide / Rempel & Lind / 222443-6 / Chapter 4
Even if the highlighted line were changed as follows (which would not cause the
exception to be thrown), the finally block will execute:
int z = y / 2;
You may find the finally block useful for avoiding duplicate code. Suppose many
exceptions could occur within a particular try block. You would need catch blocks
coded for each type of exception, and perhaps, as a final piece of code, a routine that
must be executed regardless of which exception happens. Without the finally block,
you would have to code the segment in each catch block. Fortunately, you don’t have
to do this—just code it once in the finally block.
Throwing Exceptions
As a final note to this discussion, there may be times when you decide it is necessary to
cause an exception that can then be put through the normal handling routines. You do
this by using the throw keyword.
In the following code example, we ask the user for an integer between 12 and 42. The
user normally accommodates us by providing a value in that range. But occasionally a
value less than 12 or greater than 42 escapes from the user’s fingers. We can deal with
this by throwing an exception object that will handle the error.
using System;
public class UserInputTest
{
public static void Main ()
{
try
{
int x;
Console.Write ("Enter an integer between 12 and 42 : ");
x = Int32.Parse (Console.ReadLine());
if (x > 42)
throw new GreaterThan42Exception(); //home-grown exception
else if (x < 12)
throw new LessThan12Exception(); // home-grown exception
} catch (GreaterThan42Exception g) {
Console.WriteLine ("You entered a value greater than 42");
} catch (LessThan12Exception l) {
Console.WriteLine ("You entered a value less than 12");
} catch (Exception e) {
Console.WriteLine (e.Message);
P:\010Comp\All-in-1\443-6\ch04.vp
Friday, August 23, 2002 4:57:26 PM
Color profile: Generic CMYK printer profile
Composite DefaultAll-In-One
screen / MCAD/MCSD Visual C# .NET Certification All-in-One Exam Guide / Rempel & Lind / 222443-6 / Chapter 4
PART I
class GreaterThan42Exception : Exception
{
// put some specific code here
}
class LessThan12Exception : Exception
{
// put some specific code here
}
}
The best way to become familiar with these properties and to explore the methods
that are either inherited by System.Object or are new to System.Exception and
thereby are inherited by all other exceptions is to review the .NET Framework Class
Library documentation.
P:\010Comp\All-in-1\443-6\ch04.vp
Friday, August 23, 2002 4:57:26 PM
Color profile: Generic CMYK printer profile
Composite DefaultAll-In-One
screen / MCAD/MCSD Visual C# .NET Certification All-in-One Exam Guide / Rempel & Lind / 222443-6 / Chapter 4
This is a very simple example, but it does give you an idea of what you need to do to
create your own exception class. The next step entails throwing this exception when a
customer is not found. Presumably, your logic will go out to the database, search for the
customer, and return with the result of the search. If the customer was not found, you
would code the following line:
throw new NoSuchCustomerFoundException ();
The catch block would deal with the logic that pertains to an invalid customer
request.
Event Handling
Event handling is the area of programming that has given procedural developers the big-
gest problem over the years—what is an event and how it is handled? The first item on our
agenda must be the definition of an event. The examples commonly used to describe
event handling are based on the concept of a cup object, and we have found that this type
of example, rather than a code-based example, makes the topic easier to comprehend.
P:\010Comp\All-in-1\443-6\ch04.vp
Friday, August 23, 2002 4:57:26 PM
Color profile: Generic CMYK printer profile
Composite DefaultAll-In-One
screen / MCAD/MCSD Visual C# .NET Certification All-in-One Exam Guide / Rempel & Lind / 222443-6 / Chapter 4
PART I
object: the specific object we have is blue in color with a black lid. The object has two
methods, drink() and spill(); two parameters, noSpill (closes the lid) and level (indi-
cates how much coffee there is in the cup). This can be a very common, everyday travel
mug, or it can be a software object that emulates a travel mug—either way we can inter-
act with the mug object.
Here is our task: We want to make sure we can refill the mug object as soon as it is
empty, so we need to perform a loop asking “is it empty?” that reads the level parameter
every so many seconds. When the level indicates that the mug is empty, the mug should
be refilled. We could have produced a procedural software structure similar to this
pseudo-code.
do while (true)
{
if (mug.level <= 0)
{
refill(mug)
}
}
This type of looping structure was the mainstay of the procedural world; we used to
draw lovely flowcharts so we would know the flow of execution through the applica-
tion. This type of processing is synchronous, with everything happening in a predeter-
mined order, and only in that order. In our travel mug example, the end result of all that
checking would be that coffee most likely would be removed from the daily food list—it
is too much work for the person checking the level in the mug.
The natural question is whether there is a better way, and fortunately there is. The
event model gives us the ability to have two or more objects communicating with each
other asynchronously.
We will bestow our travel mug with two events. For now we will just define an event as
something that is important to the object, and in this case we will create the empty
event and the full event. The mug will cause the empty event when the mug is empty
and the full event when the mug is close to overflowing. To take advantage of this new
better travel-mug model, we need to do one extra thing—we need to tell the mug that we
would like to be told when these events take place. This request is a registration of inter-
est. We give the mug a sticky note that says “Tell me when the empty event happens” and
gives an address where we can be contacted. Now we can go about our day doing what
we do best, and when the mug is empty we will get a message from the mug telling us
that it is empty.
This process is performed in the .NET environment every time we use an event.
Actually this is the processing that took place in previous versions of the Windows oper-
ating system, as well. In the software world, we call the event routing a callback. The
receiver of the event registers an address that should be called when the event fires (hap-
pens). The code that is called when the event fires is called the event handler.
Now let’s have a look at how we perform event handling in the .NET environment:
we use a delegate to connect the event with the code that will handle the event.
P:\010Comp\All-in-1\443-6\ch04.vp
Friday, August 23, 2002 4:57:26 PM
Color profile: Generic CMYK printer profile
Composite DefaultAll-In-One
screen / MCAD/MCSD Visual C# .NET Certification All-in-One Exam Guide / Rempel & Lind / 222443-6 / Chapter 4
Once the delegate is declared, we can instantiate it, as in the following code segment:
When we have the delegate declared and instantiated, we need to call it. The follow-
ing code segment shows how to call a delegate.
Let’s have a look at a more complete example. Let’s build a console application that
will print assorted things for us and then call these methods through delegates.
P:\010Comp\All-in-1\443-6\ch04.vp
Friday, August 23, 2002 4:57:26 PM
Color profile: Generic CMYK printer profile
Composite DefaultAll-In-One
screen / MCAD/MCSD Visual C# .NET Certification All-in-One Exam Guide / Rempel & Lind / 222443-6 / Chapter 4
PART I
{
class Class1
{
delegate void MyprtString(string s);
delegate int MyprtNumbers(double d, double d1, int i);
The two delegates, MyprtString and MyprtNumbers, are declared and instantiated
to encapsulate references (pointers) to the two methods in MyClass. When we execute
the program, it results in the following output:
42
Hello Jones!
In the examples we have worked with thus far, the delegate performed a single cou-
pling between the caller and the method. Such a delegate is known as a single delegate.
What would happen if the invocation of the delegate resulted in multiple methods
being called? There are several things that would need to be considered. For example,
the delegate would need to keep a list of all the methods that need to be called, and
some mechanism to add and remove methods from the delegates list would also be
needed. A delegate with multiple methods listed is called a multicast delegate.
Multicast delegates are derived from the System.MulticastDelegate class. All the dele-
gates have an invocation list with all the references that are to be called when the delegate
is invoked. The MulticastDelegate class has two static methods that are used to add
and remove references from the invocation list: the Combine and Remove methods. The
P:\010Comp\All-in-1\443-6\ch04.vp
Friday, August 23, 2002 4:57:26 PM
Color profile: Generic CMYK printer profile
Composite DefaultAll-In-One
screen / MCAD/MCSD Visual C# .NET Certification All-in-One Exam Guide / Rempel & Lind / 222443-6 / Chapter 4
using System;
namespace MultiDelegate
{
public delegate void PetDelegate();
class Application
{
static void Main(string[] args)
{
// instantiate the pet objects
Dog dog = new Dog();
Snake snake = new Snake();
PetDelegate a, b, c, d;
// combine a and b as c
c = (PetDelegate)Delegate.Combine(a, b);
// invoke c(), the result should be that the two methods are called
c();
Console.Write("\n\n");
// combine c and a into d
d = (PetDelegate)Delegate.Combine(c, a);
// invoke d(), the result should be that three methods are invoked
d();
}
}
public class Dog
{
public void Bark()
{
Console.WriteLine("WOOF");
}
}
public class Snake
{
public void Hiss()
{
Console.WriteLine("Sssssssssssssss");
}
}
}
P:\010Comp\All-in-1\443-6\ch04.vp
Friday, August 23, 2002 4:57:26 PM
Color profile: Generic CMYK printer profile
Composite DefaultAll-In-One
screen / MCAD/MCSD Visual C# .NET Certification All-in-One Exam Guide / Rempel & Lind / 222443-6 / Chapter 4
PART I
d() we get three lines.
WOOF
Sssssssssssssss
WOOF
Sssssssssssssss
WOOF
Microsoft has overloaded some operators in the C# language to make working with
delegates easier. We can use the plus sign (+) or the += operator rather than using the
Combine() method directly. Similarly, the minus sign (-) and the -= has been over-
loaded to give us the Remove() functionality. Using this syntax, we can rewrite the previ-
ous example as in the following code segment.
// remove a from c
c = c - a;
d += c;
d -= a;
Events
Now that we’ve defined the delegates, we can look at how we declare, connect to, and
raise events. This is what we have learned so far:
We now need a technique for connecting the event with the handler so we don’t have to
continually keep checking for the event. That connection is the delegate described in the
previous section.
P:\010Comp\All-in-1\443-6\ch04.vp
Friday, August 23, 2002 4:57:26 PM
Color profile: Generic CMYK printer profile
Composite DefaultAll-In-One
screen / MCAD/MCSD Visual C# .NET Certification All-in-One Exam Guide / Rempel & Lind / 222443-6 / Chapter 4
When we declare a delegate for a method with a void return type, the delegate will be
compiled as a MulticastDelegate by the C# compiler, and Combine and Remove meth-
ods are added for members from the invocation list. The client accesses those methods
with the overloaded operators += and -=.
The client code contains the method to handle the event, as well as the code to con-
nect the method to the event. The client code to use the TravelMugEmpty event will look
like this:
Once the object that has declared the event is ready to raise (or fire) the event, it
can use the event both as a class attribute and as a method. In our example, the
TravelMugEmpty event can be tested to see if it is not null—the event attribute will
be null only if there are no registered event handlers. If there are registered event han-
dlers, we raise the event by calling the event method TravelMugEmpty(), as can be
seen in this code segment:
// raise the TravelMugEmpty event if there are any connected event handlers
if (TravelMugEmpty != null) TravelMugEmpty();
• The class that is defining the event will declare the delegate and the event.
• The client will define the event-handling method and connect to the event.
• The class that defined the event tests to see if there are any clients registered,
and raises the event as needed.
P:\010Comp\All-in-1\443-6\ch04.vp
Friday, August 23, 2002 4:57:26 PM
Color profile: Generic CMYK printer profile
Composite DefaultAll-In-One
screen / MCAD/MCSD Visual C# .NET Certification All-in-One Exam Guide / Rempel & Lind / 222443-6 / Chapter 4
Summary
PART I
In this chapter, we have looked at how to work with character strings in the form of
System.String classes as well as System.Text.StringBuilder classes, and we
looked at where each can be used. We also discussed arrays and the utility methods
available for manipulating array data. We also looked at collections. There are a large
number of collections that can be built using the collection support in C#—there is a
special operator (foreach) that simplifies the way you can go through a collection from
the start to the end very efficiently.
We also looked at the techniques of error handling. Creating applications that handle
error conditions successfully is not rocket science. You simply need to understand when
the exception might occur and code your catch blocks accordingly. You will also have
to consider what kind of user-application errors could occur, and then build your cus-
tom exception class library.
Finally we discussed delegates and events, and how to declare them.
The next chapter will introduce the graphical environment that can be used when
working with C# in Visual Studio .NET.
Test Questions
1. Given the following code segment, what is the content of the string s in line 4?
1 string s = "Hello";
2 string r;
3 r = s;
4 r += " World!";
A. “Hello World!”
B. “Hello”
C. Nothing, it is garbage collected
D. The code will not compile
2. Which of the following array declarations will produce a compiler error?
A. int[] Integers = new int[] (1,2,3,4,5,6,7,8,9,0};
B. int[] Integers = new int[42];
C. int[] Integers = {1,2,3,4,5,6,7,8,9,0};
D. int I = 4;
int[] Integers = new int[I] {1,2,3,4};
P:\010Comp\All-in-1\443-6\ch04.vp
Friday, August 23, 2002 4:57:27 PM
Color profile: Generic CMYK printer profile
Composite DefaultAll-In-One
screen / MCAD/MCSD Visual C# .NET Certification All-in-One Exam Guide / Rempel & Lind / 222443-6 / Chapter 4
P:\010Comp\All-in-1\443-6\ch04.vp
Friday, August 23, 2002 4:57:27 PM
Color profile: Generic CMYK printer profile
Composite DefaultAll-In-One
screen / MCAD/MCSD Visual C# .NET Certification All-in-One Exam Guide / Rempel & Lind / 222443-6 / Chapter 4
PART I
01 public void MethodB ()
02 {
03 int [] MyInts = new int [2];
04 try
05 {
06 for ( int i = 0; i < 3; i++)
07 {
08 MyInts[i] = i;
09 }
10 } finally
11 {
12 System.Console.WriteLine ("This is executed");
13 }
14 }
A. The code will not compile because there is a missing catch block.
B. The code will compile and abort upon execution.
C. The code will compile and displays “This is executed”.
D. The code will compile and will abort upon execution and then display
“This is executed”.
7. You need to define a delegate for the following method:
public class Class1
{
public static int Method42(int i)
{
return i*42;
}
}
How is the delegate for Method42() declared?
A. delegate Class1.Method42;
B. delegate int Met42(int i);
C. delegate void Method42(string s);
D. delegate int Class1.Method42(int i);
8. What kind of delegate will be created for the following method?
public void Method12(object sender, System.EventArgs e)
{
…
}
A. Single delegate
B. Event delegate
C. Multicast delegate
D. Proxy delegate
P:\010Comp\All-in-1\443-6\ch04.vp
Friday, August 23, 2002 4:57:27 PM
Color profile: Generic CMYK printer profile
Composite DefaultAll-In-One
screen / MCAD/MCSD Visual C# .NET Certification All-in-One Exam Guide / Rempel & Lind / 222443-6 / Chapter 4
Test Answers
1. B. Through all the processing the content of s never changes.
2. D. The array constructor cannot take an explicit size parameter as well
as initialization.
3. D. The output is the first string in the array, the array was reversed so the
output is “World”.
4. A. The output is the first string in the array, the array was sorted so the output
is “!”.
5. C. The array has size 2, we try to access index 0, 1, and 2. There will be an
exception when we try to access index 2, resulting in an exception, the catch
block will sink the exception, then the finally block will execute.
P:\010Comp\All-in-1\443-6\ch04.vp
Friday, August 23, 2002 4:57:27 PM
Color profile: Generic CMYK printer profile
Composite DefaultAll-In-One
screen / MCAD/MCSD Visual C# .NET Certification All-in-One Exam Guide / Rempel & Lind / 222443-6 / Chapter 4
PART I
finally block will execute.
7. B. The delegate should have the same signature as the method it will
encapsulate, except the name must be unique in the scope.
8. C. Any delegate that is declared as public void is a multicast delegate.
9. D. Events must be declared as static.
10. B. The delegate must be registered, the += operator performs that action.
P:\010Comp\All-in-1\443-6\ch04.vp
Friday, August 23, 2002 4:57:27 PM