Now that we know about primitive types, basic class concepts, and interfaces, we can discuss ways that C# programs can manipulate groups of objects. Let's dive into learning about arrays and collections!

A large, varied collection of toy and model cars sits on several small shelves.
I can't even count how many dimensions this array has. Or is it just one? Photo by Karen Vardazaryan / Unsplash

Arrays

An array is a collection of objects, each of which is of the same type. Items in an array are called elements. We declare an array by specifying the type of the elements in it:

int[] numbers;

This array has no elements and no defined size. We can create a new single-dimensional array by specifying the size of the array and using the new keyword:

int[] numbers = new int[5];

We initialize an array by giving it a set of values. In this example, the size of the array is inferred by the compiler from the number of values.

int[] numbers = new int[] { 1, 2, 3, 4, 5, 6, 7 };

We can also avoid the new keyword and give the array a set of values:

string[] months = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
                    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };

To retrieve a specific value out of an array, we specify the position of the value within the array. Note that since array indexes start at 0, if we want the seventh value, we must pass an index of 6:

var month1 = months[0]; //"Jan"
var month2 = months[6]; //"Jul"

Multi-dimensional Arrays

Arrays can have multiple dimensions. For example, a two-dimensional array is an array of arrays. We can declare a two-dimensional array by specifying the dimensions:

int[,] values = new int[5, 2]; //Holds 2 groups of 5 objects

Arrays can have as many dimensions as we want, however the more dimensions they have, the more complex they are to use. Typically we don't create an array with more than two dimensions, and I have never in my life seen an array with more than three.

We can initialize a multi-dimensional array in a similar way as a single-dimensional array:

//The below line creates a 3 x 4 multi-dimensional array
int[,] myNumbers = new int[,] { {5, 2, 6, 4}, {1, 8, 9, 2}, {9, 3, 4, 2} };

Note that if we initialize a multi-dimensional array with given values, we don't need to specify the array's size; it will be inferred from the number of values given, just like with single-dimensional arrays.

Accessing Elements

We can access elements in a multi-dimensional array by specifying the desired position in each dimension (remembering that array indexes start at 0):

int[,] myNumbers = new int[,] { {5, 2, 6, 4}, {1, 8, 9, 2}, {9, 3, 4, 2} };
var value = myNumbers[1, 3]; //2 ---------------------- ^

Ranges (C# 8.0)

A new feature in C# 8.0 is the ability to "slice" a single-dimensional array and get a subset of that array's elements returned. For example, let's imagine we have the following array of ten names:

var names = new string[] { "Andy", "Brittany", "Charles", "Damian", "Etan", "Franklin", "Georgina", "Hasan", "Indira", "Jannelle" };
Does this technically count as a multidimensional array, since strings are themselves arrays?

C# 8.0 introduces a new operator, the range operator .., that allows us to get a subset of a array. Let's say we want the names from Charlie (index 2) up to but not including Indira (index 8). We can get these names like so:

var nameSubset = names[2..8];

The object at the first index (2 in this case) will be included in the result set; the object at the second index (8) will not be included.

You can also declare ranges as variables. Said variables can then be used inside the [ and ] characters.

var range = 2..8;
var nameSubset = names[range];

The type of such a variable will be System.Range.

Open-Ended Ranges

We can also leave the range open-ended by not specifying a start point or an end point.

var nameSubset = names[3..]; //Gets all names from position 3
                             //to the end of the array,
                             //including the element at position 3.
                             
var otherNames = names[..7]; //Gets all names from the beginning
                             //of the array, up to but not including
                             //the element at position 7.

Indices (C# 8.0)

We have already seen how to access a particular element using its index:

var name = names[4];
Console.WriteLine(name); //Etan

By doing this, we are accessing an element based on its position relative to the start of the array.

Starting in C# 8.0, we can also access elements based on their position relative to the end of the array. In other words, we can get the second-to-last element, third-to-last element, and so on.

We do this using the ^ operator:

Console.WriteLine(names[^1]); //Jannelle
Console.WriteLine(names[^3]); //Hasan

Note that if we were to use ^0, we would get a runtime error.

Collections

Arrays are useful for dealing with a collection of a known size. But frequently, we don't know the number of elements we need to gather and operate on. For these situations, we are better off using collections.

Namespace

Generic collections are in the System.Collections.Generic namespace, which we need to include in any file that wants to use them:

using System.Collections.Generic;

The collection types we are talking about in this article are also examples of generics, which will be discussed in Part 15 of this series.

List<T>

The most common collection type in C# is List<T>. T is a placeholder for a type; when we create an object of type List<T>, we need to specify the type of the elements that will be held by the list. We will discuss this syntax more thoroughly in a later post about generics.

List<string> names = new List<string>();

Just like with arrays, we can initialize collections by specifying their elements:

List<int> years = new List<int> { 2020, 2019, 2018, 2017, 2016 };

We can use a foreach loop to access all elements of a list:

List<int> years = new List<int> { 2020, 2019, 2018, 2017, 2016 };

foreach(var year in years)
{
    Console.WriteLine(year.ToString());
}

We can also access individual elements in a collection using array indexing:

List<string> daysOfTheWeek = new List<string> { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" };

var day = daysOfTheWeek[3]; //"Thu"

Finally, we can add elements to the list using the Add() method:

List<string> daysOfTheWeek = new List<string> ();
daysOfTheWeek.Add("Sun");
daysOfTheWeek.Add("Mon");
daysOfTheWeek.Add("Tue");
daysOfTheWeek.Add("Wed");
daysOfTheWeek.Add("Thu");
daysOfTheWeek.Add("Fri");
daysOfTheWeek.Add("Sat");

Methods

Lists and other collections implement a set of methods that allow developers to manipulate the list and its elements. The following is a small set of these methods:

List<string> names = new List<string>();

names.Add("test name"); //Adds new elements
names.Add("second name");
names.Add("third name");

bool exists = names.Contains("test name"); //Checks if a particular value 
                                           //exists in the collection
                                           
List<string> aFewNames = names.GetRange(0, 2); //Returns a copy List<T> 
                                               //starting from position 0
                                               //and getting the next 
                                               //two elements
                                       
names.Insert(2, "second and a half name"); //Inserts an element at
                                           //the specified position
                                         
int index = names.IndexOf("test name"); //Returns the zero-based index of the
                                        //first instance of the element.
                                        
names.Remove("test name"); //Removes the first occurrance 
                           //of the specified element

names.Clear(); //Removes all elements 

Combining Lists

It is possible to combine lists of the same type into a single list. We do this using the AddRange() method:

List<string> names1 = new List<string> { "alex", "amy", "angela", "adam" };
List<string> names2 = new List<string> { "brianna", "bob", "barb" };

names1.AddRange(names2); 

Other Collection Types

Besides the basic List<T>, there are other useful collection types in C#. To be perfectly honest, I could write entire articles on each of these types to fully cover their usage, but I will leave the deeper exploration of these types to you, my dear readers.

Dictionary<TKey, TValue>

A dictionary is a set of values, each having a unique key. We can add elements to a dictionary using the Add() method.

Dictionary<string, string> imageTypes = new Dictionary<string, string>();

//imageTypes.Add("key", "value");
imageTypes.Add("bmp", "Bitmap");
imageTypes.Add("jpeg", "Joint Photographic Experts Group");
imageTypes.Add("png", "Portable Network Graphics");
imageTypes.Add("gif", "Graphics Interchange Format");

The Add() method will throw an exception if the specified key already exists in the dictionary:

Dictionary<string, string> imageTypes = new Dictionary<string, string>();

imageTypes.Add("bmp", "Bitmap");
imageTypes.Add("jpeg", "Joint Photographic Experts Group");
imageTypes.Add("png", "Portable Network Graphics");
imageTypes.Add("gif", "Graphics Interchange Format");
imageTypes.Add("jpeg", "JPEG"); //EXCEPTION
System.ArgumentException: "An item with the same key has already been added. Key: jpeg"

When retrieving values out of a dictionary, we use the key to retrieve the value:

Dictionary<string, string> imageTypes = new Dictionary<string, string>();

imageTypes.Add("bmp", "Bitmap");
imageTypes.Add("jpeg", "Joint Photographic Experts Group");
imageTypes.Add("png", "Portable Network Graphics");
imageTypes.Add("gif", "Graphics Interchange Format");

string name = imageTypes["png"]; //"Portable Network Graphics"

Queue<T>

A queue is a collection of items that is implemented in a first-in-first-out style. This means that elements of the collection will be removed from the collection in the same order they were added.

We add elements to a Queue<T> using the Enqueue() method, and remove elements using the Dequeue() method:

Queue<int> orders = new Queue<int>();
orders.Enqueue(1);
orders.Enqueue(2);
orders.Enqueue(3);
orders.Enqueue(4);

var firstValue = orders.Dequeue(); //1
var secondValue = orders.Dequeue(); //2

Stack<T>

A stack is a collection of items implemented in a last-in-first-out style. This means that the most-recently-added item will be the first one removed. Items added to a stack are said to be "pushed" and items removed are said to be "popped".

We add elements to a Stack<T> using the Push() method and remove elements using the Pop() method:

Stack<int> elements = new Stack<int>();
elements.Push(1);
elements.Push(2);
elements.Push(3);
elements.Push(4);

var firstValue = elements.Pop(); //4
var secondValue = elements.Pop(); //3

Glossary

  • Array - A collection of objects of the same type with a defined size.
  • Index - The position of an element in an array or collection.
  • Element - An individual object in an array or collection.
  • Collection - A group of elements of the same type with an adjustable size.
  • Dictionary - A collection where each element has a unique key and a value.
  • Key - An object which uniquely represents a value in a dictionary.
  • Stack - A collection implemented in a last-in-first-out (LIFO) style.
  • Queue - A collection implemented in a first-in-first-out (FIFO) style.

New Operators

  • .. - The range operator. Allows us to get a subset of an array, e.g. var myItems = myArray[2..8]. Available in C# 8.0 and later.
  • ^ - Relative position operator, when used in arrays. Allows us to get elements relative to the end of the array. myArray[^3] would get the third element from the end of the myArray array.

Summary

Arrays are collections of objects with the same type of a defined size. We access elements in an array using an index object, and can initialize arrays both with and without elements. We can also have multi-dimensional arrays, where we can define X groups of Y size, or larger.

We can get a subset of the elements in an array using the range operator .., and elements based on their position relative to the end of the array using the ^ operator.

Collections are groups of elements with the same type, but with an adjustable size; they are more flexible than arrays. Collections include the base List<T> class, as well as the more specialized Dictionary<TKey, TValue>, Queue<T>, and Stack<T> classes, plus others.

We normally use methods such as Add(), Remove(), and Contains() to interact with elements in a collection, though we can also access elements in collections using array indexes. The foreach keyword provides a simple way to iterate through each element in a collection.

A view of library shelves, all stuffed with books, and a ceiling that was painted in a Renaissance style.
Wonder how long it takes to iterate over this collection of books? Photo by Susan Yin / Unsplash

Got a question about arrays or collections, or need some help with a piece of code that uses them? Ask in the comments below!

In the next post in this series, we will talk about one of the coolest features in C#, something that works nicely with collections and a major reason why much of our code can be so concise: Language Integrated Query, or LINQ. Check it out here:

C# in Simple Terms - LINQ Basics
Let’s query some C# collections using LINQ and a bunch of new keywords!

Happy Coding!