Tải bản đầy đủ (.pdf) (12 trang)

Tài liệu Creating a Generic Class docx

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (28.46 KB, 12 trang )



Creating a Generic Class
The .NET Framework Class Library contains a number of generic classes readily
available for you. You can also define your own generic classes, which is what you will
do in this section. Before we do this, I will provide a bit of background theory.
The Theory of Binary Trees
In the following exercises, you will define and use a class that represents a binary tree.
This is a very practical exercise because this class happens to be one that is missing from
the System.Collections.Generic namespace. A binary tree is a very useful data structure
used for a variety of operations, including sorting and searching through data very
quickly. There are volumes written on the minutiae of binary trees, but it is not the
purpose of this book to cover binary trees in detail. Instead, we will just look at the
pertinent details. If you are interested you should consult a book such as The Art of
Computer Programming, Volume 3: Sorting and Searching by Donald E. Knuth
(Addison-Wesley Professional; 2
nd
edition 1998).
A binary tree is a recursive (self-referencing) data structure that can either be empty or
comprise three elements: some data that is typically referred to as the node and two sub-
trees, which are themselves binary trees. The two sub-trees are conventionally called the
left sub-tree and the right sub-tree because they are typically depicted to the left and right
of the node respectively. Each left sub-tree or right sub-tree is either empty, or contains a
node and other sub-trees. In theory, the whole structure can continue ad infinitum. Figure
17-1 shows the structure of a small binary tree.
The real power of binary trees becomes evident when you use them for sorting data. If
you start with an unordered sequence of objects of the same type, you can use them to
construct an ordered binary tree and then walk through the tree to visit each node in an
ordered sequence. The algorithm for inserting an item into an ordered binary tree is
shown below:
If the tree, T, is empty


Then
Construct a new tree T with the new item I as the node, and empty left and
right sub-trees
Else
Examine the value of the node, N, of the tree, T
If the value of N is greater than that of the new item, I
Then
If the left sub-tree of T is empty
Then
Construct a new left sub-tree of T with the item I as the node, and
empty left and right sub-trees
Else
Insert I into the left sub-tree of T
End If
Else
If the right sub-tree of T is empty
Then
Construct a new right sub-tree of T with the item I as the node, and
empty left and right sub-trees
Else
Insert I into the right sub-tree of T
End If
End If
End If

Figure 17-1 A binary tree.
Notice that this algorithm is recursive, calling itself to insert the item into the left or right
sub-tree depending on the value of the item and the current node in the tree.
NOTE
The definition of the expression greater than depends on the type of data in the item and

node. For numeric data, greater than can be a simple arithmetic comparison, for text data
it can be a string comparison, but other forms of data must be given their own means of
comparing values. This is discussed in more detail when we implement a binary tree in
the section “Building a Binary Tree Class Using Generics” later in this chapter.
If you start with an empty binary tree and an unordered sequence of objects, you can
iterate through the unordered sequence inserting each one into the binary tree by using
this algorithm, resulting in an ordered tree. Figure 17-2 shows the steps in the process for
constructing a tree from a set of 5 integers.

Figure 17-2 Constructing an ordered binary tree.
Once you have built an ordered binary tree, you can display its contents in sequence by
visiting each node in turn and printing the value found. The algorithm for achieving this
task is also recursive:
If the left sub-tree is not empty
Then
Display the contents of the left sub-tree
End If
Display the value of the node
If the right sub-tree is not empty
Then
Display the contents of the right sub-tree
End If
Figure 17-3 shows the steps in the process for outputting the tree constructed earlier.
Notice that the integers are now displayed in ascending order.

Figure 17-3 Printing an ordered binary tree.
Building a Binary Tree Class Using Generics
In the following exercise, you will use generics to define a binary tree class capable of
holding almost any type of data. The only restriction is that the data type must provide a
means of comparing values between different instances.

The binary tree class is a class that you might find useful in many different applications.
Therefore, you will implement it as a class library rather than an application in its own
right. You can then reuse this class elsewhere without needing to copy the source code
and recompile it. A class library is a set of compiled classes (and other types such as
structs and delegates) stored in an assembly. An assembly is a file that usually has the
“.dll” suffix. Other projects and applications can make use of the items in a class library
by adding a reference to its assembly, and then bringing its namespaces into scope with
using statements. You will do this when you test the binary tree class.
The System.IComparable and System.IComparable<T> Interfaces
If you need to create a class that requires you to be able to compare values according to
some natural (or possibly unnatural) ordering, you should implement the IComparable
interface. This interface contains a method called CompareTo, which takes a single
parameter specifying the object to be compared to the current instance and returns an
integer that indicates the result of the comparison as shown in the following table.
Value Meaning
Less than zero The current instance is less than the value of the parameter
Zero The current instance is equal to the value of the parameter.
Greater than zero The current instance is greater than the value of the parameter
As an example, consider the Circle class that was described in Chapter 7, “Creating and
Managing Classes and Objects,” and is reproduced below:
class Circle
{
public Circle(double initialRadius)
{
radius = initialRadius;
}

public double Area()
{
return 3.141593 * radius * radius;

}

private double radius;
}
You can make the Circle class comparable by implementing the System.IComparable
interface and providing the CompareTo method. In the example shown, the CompareTo
method compares Circle objects based on their areas. The area of a circle with a larger
area is greater than a circle with a smaller area.
class Circle : System.IComparable
{
...
public int CompareTo(object obj)
{
Circle circObj = (Circle)obj; // cast the parameter to its real type
if (this.Area() == circObj.Area())
return 0;

if (this.Area() > circObj.Area())
return 1;

return -1;
}
}
If you examine the System.IComparable interface, you will see that its parameter is
defined as an object. However, this approach is not typesafe. To understand why this is
so, consider what happens if you try to pass something that is not a Circle to the
CompareTo method. The System.IComparable interface requires the use of a cast in order
to be able to access the Area method. If the parameter is not a Circle but some other type
of object then this cast will fail. However, the System namespace also defines the generic
IComparable<T> interface, which contains the following methods:

int CompareTo(T other);
bool Equals(T other);
Notice there is an additional method in this interface, called Equals, which should return
true if both instances are equals, false if they are not equals.
Also notice that these methods take a type parameter (T) rather than an object, and as
such, are much safer than the non-generic version of the interface. The following code
shows how you can implement this interface in the Circle class:
class Circle : System.IComparable<Circle>
{
...
public int CompareTo(Circle other)
{
if (this.Area() == other.Area())
return 0;

if (this.Area() > other.Area())
return 1;

return -1;
}

public bool Equals(Circle other)
{
return (this.CompareTo(other) == 0);
}
}
The parameters for the CompareTo and Equals method must match the type specified in
the interface, IComparable<Circle>. In general, it is preferable to implement the
System.IComparable<T> interface rather than System.IComparable. You can also
implement both, as many of the types in the .NET Framework do.

Create the Tree<T> class
1. Start Visual Studio 2005.

×