The System.Collections.Generic Namespace

When you are building a .NET Core application and need a way to manage in-memory data, the classes of System.Collections.Generic will most likely fit the bill.

The System.Collections.Generic namespace defines generic replacements for many of them.

Dictionary<TKey,TValue> : This represents a generic collection of keys and values.

LinkedList<T> : This represents a double linked list.

List<T> : This is a dynamically resizable sequential list of items.

Queue<T> : This is a generic implementation of a FIFO (First-in First-out) list.

SortedDictionary<TKey,TValue> : This is a generic implementation of a sorted set of key-value pairs.

SortedSet<T> : This represents a collection of objects that is maintained in sorted order with no duplication.

Stack<T> : This is a generic implementation of a LIFO (Last-in First-Out).


Understanding Collection Initialization Syntax

This C# language feature makes it possible to populate many containers with items by using syntax similar to what you use to populate a basic array.

Note: You can apply collection initialization syntax only to classes that support an Add() method, which is formalized by the ICollection<T>/ICollection interfaces.

// Init a standard array.
int[] myArrayOfInts = { 0, 1, 2, 3 };
// Init a generic List<> of ints.
List<int> myGenericList = new List<int> { 0, 1, 2, 3 };
// Init an ArrayList with numerical data.
ArrayList myList = new ArrayList { 0, 1, 2, 3 };

If your container is managing a collection of classes or structure, you can blend object initialization syntax with collect initialization syntax to yield some functional code.

List<Point> myListOfPoints = new List<Point>
{
     new Point {xPos=2,yPos=2}
};


Working with the List<T> Class

The List<T> class is bound to be your most frequently used type in the System.Collections.Generic namespace because it allows you to resize the contents of the container dynamically.

List<Person> people = new List<Person>
 {
     new Person{FirstName="Dotnet",LastName="Guard",Age=47}
 };
foreach(Person p  in people)
 {
     Console.WriteLine(p);
 }
people.Insert(2, new Person { FirstName = "Maggie", LastName = "Simpson", Age = 2 });
Person[] arrayOfPeople = people.ToArray();

Working with the Stack<T> Class

The Stack<T> class represents a collection that maintains items using a last-in, first-out manner. As you might expect, Stack<T> defines members named Push() and Pop() to place items onto or remove items from the stack.

Stack<Person> stackOfPeople = new();
stackOfPeople.Push(new Person { FirstName = "Homer", LastName = "Simpson", Age = 47 });
stackOfPeople.Push(new Person { FirstName = "Maige", LastName = "Simpson", Age = 45 });
stackOfPeople.Push(new Person { FirstName = "Lisa", LastName = "Simpson", Age = 9 });
Console.WriteLine($"First person is : {stackOfPeople.Peek()}");
Console.WriteLine($"Popped off is: {stackOfPeople.Pop()}");
Console.WriteLine($"First person is : {stackOfPeople.Peek()}");
Console.WriteLine($"Popped offis  : {stackOfPeople.Pop()}");
Console.WriteLine($"First person is : {stackOfPeople.Peek()}");
Console.WriteLine($"Popped offis  : {stackOfPeople.Pop()}");
try
 {
     Console.WriteLine($"First person is : {stackOfPeople.Peek()}"); // Stack Empty
     Console.WriteLine($"Popped off  is  : {stackOfPeople.Pop()}"); // Error.
 }
catch (InvalidOperationException ex)
 {
     Console.WriteLine(ex.Message);
 }

Working with the Queue<T> Class

Queues are containers that ensure items are accessed in a first-in, first-out (fifo) manner. Sadly, we humans are subject to queues all day long : lines at the bank, lines at the movie theater, and lines at the morning coffeehouse.

When you need to model a scenario in which items are handled on a first come, first-served basis, you will find the Queue<T> class fits the bill.

Dequeue() : Removes and returns the object at the beginning of the Queue<T>.

Enqueue() : Adds an object to the end of the Queue<T>.

Peek() : Returns the object at the beginning of the Queue<T> without removing it.

static void GetCoffee(Person p)
 {
     Console.WriteLine(p.FirstName);
 }
 
Queue<Person> people = new();
people.Enqueue(new Person { FirstName = "Homer", LastName = "Simpson", Age = 47 });
people.Enqueue(new Person { FirstName = "Magie", LastName = "Simpson", Age = 45 });
people.Enqueue(new Person { FirstName = "Lisa", LastName = "Simpson", Age = 9 });
// Peek at first person in Q.
Console.WriteLine(people.Peek().FirstName);
GetCoffee(people.Dequeue()); 
GetCoffee(people.Dequeue()); 
GetCoffee(people.Dequeue());
try {
     GetCoffee(people.Dequeue());
 }catch(InvalidOperationException e)
 {
     Console.WriteLine(e.Message);
 }

Working with the PriorityQueue<TElement,TPriority> Class

The PriorityQueue works just like the Queue<T> except that each queued item is given a priority when items are dequeued, they are remove from lowest to highest priority.

PriorityQueue<Person,int> people = new();
people.Enqueue(new Person { FirstName = "Homer", LastName = "Simpson", Age = 47 },1);
people.Enqueue(new Person { FirstName = "Magie", LastName = "Simpson", Age = 45 },3);
people.Enqueue(new Person { FirstName = "Lisa", LastName = "Simpson", Age = 9 },3);
people.Enqueue(new Person { FirstName = "John", LastName = "Simpson", Age = 9 },2);
// Peek at first person in Q.
while (people.Count > 0)
 {
     Console.WriteLine(people.Dequeue().FirstName);
 }

If more than one item is set to the current lowest priority, the order of dequeuing is not guaranteed.

Working with the SortedSet<T> Class

This class is useful because it automatically ensures that items in the set are sorted when you insert or remove items. However, you do need to inform the SortedSet<T> class exactly how you want if to sort the objects, by passing in as a constructor argument an object that implements the generic IComparer<T> interface.

class SortPeopleByAge : IComparer<Person>
 {
     public int Compare(Person? p, Person? p2)
     {
         if (p?.Age > p2?.Age)
         {
             return 1;
         }
         if (p?.Age < p2.Age)
         {
             return -1;
         }
         return 0;
     }
 }
 
SortedSet<Person> s = new SortedSet<Person>(new SortPeopleByAge())
 {
     new Person { FirstName = "Homer", LastName = "Simpson", Age = 47 },
     new Person { FirstName = "Merge", LastName = "Simpson", Age = 45 },
     new Person { FirstName = "Lisa", LastName = "Simpson", Age = 9 },
     new Person { FirstName = "Bart", LastName = "Simpson", Age = 8 },
 };
// Enumerate
foreach(Person p in s)
 {
     Console.WriteLine(p);
 }

Working with the Dictionary<TKey,TValue> Class

This class which allows you to hold any number of objects that may be referred to via a unique key. Thus, rather than obtaining an item from a List<T> using a numerical identifier, you could use the unique text key.

Like other collection objects, you can populate a Dictionary<TKey,TValue> by calling the generic Add() method manually. However, you can also fill a Dictionary<TKey,TValue> using collection initialization syntax. Do be aware that when you are population this collection object, key names must be unique. If you mistakenly specify the some key multiple times, you will receive a runtime exception.

Dictionary<string, Person> people = new Dictionary<string, Person>();
people.Add("Homer", new Person { FirstName = "Homer", LastName = "Simpson", Age = 47 });
people.Add("Linda", new Person { FirstName = "Linda", LastName = "Simpson", Age = 47 });
people.Add("Lisa", new Person { FirstName = "Lisa", LastName = "Simpson", Age = 47 });
// Get Homer
Person homer = people["Homer"];

It's also possible to populate a Dictionary<TKey,TValue> using a related initialization syntax that is specific to this type of container (not surprisingly termed dictionary initialization).