Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C# 2008 Step by Step.pdf
Скачиваний:
26
Добавлен:
25.03.2016
Размер:
13.96 Mб
Скачать

Chapter 18 Introducing Generics

335

The Queue data type expects the items it holds to be reference types. Enqueueing a value type, such as an int, requires it to be boxed to convert it to a reference type. Similarly, dequeueing into an int requires the item to be unboxed to convert it back to a value type. See the sections titled “Boxing” and “Unboxing” in Chapter 8 for more details. Although boxing and unboxing happen transparently, they add a performance overhead because they involve dynamic memory allocations. This overhead is small for each item, but it adds up when a program creates queues of large numbers of value types.

The Generics Solution

C# provides generics to remove the need for casting, improve type safety, reduce the

amount of boxing required, and make it easier to create generalized classes and methods. Generic classes and methods accept type parameters, which specify the type of objects that

they operate on. The .NET Framework class library includes generic versions of many of the collection classes and interfaces in the System.Collections.Generic namespace. The following code example shows how to use the generic Queue class found in this namespace to create a queue of Circle objects:

using System.Collections.Generic;

...

Queue<Circle> myQueue = new Queue<Circle>(); Circle myCircle = new Circle(); myQueue.Enqueue(myCircle);

...

myCircle = myQueue.Dequeue();

There are two new things to note about the code in the preceding example:

The use of the type parameter between the angle brackets, <Circle>, when declaring the myQueue variable

The lack of a cast when executing the Dequeue method

The type parameter in angle brackets specifies the type of objects accepted by the queue.

All references to methods in this queue will automatically expect to use this type rather than object, rendering unnecessary the cast to the Circle type when invoking the Dequeue

method. The compiler will check to ensure that types are not accidentally mixed and will

generate an error at compile time rather than at run time if you try to dequeue an item from circleQueue into a Clock object, for example.

If you examine the description of the generic Queue class in the Microsoft Visual Studio 2008 documentation, you will notice that it is defined as follows:

public class Queue<T> : ...

336 Part III Creating Components

The T identifies the type parameter and acts as a placeholder for a real type at compile time. When you write code to instantiate a generic Queue, you provide the type that should be substituted for T (Circle in the preceding example). Furthermore, if you then look at the methods of the Queue<T> class, you will observe that some of them, such as Enqueue and Dequeue, specify T as a parameter type or return value:

public void Enqueue( T item ); public T Dequeue();

The type parameter, T, will be replaced with the type you specified when you declared the queue. What is more, the compiler now has enough information to perform strict type checking when you build the application and can trap any type mismatch errors early.

You should also be aware that this substitution of T for a specified type is not simply a textual replacement mechanism. Instead, the compiler performs a complete semantic substitution so that you can specify any valid type for T. Here are more examples:

struct Person

{

...

}

...

Queue<int> intQueue = new Queue<int>(); Queue<Person> personQueue = new Queue<Person>();

Queue<Queue<int>> queueQueue = new Queue<Queue<int>>();

The first two examples create queues of value types, while the third creates a queue of queues (of ints). For example, for the intQueue variable the compiler will also generate the following versions of the Enqueue and Dequeue methods:

public void Enqueue( int item ); public int Dequeue();

Contrast these definitions with those of the nongeneric Queue class shown in the preceding section. In the methods derived from the generic class, the item parameter to Enqueue is passed as a value type that does not require boxing. Similarly, the value returned by Dequeue

is also a value type that does not need to be unboxed.

It is also possible for a generic class to have multiple type parameters. For example, the generic System.Collections.Generic.Dictionary class expects two type parameters: one type for

keys and another for the values. The following definition shows how to specify multiple type parameters:

public class Dictionary<TKey, TValue>

A dictionary provides a collection of key/value pairs. You store values (type TValue) with an associated key (type TKey) and then retrieve them by specifying the key to look up. The

Chapter 18 Introducing Generics

337

Dictionary class provides an indexer that allows you to access items by using array notation. It is defined like this:

public virtual TValue this[ TKey key ] { get; set; }

Notice that the indexer accesses values of type TValue by using a key of type TKey. To create and use a dictionary called directory containing Person values identified by string keys, you

could use the following code:

struct Person

{

...

}

...

Dictionary<string, Person> directory = new Dictionary<string, Person>(); Person john = new Person();

directory[“John”] = john;

...

Person author = directory[“John”];

As with the generic Queue class, the compiler will detect attempts to store values other than Person structures in the directory, as well as ensure that the key is always a string value. For more information about the Dictionary class, you should read the Visual Studio 2008

documentation.

Note You can also define generic structures and interfaces by using the same type–parameter syntax as generic classes.

Generics vs. Generalized Classes

It is important to be aware that a generic class that uses type parameters is different from a generalized class designed to take parameters that can be cast to different types. For ex-

ample, the System.Collections.Queue class is a generalized class. There is a single implementation of this class, and its methods take object parameters and return object types. You can use this class with ints, strings, and many other types; in each case, you are using instances of the

same class.

Compare this with the System.Collections.Generic.Queue<T> class. Each time you use this class with a type parameter (such as Queue<int> or Queue<string>) you actually cause the com-

piler to generate an entirely new class that happens to have functionality defined by the generic class. You can think of a generic class as one that defines a template that is then used by

the compiler to generate new type-specific classes on demand. The type-specific versions of a generic class (Queue<int>, Queue<string>, and so on) are referred to as constructed types,

and you should treat them as distinctly different types (albeit ones that have a similar set of methods and properties).

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]