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!
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:
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:
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 themyArray
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.
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:
Happy Coding!