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