Sunday, February 5, 2012

- Generics

Let's go through some of common non_generic collection belong to System.Collections
  • ArrayList : Dynamic size collection of objects listed in sequential order.
  • Hashtable : Collection of key/value pairs that are organized based on the hash code of the key.
  • Queue : FIFO collection.
  • SortedList : Collection of key/value pairs that sorted by the keys and are accessible by key and by index.
  • Stack : LIFO collection.
Furthermore here are some key interfaces implemented by some of the above non-generic types:
  • ICollection : General Characteristic such as; size, enumeration, and thread safety.
  • ICloneable : Return a copy of itself
  • IDictionary : Represent it contents using key/value pairs.
  • IEnumerable : Retrun object implemented IEnumerator.
  • IList : Provides add, remove, and index item in a sequential list of objects,
System.Collections.Specialized namespace of System.dll also added some specialized collection types:
BitVector32, ListDictionary, StringDictionary, StringCollection.

Issue With Non_Generics Collections:
  • poor performance since CLR needs to perform number of memory transfer operations when you store structures in a classic collection classes.
  • not type safe since they were developed to operate on System.Objects.
Boxing / UnBoxing:  boxing is store the data in a value type within a reference variable (assigned a value type to a System.Object variable).  Unboxing is opposite of boxing Consider the following example:

//ValueType
int ValueTypeInt = 30;

//Box int into the object reference
object boxedInt = ValueTypeInt;

//Unbox the reference back into the corresponding int.
int UnboxedInt = (int)boxedInt;

You need to place the unbox block in try/catch since it might cause an exception if you are not expecting correct underling value.
Any non_generic type built to operate on objects, represent data allocated on the heap. Runtime automatically boxes the stack-based data on your behalf, and you need to unbox it when you want to retrieve an item. Boxing and unboxing cause performance issues.

Type Safety Issue:
Type safety  insure C# compiler determine any attempt to insert an incompatible data type.Custom collections (strongly typed) can provide type safety, however the problem is that you need to create an almost identical custom collection for each unique data type you wish to contain. Consider the following example:

public class ShapeCollection : IEnumerable
{
    //declare a private ArrayList
    private ArrayList shape_array = new ArrayList();

    public Shape GetShape(int pos)
    {
        return (Shape)shape_array[pos];
    }

    //Only accept Shape type
    public void AddShape(Shape s)
    {
        shape_array.Add(s);
    }

    public void ClearShape()
    {
        shape_array.Clear();
    }

    public int Count
    {
        get { return shape_array.Count; }
    }

    public IEnumerator GetEnumerator()
    {
        return shape_array.GetEnumerator();
    }
}

There is no way to get rid of boxing and unboxing using non-generic custom collection classes. System.Collection.Generic namespace contain generic types such as  List<> generic type which help you to obtain type safe and escape from boxing/unboxing penalties.

Generics:
You can write classes, structures, interfaces, and delegates generically but not enum types.
Object browser (System.Collections.Generic)
 As you see there is a letter or other token within a angled brackets. These tokens can be called type parameters or placeholdes.
  • T is used for types
  • TKey or K is used for keys
  • TValue or V is used for values.
When you specify a type parameter for a generic class or structure, all occurrences of the placeholders are now replaced with your supplied value. Let's go through an example:

Generic member example:

int[] numbers = {100, 50, 2, 30, 70, 5};
//Type parameter for Generic Members
Array.Sort<int>(numbers);

Generic Interfaces:
It is always better if you choose to use generic interfaces in case of availability to avoid runtime checks and casting operations. IComparable generic interface is look like this:

public interface IComparable<in T>
{
    int CompareTo(T other);
}

Implementation of this interface is goes as follow:

class Shape:IComparable<Shape>
{
    //Explicit implementation
    int IComparable<Shape>.CompareTo(Shape other)
    {
        if (this.ShapeID > other.ShapeID)
            return 1;
        else if (this.ShapeID < other.ShapeID)
            return -1;
        else
            return 0;
    }
}
For more information about generic collection you can refer to System.Collections.Generic Namespace at MSDN


SortedSet<T> :
A generic class introduced in .NET 4.0 which automatically sort the items. You need to send an object which implement the IComparer<T> as a constructor argument to specify the criteria for sorting. Consider the following example:

//sort by ShapeID
class SortShpaesByID : IComparer<Shape>
{
    public int Compare(Shape first_shape, Shape second_shape)
    {
        if (first_shape.ShapeID > second_shape.ShapeID)
            return 1;
        else if (first_shape.ShapeID < second_shape.ShapeID)
            return -1;
        else
            return 0;
    }
}

You may use the above generic class like this:

static void Main(string[] args)
{
    //create a SortedSet
    SortedSet<Shape> sortedShapes = new SortedSet<Shape>(new SortShpaesByID());
}

When your overloaded methods just differ by incoming arguments, you may use generics. Example:

static void Swap<T>(ref T first, ref T second)
{
    T temp = first;
    first = second;
    second = temp;
}

You can use the above generic like this:

int int_1 = 3;
int int_2 = 60;
Swap<int>(ref int_1, ref int_2);

You can optionally remove the type parameter if and only if the generic method requires arguments.
In order to set the default value for generic you can use default keyword just like the following example:
T temp = first;

first = default(T);
Generics classes can be the base class and derived class need to follow these rules:
  • Derived class must specify a type parameter when a non-generic class extends a generic class.
  • Derived type must override the generic methods using the specified type parameter, when generic base class defines generic virtual or abstract methods.
  • The child class can (optionally) reuse the type placeholder in its definition, when the derived type is generic as well.
Constraining Type Parameters:
With help of where keyword you can get extremely specific about what a given type parameter must look like:
  • where T : struct  (T must be a structure, "must have System.ValueTyep")
  • where T : class   (T must be reference type)
  • where T : new()  (T must have default constructor)
  • where T : NameOfBaseClass  (T must be derived from the class specified by name of baseClass)
  • where T : NameOfInterface     (T must implement the interface specified by NameOfInterface)
Note that new() constrain must be listed last. If your generic collection class have multiple type parameters, you can specify a unique set of collection for each using separate where clauses. Last but not least there is lack of operator constraints which means you cannot apply operators to type parameters. However for custom class or structure type, the compiler could assume the class supports the +, -, *, and / operator. Also generic type can be constrained by supported operators:

public class Calculate<T> where T: operator +, operator -, operator *, operator /
{
    public T Add (T first, T second)
    {
        return first + second;
    }
    .
    .
    .
}


Reference: Pro C# 2010 and the .NET 4 Platform by Andrew Troelsen.

No comments:

Post a Comment