Wednesday, 27 May 2015

Generics

Generics allow code reuse with type safety.  A generic is a class that defers specifying the type until it is instantiated by the client.

Generics

In ArrayLists, you store references to objects, and so when retrieving an object from an ArrayList you typically had to cast it into the type you want.  Also, you couldn't prevent objects of the wrong/a different type being added into your array (because the array holds refs to objects).  So an ArrayList of employees would happily accept you adding a string into the array.  You'd probably get an error when retrieving that array item though.  You lost type safety and had to do lots of checks and casts.  If you wanted different types of ArrayLists, e.g. Employees and Projects, you'd have to write practically identical code to create your own custom ArrayList type.

Generics avoid all of that.  Also produce better performance due to no boxing for value types

Generic Collections:-
  • HashSet<T> - Set of items; no duplicates allowed.  (Compiler calculates the hash value of every item being added to ensure no other objects with the same hash)
  • List<T> - Like an array that can dynamically array.  No restrictions on type, duplicates.  Methods to sort, order list.
  • Queue<T> - FIFO list  (Enqueue and Dequeue operations)
  • Stack<T> - LIFO (push and pop operations).
  • Dictionary<Tkey, Tvalue> - Key, Value pair.  Look up by Key using hash codes, not index over a list.

Generic Types for value types, e.g. List<int>.  CLR generates a specialised generic type for that value type.  (So the generic type for a List<int> is different from the generic type for a List<datetime>.  It pre-allocates space on the heap for these types.  Thus the List<int> stores ints, not references to objects, so there is no boxing/unboxing required.

It is slightly different for ref types, e.g. List<employee>.  The Run time creates a specialised generic of ref types, irrespective of what the ref type is.  So List<projects> will create the same specialised generic type as List<employee>.  (Like templates in C++).  Pointers are always the same size for a ref type, hence you can use one specialised ref type for generics holding a ref types.

Public class MagicHat<T>
{
   public void Add(T thing)
  {
      _things.Add(thing);
  }
  List<T> _things;
}

//
Rabbit rabbit = new Rabbit { Name = "Fuffy" };
MagicHat<Rabbit> _rabbitHat = new MagicHat<Rabbit>();
_rabbitHat.Add(rabbit);

Or
Class MagicHat<T> : Queue<T>
{}


Generic Constraints

Restrictions on the type of parameter
Force the type to be a struct or class
Force the type to have a public default constructor
Force the type to implement an interface or derive from a base class

Public class MagicHat<Tanimal> where Tanimal : Ianimal
{
  …
}

Need at least 1 constraint when you want to perform some operation within the generic collection class that isn't supported by System.Object.

Null references and Generic Types

It's ok to compare value types with null (It's always false)
But it's not ok to assign null to a <T> because you can't assign null to a value type.  (Set a constraint where T: class enforces ref types).

Where T: class, new()    <- new() enforces a default constructor (0 params)
To ensure a type has a method, force it to implement an interface.  The interface defines that method.  (Could also be a base class).

Generic Terminology : Type Classifications

Unbound generic types.  Type param hasn’t been specified - List<>
Can be used in reflective code with the typeof operator.  It's a blueprint to create other types.  The opposite is a constructed generic type

Constructed Generic type - two forms
Open Generic Type - has type parameter that still needs to be substitued - List<T>
Closed Generic Type - has no type parameters - List<int>

An object constructed from an instance of a generic type is a concrete object
At runtime, code executes in a closed, constructed type.

Thus in inheritance - when a generic class inherits, you can't inherit from an unbound generic type - must be constructed (but could be open or closed).
Thus:
Public class AnimalCollection<Tanimal> : Icollection<Tanimal> {}
Or
Public class AnimalCollection<Tanimal> : Icollection<int> {}


When defining a non-generic type that inherits, it can only inhert from a closed constructed generic type.  Thus:
Public class RabbitCollection : Icollection<Rabbit> {}


Might see this in Reflection or Inversion of control container programming.

The default keyword

The default keyword can be used to assign a default value to a generic when you don't know if it will be using a value or reference type.  Useful for returning results from a method instead of raising an exception.     You can't return a null as you can't assign a null to a value type.

C# supports Generic Interfaces and Generic Delegates (EventHander, Func, Action and Predicate).
Generics and Variance
Before C# 4.0 generic collections were invariant.  That is they didn't support polymorphism.  If a generic class Dog implemented IAnimal you couldn't pass an object of type Dog into a method that expected an input parameter that implemented IAnimal  Generics were invariant.  This changed in C# 4.0, with exceptions.

  • <T> must be a reference type
  • It only works with some specific generic interfaces - IEnumerable<T>, IEnumerator<T>, IQueryable<T> and IGrouping<T> (These are popular with Linq)




No comments:

Post a Comment