C Sharp Programming/Generics

From Wikibooks, the open-content textbooks collection

Jump to: navigation, search

Generics are a new feature in version 2.0 of the C# language and the common language runtime (CLR). Generics introduce to the .NET Framework the concept of type parameters, which make it possible to design classes and methods that defer the specification of one or more types until the class or method is declared and instantiated by client code. The most common use of generics is to create collection classes. Generic types were introduced to maximize code reuse, type safety, and performance.[1]

Contents

[edit] Generic Classes

There are cases when you need to create a class to manage objects of some type, without modifying them. Without Generics, the usual approach (highly simplified) to make such class would be like this:

public class SomeObjectContainer
{
    private object obj;
 
    public SomeObjectContainer(object obj)
    {
        this.obj = obj;
    }
 
    public object GetObject()
    {
        return this.obj;
    }
}

And its usage would be:

class Program
{
    static void Main(string[] args)
    {
        SomeObjectContainer container = new SomeObjectContainer(25);
        SomeObjectContainer container2 = new SomeObjectContainer(5);
        Console.WriteLine((int)container.GetObject() + (int)container2.GetObject());
 
        Console.ReadKey(); // wait for user to press any key, so we could see results
    }
}

Notice that we have to cast back to original data type we have chosen (in this case - int) every time we want to get an object from such a container. In such small programs like this everything is clear. But in more complicated cases with more containers in different parts of the program, we would have to take care that the container is supposed to have int type in it, would not have a string or any other data type. If that happens, InvalidCastException is thrown.

Additionally, if the original data type we have chosen is a value type, such as int, we will incur a performance penalty every time we access the elements of the collection, due to the Autoboxing feature of C#.

However, we could surround every unsafe area with try - catch block, or we could create a separate "container" for every data type we need, just to avoid casting. While both ways could work (and worked for many years), it is unnecessary now, because Generics offers a much more elegant solution.

To make our "container" class to support any object and avoid casting, we replace every previous object type with some new name, in this case - T, and add <T> mark immediately after the class name to indicate that this "T" type is Generic / any type.

Note: You can choose any name and use more than one generic type for class, i.e <genKey, genVal>
public class GenericObjectContainer<T>
{
    private T obj;
 
    public GenericObjectContainer(T obj)
    {
        this.obj = obj;
    }
 
    public T getObject()
    {
        return this.obj;
    }
}

Not a big difference, which results in simple and safe usage:

class Program
{
    static void Main(string[] args)
    {
        GenericObjectContainer<int> container = new GenericObjectContainer<int>(25);
        GenericObjectContainer<int> container2 = new GenericObjectContainer<int>(5);
        Console.WriteLine(container.getObject() + container2.getObject());
 
        Console.ReadKey(); // wait for user to press any key, so we could see results
    }
}

Generics ensures that you specify the type for a "container" only when creating it, and after that you will be able to use only the type you specified. But now you can create containers for different object types, and avoid the previously mentioned problems. In addition, this avoids the Autoboxing for struct types.

While this example is far from practical, it does illustrate some situations where generics are useful:

  • You need to keep objects of a single type in a class
  • You don't need to modify objects
  • You need to manipulate objects in some way
  • You wish to store a "value type" (such as int, short, string, or any custom struct) in a collection class without incurring the performance penalty of Autoboxing every time you manipulate the stored elements.

[edit] Generic Interfaces

A generic interface is an interface that accepts one or more type parameters, similar to a generic class:

public interface IContainer<T>
{
    T GetObject();
    void SetObject(T value);
}
 
public class StringContainer : IContainer<string>
{
    private string str;
 
    public string GetObject()
    {
        return str;
    }
 
    public void SetObject(string value)
    {
        str = value;
    }
}
 
public class FileWithString : IContainer<string>
{
    ...
}
class Program
{
    static void Main(string[] args)
    {
        IContainer<string> container = new StringContainer();
 
        container.SetObject("test");
        Console.WriteLine(container.GetObject());
        container = new FileWithString();
        container.SetObject("another test");
        Console.WriteLine(container.GetObject());
 
        Console.ReadKey();
    }
}

Generic interfaces are useful when we could have multiple implementations of a particular generic class. For example, both the List<T> class (discussed below) and the LinkedList<T> class implement the IEnumerable<T> interface. List<T> has a constructor that creates a new list based on an existing object that implements IEnumerable<T>, and so we can write the following:

LinkedList<int> linkedList = new LinkedList<int>();
 
linkedList.AddLast(1);
linkedList.AddLast(2);
linkedList.AddLast(3);
// linkedList now contains 1, 2 and 3.
 
List<int> list = new List<int>(linkedList);
 
// now list contains 1, 2 and 3 as well!

[edit] Generic Methods

Generic methods are very similar to generic classes and interfaces:

public static bool ArrayContains<T>(T[] array, T element)
{
    foreach (T e in array)
    {
        if (e.Equals(element))
        {
            return true;
        }
    }
    return false;
}

This method can be used to search any type of array:

class Program
{
    static void Main(string[] args)
    {
        string[] strArray = { "string one", "string two", "string three" };
        int[] intArray = { 123, 456, 789 };
 
        Console.WriteLine(ArrayContains<string>(strArray, "string one")); // True
        Console.WriteLine(ArrayContains<int>(intArray, 135)); // False
    }
}

[edit] Type Constraints

You may specify one or more type constraints in any generic class, interface or method using the where keyword. The following example shows all of the possible type constraints:

public class MyClass<T, U, V, W>
    where T : class, // T must be a reference type (class, interface, delegate, array)
    new() // T must have a public constructor with no parameters
    where U : struct // U must be a value type (struct, int, long, float, double, byte, uint, etc.)
    where V : MyOtherClass, // V must be derived from MyOtherClass
    IEnumerable<U> // V must implement IEnumerable<U>
    where W : T, // W must be derived from T
    IDisposable // W must implement IDisposable
{
    ...
}

These type constraints are often necessary

  1. To create a new instance of a generic type (the new()) constraint
  2. To use foreach on a variable of a generic type (the IEnumerable<T> constraint)
  3. To use using on a variable of a generic type (the IDisposable constraint)

[edit] Notes