Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Visual CSharp .NET Programming (2002) [eng]

.pdf
Скачиваний:
58
Добавлен:
16.08.2013
Размер:
7.98 Mб
Скачать

}

class TRex : Dinosaur {

public override string GetFood (){

return "I am Tyrannosaurus Rex. I eat everything. I am always hungry.";

}

}

Each of the four classes derived from Dinosaur in Listing 8.13 implement their overridden GetFood method in their own way-that is, each returns a different string. (After all, the method-like dinosaurs-is pretty simpleminded!)

The objects in the jPark array shown in Listing 8.14 are of type Dinosaur. However, when the foreach statement is used to loop through the array, because of the override modifier marking the GetFood method in each of the derived classes, those versions of the method are invoked, as shown in Figure 8.18.

Listing 8.14: Invoking the GetFood Methods of the Classes Derived from Dinosaur

private void btnPoly_Click(object sender, System.EventArgs e) { Dinosaur [] jPark = new Dinosaur [4];

jPark[0] = new Lessemsaurus(); jPark[1] = new Apatosaurus(); jPark[2] = new Allosaurus(); jPark[3] = new TRex();

foreach (Dinosaur dino in jPark){ lstDino.Items.Add(dino.GetFood());

}

}

Figure 8.18: The same-but-different polymorphic behavior of the classes derived from Dinosaur can be seen in the text each one adds to the ListBox Items collection.

Overriding Virtual Methods

You may recall that in the Guns, Germs, and Steel application, the Civilization class is derived from the CityState class, which in turns derives from Tribe. (Put the other way round, Tribe is the base class for CityState, which is the base class for Civilization.)

Instances of the GroupOfPeople class act as the facade for the Tribe, CityState, and Civilization class. Specifically, the ProgressTheTribe method of GroupOfPeople, shown excerpted in Listing 8.15, determines which kind of object is currently instantiated.

Listing 8.15: Portions of the ProgressTheTribe Method

private Tribe tr;

// tr is instantiated in GetNewTribe

public void ProgressTheTribe() {

const long veryBig = long.MaxValue / 2; if (m_Population <= 100000){

if (m_Population >= 0 && m_Population < 100 ) { // fire the extinction event

...

}

else

m_Population = tr.GetNewPopulation(); this.DrawForm(); tr.IncrementGenerations();

}

else if (m_Population <= 1000000) { // initialize city state

if (cs == null){

cs = new CityState(); cs.SetMeUp(tr);

frmTribe.Text = cs.Text + " City State";

}

m_Population = cs.GetNewPopulation(); this.Bacteria = cs.Bacteria; this.DrawForm(); cs.IncrementGenerations();

}

else {

// initialize city state if (civil == null) {

civil = new Civilization(); civil.SetMeUp(cs);

frmTribe.Text = civil.Text + " Civilization";

}

if (m_Population >= veryBig) { // fire max pop event

...

}

else

m_Population = civil.GetNewPopulation(); this.Bacteria = civil.Bacteria;

this.Guns = civil.Guns; this.DrawForm(); civil.IncrementGenerations();

}

}

If you look at Listing 8.15, you'll see a recurring theme: at each pass, whether the instance is a Tribe, a CityState, or a Civilization, a version of GetNewPopulation is called.

As you would likely expect, the GetNewPopulation method as originally declared in the Tribe class is virtual (Listing 8.16)-so that it can be overridden in its derived classes.

Listing 8.16: The Virtual GetNewPopulation Method

public virtual long GetNewPopulation () {

long increment = (long) (GetGrowthRatePerGeneration() * m_Population); m_Population += increment;

if (m_Population >= 0 && m_Population < 10) { m_Population = 0;

}

return m_Population;

}

The pace picks up in the CityState class (Listing 8.17), where the population incremental increase is subject to a multiplier, and the bacteria measure of disease production and resistance is introduced for the first time.

Listing 8.17: The GetNewPopulation Override Method in the CityState Class

public override long GetNewPopulation () { const long veryBig = long.MaxValue / 2;

long increment = (long) (GetGrowthRatePerGeneration() * m_Population); if (m_Bacteria < veryBig - 1) {

m_Bacteria += m_Bacteria;

}

m_Population += (long) (increment * 1.2); return m_Population;

}

As the culture starts to really race for the stars in the Civilization class, things pick up still more, with the introduction of the guns and technology index if the population growth rate is sufficiently high (Listing 8.18).

Listing 8.18: The GetNewPopulation Method in the Civilization Class

public override long GetNewPopulation () { const long veryBig = long.MaxValue / 2;

float growthRate = GetGrowthRatePerGeneration(); long increment = (long) (growthRate * m_Population); if (m_Population >= veryBig) {

m_Population = long.MaxValue - 1; return m_Population;

}

if (m_Bacteria < veryBig - 1) { m_Bacteria += m_Bacteria;

}

if ((growthRate * 2) >= 0.5){ if (m_Guns < veryBig - 1) {

m_Guns = m_Guns + Convert.ToInt64(m_Guns * growthRate);

}

}

m_Population += (long) (increment * 1.2); return m_Population;

}

As these overrides of the GetNewPopulation method show, you can add features to a derived class that were not present in the original class by using the mechanism of overriding methods marked virtual.

Creating a Custom Collection

All along in the Guns, Germs, and Steel application, I've had the issue of how to deal with all the instances of GroupOfPeople. These instances need to be easily available for programmatic reference in several circumstances.

The obvious answer is create a custom collection for them, derived from one of the collection classes explained in Chapter 7. Listing 8.19 shows a simple-but powerful-collection class derived from ArrayList with two member methods: AddAPeople and RemoveAPeople.

Listing 8.19: A Custom Collection Class Derived from ArrayList

using System;

using System.Collections;

namespace SybexC21

{

public class PeoplesCollection : ArrayList

{

public PeoplesCollection() {

}

public void AddAPeople (GroupOfPeople aGoP) { this.Add(aGoP);

}

public void RemoveAPeople (GroupOfPeople aGoP) { this.Remove(aGoP);

}

}

}

In the main form of the application, it's now easy to instantiate a collection based on this class:

PeoplesCollection peoples = new PeoplesCollection();

We can now use the power of the peoples collection-which includes the derived members from the ArrayList class-to access all the instances of GroupOfPeople, making for some fairly compact code. For example, Listing 8.20 shows the entire code of the Timer's Elapsed eventwhich uses the peoples collection to advance each GroupOfPeople instance through time.

Listing 8.20: Using the Collection to Progress Each Instance through Time

private void timer1_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { this.lblYears.Text = Globals.globalTime.ToString(); foreach (GroupOfPeople aGoP in peoples) {

if (aGoP.isAlive) aGoP.ProgressTheTribe();

}

Globals.globalTime += Globals.generation;

}

Conclusion

Objects, classes, and object-oriented programming in C# are fun and heady stuff! In a fully OOP language and environment, the concepts explained in this chapter are extremely important. Object and classes are the notes and score that keep the C# music playing!

I'm glad I've had the chance to share some of this material with you. Although in some respects, this chapter has touched only the surface of this topic, important material has been explained. I also hope that I've given you some sense of how OOP concepts might be used in real-world projects.

Chapter 9: Everything Is String

Manipulation

Overview

The immutability of the string type

The char type and static char methods

Control characters

Creating strings, string methods, and verbatim strings

The StringBuilder class

Implementing the ToString method

String class interfaces, implementing IComparable, and creating your own interface

Regular expression classes and syntax

Why is string manipulation so important? Because, as programmers, we do so much of it. In fact, on some days, it seems-like the title of this chapter-that everything is string manipulation.

Fortunately, C# provides great and powerful facilities for working with and manipulating strings. This chapter explains the types and methods related to strings, so that you can make short work of most common string-manipulation tasks in your projects.

The String class implements several interfaces, including IComparable and IEnumerable. In this chapter, I'll explain what it means to implement an interface and show you what String's implementation of these interfaces means to you. We'll also take a-hopefully profitable-

detour to explore how you can implement an interface such as IComparable in your own classes, using the Dinosaur classes developed at the end of Chapter 8 as an example. Continuing with the interface detour, I'll show you how to create a custom interface of your own-ICarnivore- that can be implemented by the Dinosaur classes.

The last part of this chapter explains the basics of working with regular expressions in C#.

C# String Basics

As I explained in Chapter 6, 'Zen and Now: The C# Language,' a text string is stored in a data type named string, which is an alias to the System.String type. In other words, when you create a string, you instantiate a System.String object.

In addition to its instance members, the System.String class has quite a few important static methods. These methods don't require a string instance to work and are discussed later in this chapter in "The String Class."

It's important to understand that the string type is immutable. This means that once it has been created, a string cannot be changed. No characters can be added or removed from it, nor can its length be changed.

But wait, you say; my strings change all the time when I do things like:

string str = "I am going to have a lot of fun"; str += " with Dudley this summer. ... ";

str += " The End";

Well, my friend, what this set of statements actually does is create a new instance of the variable str each time a value is assigned to str. In other words, in this example, an instance of str has been created-and a literal string value assigned to it-three times. The statement that the type is immutable means that an instance cannot be edited-but it can be assigned.

Using an instance method without an assignment is syntactically legal, but it doesn't do anything (has no effect). For example

string str = "hello";

str.Replace ("l", "L"); // Doesn't do anything

does not change the contents of the variable str, although combining it with an assignment would work:

string str = "hello";

str = str.Replace ("l", "L");

The value of the 'new,' assigned str is, of course, "heLLo".

Is there any real problem with this? Well, no, it's easy enough to use assignments when you need to change the value of a string. But instantiating and destroying strings every time you need to change or edit one has negative performance consequences. If performance is an issue, you should consider working with instances of the System.Text.StringBuilder class,

which are mutable (can be changed). The StringBuilder class is discussed later in this chapter in more detail.

Once created, a string has a specific length-which, of course, is fixed (since the type is immutable). A C# string is not a terminated array of characters (unlike in the C language, in which a string is simply an array of characters terminated with a zero byte). So, how does one determine the length of a C# string? As you probably know, you can query the read-only Length property of the string instance to find its length.

The Char Type

Char is a value type that contains a single Unicode character. While chars have a numeric value from hexadecimal 0x0000 through 0xFFFF, they are not directly numerically usable without explicit casting.

Literal values are assigned to char variables using single quotes. The literal can be a simple, single letter, or an escape sequence (for more on escape sequences, see the next section). Here are two examples:

char

chr = 'P';

//

contains

capital

P

char

pi = '\u03A0';

//

contains

capital

Pi

The char data type is related to the string data type: A string can be constructed from and converted into an array of characters-but string and char are two distinct types.

Suppose you have a string defined like this:

string str = "rotfl";

You can retrieve a character of the string using the string's indexer. For example, the following statement

char chr = str[1];

stores the character ‘o' in the variable chr. But you cannot set a character in the string with a statement like

str[1] = 'a';

because the indexer property of the String class is read-only. (Actually, one would expect this in any case, because the string instance is immutable.)

Note If you use the Object Browser to have a look at the String class, you'll see that its indexer property, declared as this[int], has a get accessor, but no set accessor.

Control Characters

The backslash (\) is a special control character, also called the escape character. The character after the backslash has a special meaning, as shown in Table 9.1.

 

 

Table 9.1: Common C# Escaped Characters

Character

 

Meaning

 

 

 

\0

 

Null

 

 

 

\b

 

Backspace

 

 

 

\t

 

Tab

 

 

 

\r

 

Carriage return

 

 

 

\n

 

New line

 

 

 

\v

 

Vertical tab

 

 

 

\f

 

Form feed

 

 

 

\u, \x

 

A single Unicode character when followed by a four-digit hexadecimal number

 

 

 

\"

 

Double quote

 

 

 

\'

 

Single quote

\\Backslash

The escape sequences \u or \x are used (interchangeably) to specify a single Unicode character. For example, \x03A9 and \u03A9 both mean the Greek letter capital omega (). The escape sequences \x03A3 and \u03A3 will appear as the Greek letter capital sigma (Σ), as in this example, in which a button displays a capital sigma as its text when it is clicked:

private void btnDoIt_Click(object sender, System.EventArgs e) { char chr = '\u03A3';

btnDoIt.Text = chr.ToString();

}

Note You can find a complete list of the Unicode character sets and their four-digit hexadecimal representations at www.unicode.org/charts/.

Char Static Methods

The methods of the Char class are, for the most part, static. These static methods allow you to determine whether a character is a letter, number, white space, etc. Figure 9.1 shows some of these static methods in the Object Browser.

Figure 9.1: The static methods of the Char class-shown here in the Object Browserallow discovery of the nature of individual characters.

These methods return Boolean values, and each comes in two styles. One style takes a char as an argument. The other takes two arguments: a string, and an index representing the position of the character to be tested in the string. For example, here are the signatures of the static Char methods that test to see whether a character is a number:

bool IsNumber (char chr)

bool IsNumber (string str, int iIndex)

As an example, the following click event tests to see whether Unicode (hex) 0x0041 is a letter.

private void btnChar_Click(object sender, System.EventArgs e) { char chr = '\u0041'; // capital Latin A

if (Char.IsLetter(chr)){

MessageBox.Show ("\'" + chr.ToString() + "\' is a letter.", "Char re me fa so la", MessageBoxButtons.OK, MessageBoxIcon.Information);

}

}

Since 0x0041 is a Unicode capital A, the IsLetter method returns true, and the message box is displayed.

Using the other form of the method, one can check to see whether the third character that the user enters in a TextBox is a letter:

string str = txtDoIt.Text;

if (Char.IsLetter (str, 2)) {

MessageBox.Show ("\'" + str[2].ToString() + "\' is a letter.", "Char re me fa so la", MessageBoxButtons.OK, MessageBoxIcon.Information);

}

else {

MessageBox.Show ("\'" + str[2].ToString() + "\' is NOT a letter.", "Char re me fa so la", MessageBoxButtons.OK, MessageBoxIcon.Information);

}

Running this code determines whether the third character entered by the user is a letter, and displays an appropriate message.

Warning There is no user input validation or exception handling in this code. If there is no third character-because the user has not entered one-this code will cause a run-time exception.

Note The static method invocation Char.IsLetter (str, 2) is equivalent to Char.IsLetter (str[2]). Note You may have noticed the rather oddly named static Char IsSurrogate methods. If you are curious about the name, you may like to know that they test a character to see

whether it has a Unicode value that is reserved for future expansion of the Unicode system (with values between 0xD800 and 0xDFFF).

Creating Strings

String, how shall I make thee: let me count the ways. There are many ways to create string variables, and no doubt you've used most of them. But let's go over this familiar ground one more time.

String Assignment

First, and most obviously, you can create a string by assigning a literal to a string variable:

string str = "I am a string!";

By the way, you can-of course-declare a string variable without initializing it:

string str;

As I showed you in Chapter 6, attempting to use this uninitialized variable causes a compiler error. What you may not know is that a string variable, since it is a reference type, can be initialized to null: