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

Visual CSharp .NET Programming (2002) [eng]

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

}

}

The base Dinosaur class shown in Listing 9.4 is no longer abstract; instead, it has a virtual GetFood method that can be overridden. In addition, a field, Length, and two properties, Name and EatMeat, have been implemented. The EatMeat property is a Boolean used to get or set the fact that the instance is a carnivore. Also, note that the class has no constructor.

It's easy to add a GetString method to the Dinosaur class using the override keyword. This GetString method combines the members of the class to return a string, and will be invoked in instances of Dinosaur and its derived classes:

public override string ToString() {

string retval = "Dino Name: " + this.Name; retval += ", GetFood: " + this.GetFood();

retval += ", Length: " + this.Length.ToString() + " meters"; retval += ", Carnivore: " + this.EatMeat.ToString();

return retval;

}

Listing 9.5 shows the Dinosaur class with the ToString method added.

Listing 9.5: Dinosaur Class with ToString Method

public class Dinosaur

{

private string m_Name; private bool m_EatMeat; public int Length = 0;

public virtual string GetFood(){ return "You are what you eat!";

}

public string Name { get {

return m_Name;

}

set {

m_Name = value;

}

}

public bool EatMeat { get {

return m_EatMeat;

}

set {

m_EatMeat = value;

}

}

public override string ToString() {

string retval = "Dino Name: " + this.Name; retval += ", GetFood: " + this.GetFood();

retval += ", Length: " + this.Length.ToString() + " meters"; retval += ", Carnivore: " + this.EatMeat.ToString();

return retval;

}

}

Before we can actually take our ToString method out for a spin, we need to derive some classes from Dinosaur and instantiate some objects based on the derived classes. Listing 9.6 shows five classes derived from Dinosaur. Each class has a constructor that requires values for the instance Length field, and Name property, when an instance is created. Note that the EatMeat property is set in the constructor for each class, meaning that it cannot be set as part of instantiation. In addition, each derived class overrides the virtual Dinosaur GetFood method.

Listing 9.6: Classes Derived from Dinosaur

class Lessemsaurus : Dinosaur {

public Lessemsaurus (string theName, int length){ this.Name = theName;

this.Length = length; this.EatMeat = false;

}

public override string GetFood (){

return "Lessemsaurus bites branches from treetops.";

}

}

class Apatosaurus : Dinosaur {

public Apatosaurus (string theName, int length){ this.Name = theName;

this.Length = length; this.EatMeat = false;

}

public override string GetFood (){ return "Apatosaurus eats plants.";

}

}

class Diplodocus : Dinosaur {

public Diplodocus (string theName, int length){ this.Name = theName;

this.Length = length; this.EatMeat = false;

}

public override string GetFood (){ return "Diplodocus likes his greens.";

}

}

class Allosaurus : Dinosaur {

public Allosaurus (string theName, int length){ this.Name = theName;

this.Length = length; this.EatMeat = true;

}

public override string GetFood (){

return "My name is Allosaurus and I eat Apatosaurus.";

}

}

class TRex : Dinosaur {

public TRex (string theName, int length){ this.Name = theName;

this.Length = length; this.EatMeat = true;

}

public override string GetFood (){

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

}

}

Next, in a Form class, declare an array of type Dinosaur named jPark[ ], and in a Button click event instantiate five new objects based on the classes derived from Dinosaur. As shown in Listing 9.7, add a concatenated string containing each object's GetFood method and name to a ListBox's Items collection.

Listing 9.7: Instantiating Objects Based on Classes Derived from Dinosaur

Dinosaur [] jPark;

private void btnPoly_Click(object sender, System.EventArgs e) { lstDino.Items.Clear();

jPark = new Dinosaur [5];

jPark[0] = new Lessemsaurus("lessaemsaurus", 7); jPark[1] = new Apatosaurus("apatosaurus", 34); jPark[2] = new Allosaurus("allosaurus", 12); jPark[3] = new TRex("tRex", 14);

jPark[4] = new Diplodocus("doc", 9); foreach (Dinosaur dino in jPark){

lstDino.Items.Add(dino.GetFood() + " -- " + dino.Name);

}

}

Finally-and it's about time, before all those dinosaurs go extinct-implement a click event that places the ToString return value from a dinosaur selected in the ListBox into a multiline TextBox (Listing 9.8).

Listing 9.8: Displaying the ToString Return Value for the Selected Dinosaur Object

private void btnToStr_Click(object sender, System.EventArgs e) { if (lstDino.Items.Count > 0) {

if (lstDino.SelectedItems.Count > 0){ txtToStr.Text = "";

txtToStr.Text = jPark [lstDino.SelectedIndex].ToString();

}

}

}

If you run this project and select an item representing an instantiation of a class derived from Dinosaur in the ListBox, the return value of its ToString method-based on the ToString method placed in the base Dinosaur class-will be shown. Figure 9.5 shows the ToString value for the instantiation of the TRex class.

Figure 9.5: It's up to you to implement a meaningful ToString method for your classes (the ToString return value for a TRex instance is shown).

Interfaces

An interface is like an abstract base class: both do not spell out implementation details and are used by derived classes. However, a class can derive from a base class and can also implement an interface-or, in fact, implement multiple interfaces. (In contrast, C# classes can only inherit from one base class.) In this respect, interfaces are more general-and more usefulthan class inheritance.

What it boils down to is that an interface is a kind of contract. If a class implements an interface, it means that it-and its derived classes-must have implementations for the members whose signatures are included in the implementation specification. This means that if you know that a class implements an interface, you know that it implements certain members, and you know what to expect about how those members work.

Interface implementations help to ensure consistency. Studies have shown that software written with class libraries that implement interfaces tends to have fewer defects.

Interfaces work well in situations in which one set of programmers has created a class library (for example, the creators of the .NET Framework) and another set will be using the classes (you and me). Interface implementation also provides a good way for a team leader to deliver project specifications: the project lead writes the interfaces, and specifies mandatory implementation within classes.

By convention, an interface identifier always starts with a capital I, followed by another capital letter.

String Interfaces

The String class implements four interfaces: ICloneable, IComparable, IConvertible, and IEnumerable. You can determine the interfaces implemented by this class (or any other .NET class) by looking up the class's documentation in online help or by having a look at the class in the Object Browser (Figure 9.6). Each of these interfaces is documented in online help.

Figure 9.6: You can find the interfaces implemented by the String class by using the Object Browser.

Note The Object Browser will also tell you the members that the interface 'contract' requires to be implemented, and define those members for you.

Of the String class interface implementations, IComparable and IEnumerable are the most important.

The fact that String implements IEnumerable implies that you can use the foreach syntax to enumerate the characters in a string, using a statement like this:

foreach (char chr in str) {

...

}

Implementing the IComparable interface means that the class must contain a CompareTo method that compares the current instance with another object of the same type. Once this method is implemented, objects of the class work with the BinarySearch and Sort methods of the Array class.

Implementing IComparable in Your Own Class

Let's go ahead and implement IComparable in the Dinosaur class (it will then be implemented in all the classes derived from Dinosaur). The first step is to add the interface name following the inheritance operator in the Dinosaur class declaration:

public class Dinosaur : IComparable

Note If you don't actually add the members specified in an interface to a class (or derived class) that implements the interface, you will get a compile-time error.

Next, we need to add a CompareTo method to the Dinosaur class. CompareTo has a fairly complex int return type that must be less than zero if the current instance is less than its comparison instance, zero if the two instances are the same, and greater than zero if the current instance is greater that its comparison instance. You can read more about the implementation details by looking up "IComparable.CompareTo Method" in online help. In the meantime, I'm going to take a shortcut: the Dinosaur class CompareTo will be implemented as a string CompareTo on the Name property of each dinosaur instance created

from Dinosaur and its derived classes. You can see the code for this method in Listing 9.9. The long and the short of it is that Dinosaur instances will be sorted in arrays based on their Name property.

Listing 9.9: Implementing IComparable in the Dinosaur Class

public class Dinosaur : IComparable

{

...

public int CompareTo (object obj) { Dinosaur dino = (Dinosaur) obj;

return this.Name.CompareTo (dino.Name);

}

}

To try this out, we can add a call to the static method Array.Sort after the Dinosaur objects have been instantiated in the jPark array:

Array.Sort (jPark);

The code that uses the IComparable implementation to sort the objects created from classes derived from Dinosaur is shown in Listing 9.10.

Listing 9.10: Sorting the Dinosaurs

private void btnPoly_Click(object sender, System.EventArgs e) { lstDino.Items.Clear();

jPark = new Dinosaur [5];

jPark[0] = new Lessemsaurus("lessaemsaurus", 7); jPark[1] = new Apatosaurus("apatosaurus", 34); jPark[2] = new Allosaurus("allosaurus", 12); jPark[3] = new TRex("tRex", 14);

jPark[4] = new Diplodocus("doc", 9); Array.Sort (jPark);

foreach (Dinosaur dino in jPark){ lstDino.Items.Add(dino.GetFood() + " -- " + dino.Name);

}

}

When you run this code, you'll see that the Dinosaur instances have been added to the ListBox alphabetically by name (Figure 9.7), because that's the way they were sorted in the jPark Array.

Figure 9.7: You can tell that the classes derived from Dinosaur have implemented IComparable because they are ordered by dinosaur name.

Note If the ListBox Sorted property were set to True, then the ListBox would be sorted by the

text strings in the Items collection, not the Dinosaurs' Name properties. ("Apatosaurus" in Figure 9.7 would be first, and "My name" would be last.)

Implementing the ICarnivore Interface

Kicking this all up a notch, let's implement our own interface. Since it is ours, we get to name it and to define its members. Let's make an ICarnivore interface that consists of the Boolean property EatMeat (already implemented in the Dinosaur class) and a Boolean method CanIEatU, which determines whether one dinosaur instance can eat another. Listing 9.11 shows the interface definition.

Note It's worth saying explicitly that the interface definition only defines the members required for the interface. There is no implementation code in the interface definition.

Listing 9.11: The ICarnivore Interface

interface ICarnivore { bool EatMeat {get; set;}

bool CanIEatU (object obj);

}

To implement the interface, we need to add ICarnivore to the inheritance clause of the Dinosaur class declaration:

public class Dinosaur : IComparable, ICarnivore

Next, we need to create an implementation of CanIEatU for the Dinosaur base class. The logic of this method is that I can eat you if I am a carnivore and you are not, or if we both are carnivores and I am bigger than you:

public bool CanIEatU (object obj){ Dinosaur dino = (Dinosaur) obj; if (this.EatMeat == false)

return false;

else if (this.EatMeat == true && dino.EatMeat == false) return true;

else // this.EatMeat == true && dino.EatMeat == true

{

if (this.Length > dino.Length) return true;

else

return false;

}

}

Listing 9.12 shows the complete Dinosaur class with the ToString method, the CompareTo method, and the CanIEatU implementation of ICarnivore. (You'll find the CanIEatU method towards the bottom of the listing.)

Listing 9.12: The Complete Dinosaur Class (CanIEatU Method at the End)

public class Dinosaur : IComparable, ICarnivore

{

private string m_Name; private bool m_EatMeat; public int Length = 0;

public virtual string GetFood(){ return "You are what you eat!";

}

public string Name { get {

return m_Name;

}

set {

m_Name = value;

}

}

public bool EatMeat { get {

return m_EatMeat;

}

set {

m_EatMeat = value;

}

}

public override string ToString() {

string retval = "Dino Name: " + this.Name; retval += ", GetFood: " + this.GetFood();

retval += ", Length: " + this.Length.ToString() + " meters"; retval += ", Carnivore: " + this.EatMeat.ToString();

return retval;

}

public int CompareTo (object obj) { Dinosaur dino = (Dinosaur) obj;

return this.Name.CompareTo (dino.Name);

}

public bool CanIEatU (object obj){ Dinosaur dino = (Dinosaur) obj;

if (this.EatMeat == true && dino.EatMeat == false) return true;

else if (this.EatMeat == false && dino.EatMeat == true) return false;

else if (this.EatMeat == false && dino.EatMeat == false) return false;

else // this.EatMeat == true && dino.EatMeat == true

{

if (this.Length > dino.Length) return true;

else

return false;

}

}

}

You can now alter the project to add a click event that instantiates two dinosaur objects, uses the CanIEatU method to determine who is lunch (and who is not), and then uses the members of the instances to display an appropriate message:

private void btnICarnivore_Click(object sender, System.EventArgs e) { lstDino.Items.Clear();

TRex chomper = new TRex ("Chomper", 14);

Diplodocus doc = new Diplodocus ("Doc", 9); if (chomper.CanIEatU (doc)){

lstDino.Items.Add(chomper.Name + " the " + chomper.GetType().ToString());

lstDino.Items.Add("has eaten");

lstDino.Items.Add(doc.Name + " the " + doc.GetType().ToString()); lstDino.Items.Add("for lunch.");

}

}

Figure 9.8 shows the results of running this code displayed in the ListBox at the top of the form.

Figure 9.8: The ICarnivore interface determines who has eaten whom.

You should also note that the ICarnivore interface and implementation members appear in the Class View window when you examine one of the classes derived from Dinosaur (Figure 9.9) and also in the Object Browser (Figure 9.10).

Figure 9.9: The ICarnivore interface (and the members of the TRex class) are shown in Class View.

Figure 9.10: The ICarnivore interface and the classes derived from Dinosaur appear in the Object Browser.

This discussion of interfaces and interface implementation has necessarily been somewhat superficial. For one thing, you are unlikely to need to implement Dinosaur and derived classes-and an ICarnivore interface-unless the dinosaurs come back. But the principles are the same when they are used in more substantive ways-as an important mechanism requiring adherence to standards-and you should know about interfaces and interface inheritance; arguably, it is as important as class inheritance for creating industrial-strength software.

Regular Expressions

One of the most powerful tools in the string manipulator's arsenal is the regular expression. A regular expression is used to define a pattern of characters. These patterns can be used for many purposes, including string manipulation, user input validation, and searching and replacing. Typically, the pattern represented by the regular expression is matched against a string. In the simplest example, the regular expression is a literal string of characters-for example, 'saur'. This simple regular expression pattern matches against a string that contains it-for example, 'Allosaurus'. The regular expression fails to match a string that does not contain it, such as 'Montezuma'.

While this example seems simple enough, you should not underestimate the importance

of regular expressions, which provide access to powerful algorithms that are the natural and easy answer to many programming problems. The simplest solution to many programming problems-particularly those involving strings-often involves regular expressions.

Note The .NET Framework's powerful regular expression syntax is based on the regexp syntax used in Perl5.

In this section, I'll

Tell you about the .NET regular expression classes.

Show you how to set up a regular expression test bed.

Explain the language of regular expression patterns.