Generics






Generics

When you design classes, you do so without a crystal ball. Classes are designed based on the available knowledge of who the consumers of the class likely could be, and the data types those developers would like to use with your class.

While developing a system, you may have a need to develop a class that stores a number of items of the integer type. To meet the requirement, you write a class named Stack that provides the capability to store and access a series of integers:

using System;

public class Stack
{
    int[] items;

    public void Push (int item) {...};
    public int Pop() {...};

}

Suppose that at some point in the future, there is a request for this same functionality, only the business case requires the support for the double type. This current class will not support that functionality, and some changes or additions need to be made.

One option would be to make another class, one that provided support for the double type:

public class DoubleStack
{
    double[] items;

    public void Push (double item) {...};
    public int Pop() {...};

}

But doing that is not ideal. It leaves you with two classes, and the need to create others if requests to support different types arise in the future.

The best case would be to have a single class that was much more flexible. In the .NET 1.x world, if you wanted to provide this flexibility, you did so using the object type:

using System;

public class Stack
{
    object[] items;

    public void Push (object item) {...};
    public int Pop() {...};

}

This approach provides flexibility, but that flexibility comes at a cost. To pop an item off the stack, you needed to use typecasting, which resulted in a performance penalty:

double d = (double)stack.Pop;

In addition, that class allowed the capability to write and compile the code that added items of multiple types, not just those of the double type.

Fortunately, with version 2.0 of the .NET Framework, Generics provide us a more palatable solution. So what are Generics? Generics are code templates that allow developers to create type-safe code without referring to specific data types.

The following code shows a new generic, GenericStack<M>. Note that we use a placeholder of M in lieu of a specific type:

public class GenericStack<M>
{
    M[] items;

    void Push(M input) { }
    public M Pop() {}
}

The following code shows the use of GenericStack<M>. Here, two instances of the class are created, stack1 and stack2. stack1 can contain integers, whereas stack2 can contain doubles. Note that in place of M, the specific type is provided when defining the variables:

class TestGenericStack
{

    static void Main()
    {
        // Declare a stack of type int
        GenericStack<int> stack1 = new GenericStack<int>();

       // Declare a list of type double
       GenericStack<double> stack2 = new GenericStack<double>();

    }
}

This provides support for us to use the functionality for the types we know we must support today, plus the capability to support types we'll be interested in in the future. If in the future, you create a class named NewClass, you could use that with a Generic with the following code:

// Declare a list of the new type
GenericStack<NewClass> list3 = new GenericStack<NewClass>();

In GenericStack<M> class, M is what is called a generic-type parameter. The type specified when instantiating the GenericStack<M> in code is referred to as a type argument. In the code for the TestGenericStack class, int and double, are both examples of type arguments.

In the preceding example, there was only one generic-type parameter, M. This was by choice and not because of a limitation in the .NET Framework. The use of multiple generic-type parameters is valid and supported, as can be seen in this example:

class Stock<X, Y>
{

    X identifier;
    Y numberOfShares;


    ...

}

Here are two examples of how this could then be used in code. In the first instance, the identifier is an int, and the number of shares a double. In the second case, the identifier is a string, and the number of shares is an int:

Stock<int,double> x = new Stock<int,double>();
Stock<string,int> y = new Stock<string,int>();

In these examples, we've also used a single letter to represent our generic-type parameter. Again, this is by choice and not by requirement. In the Stock class example, X and Y could have also been IDENTIFIER and NUMBEROFSHARES.

Now that you've learned the basics of generics, you may have a few questions: What if I don't want to allow just any type to be passed in, and want to make sure that only types deriving from a certain interface are used? What if I need to ensure that the type argument provided refers to a type with a public constructor? Can I use generic types in method definitions?

The good news is that the answer to each of those questions is yes.

There will undoubtedly be instances in which you will want to restrict the type arguments that a developer may use with the class. If, in the case of the Stock<X, Y> generic, you wanted to ensure that the type parameter provided for X was derived from IStockIdentifier, you could specify this, as shown here:

public class Stock<X, Y> where X:IStockIdentifier

Although this example uses a single constraint that X must derive from IStockIdentifier, additional constraints can be specified for X, as well as Y.

In regard to other constraints for X, you may want to ensure that the type provided has a public default constructor. In that way if your class has a variable of type X that creates a new instance of it in code, it will be supported.

By defining the class as

public class Stock<X, Y> where X:new()

you ensure that you can create a new instance of X within the class:

X identifier = new X();

As mentioned earlier, you can specify multiple constraints. The following is an example of how to specify that the Stock class is to implement both of the constraints listed previously:

public class Stock<X, Y> where X:IStockIdentifier, new()

In the GenericStack<M> example, you saw that there were Push and Pop methods:

public class GenericStack<M>
{
    M[] items;

    void Push(M input) { }
    public M Pop() {}
}

Push and Pop are examples of generic methods. In that code, the generic methods are inside of generic types. It is important to note that they can also be used outside of generic types, as seen here:

public class Widget
{
    public void WidgetAction<M>(M m)
    {...}
}

Note

It should be noted that attempts to cast a type parameter to the type of a specific class will not compile. This is in keeping with the purpose of genericscasting to a specific type of class would imply that the generic-type paremeter is, in fact, not generic. You may cast a type parameter, but only to an interface.


Inheritance is the last area we'll cover on Generics in this chapter. Inheritance is handled in a very straightforward mannerwhen defining a new class that inherits from a generic, you must specify the type argument(s).

If we were to create a class that inherited from our Stock class, it would resemble the following code:

public class Stock<X,Y>
{...}
public class MyStock : Stock<string,int>
{...}



 Python   SQL   Java   php   Perl 
 game development   web development   internet   *nix   graphics   hardware 
 telecommunications   C++ 
 Flash   Active Directory   Windows