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

The Generics Solution

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 (22.11 KB, 4 trang )



The Generics Solution
Generics was added to C# 2.0 to remove the need for casting, improve type safety, reduce
the amount of boxing required, and to 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. Version 2.0 of 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 fragment 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 above sample:

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 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 the cast to the Circle type when invoking the Dequeue method unnecessary.
The compiler will check to ensure that types are not accidentally mixed, generating an
error at compile time rather than runtime 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 Visual Studio 2005
Documentation, you will notice that it is defined as:


public class Queue<T> : …
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, as shown in the previous example which specifies Circle.
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 allowing you to 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). If we take the intQueue variable as an example, 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 non-generic Queue class shown in the
previous 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.Collecions.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<T, U>
A dictionary provides a collection of key/value pairs. You store values (type U) with an
associated key (type T), and then retrieve them by specifying the key to look up. The
Dictionary class provides an indexer that allows you to access items by using array
notation. It is defined like this:
public virtual U this[ T key ] { get; set; }
Notice that the indexer accesses values of type U by using a key of type T. 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;
...
john = directory["John"];
As with the generic Queue class, the compiler will detect attempts to store values other
than Person structures, as well as ensuring that the key is always a string value. For more
information about the Dictionary class, you should read the Visual Studio 2005
documentation.
NOTE

You can also define generic structs and interfaces by using the same 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
example, 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, but these are all
instances of the same class.
Contrast 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 compiler 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.
Generics and Constraints
Sometimes there will be occasions when you want to ensure that the type parameter used
by a generic class identifies a type that provides certain methods. For example, if you are
defining a PrintableCollection class, you might want to ensure that all objects stored in
the class have a Print method. You can specify this condition by using a constraint.
Using a constraint enables you to limit the type parameters of a generic class to those that
implement a particular set of interfaces, and therefore provide the methods defined by
those interfaces. For example, if the IPrintable interface contained the Print method, you
could define the PrintableCollection class like this:
public class PrintableCollection<T> where T : IPrintable
When the class is compiled, the compiler will check to ensure that the type used for T
actually implements the IPrintable interface and will stop with a compilation error if it
doesn't.




Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Tải bản đầy đủ ngay
×