Description

In the past if you wanted to creare a list of int or a list of Book objects, you had to create 2 methods or classes to do this.
Even if creating a list of objects that will in effect work for both int and Book objects, you will incur performance penelaties by boxing and unboxing.
So, Generics introduced the generic T type, that can be used by any type, and create an generic list of the actual type, so no boxing and unboxing is neccessary.

Generic collections in .NET

All the generic collections in .NET is stored under System.Collections.Generic...

Dictionary

A dictionary is a generic data structure that uses a hash table to store and retrieve objects and is very fast and effecient.
You have to define a key and value pair that can be of any type.

var dictionary = new GenericDictionary<string, Book>();
dictionary.Add("1234",new Book());

Any type can be passed to the T type.
If you want to limit what can be passed to T, you can define a constraint.

public T Max<T>(T a, T b) where T : IComparable
{
    return a.CompareTo(b) > 0 ? a : b;
}

We added a constraint here to specify that T must implement the IComparable interface, which provides a method called CompareTo. You can also move the constraint to class level and then you don't have to specify the constraint on the method.

public class Utilities<T> where T : IComparable
{
    public T Max(T a, T b)
    {
        return a.CompareTo(b) > 0 ? a : b;
    }
}

Additional Constraint Types:
where T : IComparable (applying a constraint to an interface)
where T : Product (applying a constraint to a class, so if T is a Product or any of its sub classes)
where T : struct (where T should be a value type, so using the key word struct)
where T : class (where T should be class (reference type))
where T : new() (where T is an object that has a default contructor)
You can add multiple constraints to a method or class:

public class Utilities<T> where T : IComparable, new()
{
    public void DoSomething(T value)
    {
        var obj = new T();
    }
}

You were able to create a new instance of the T value type, because you added the new() constraint to the class, specifying that T must be an object with a default constructor.