Skip to content

Arrays and Lists in C#

As a developer, you have certainly stored various collections within your applications, such as user data, books, and logs. One of the natural ways of storing such data is by using arrays and lists. However, have you ever thought about their variants? Have you heard about jagged arrays or circular-linked lists? In this chapter you will see such data structures in action, together with examples and detailed descriptions. That is not all, because the chapter is related to many topics regarding arrays and lists, suitable for developers with various levels of programming skills.

At the start of the chapter, the arrays will be presented and divided into single-dimensional, multi-dimensional, and jagged arrays. You will also get to know four sorting algorithms, namely selection, insertion, bubble sort, and quicksort. For each of them, you will see an illustration-based example, the implementation code, and a step-by-step explanation.

The arrays have a lot of possibilities. However, generic lists available while developing in the C# language are even more powerful. In the remaining part of the chapter, you will see how to use a few variants of lists, such as simple, sorted, double-linked, and circular-linked. For each of them, the C# code of an example will be shown with a detailed description.

You will cover the following topics in this chapter:

  • Arrays
  • Sorting algorithms
  • Simple lists
  • Sorted lists
  • Linked lists
  • Circular-linked lists

Arrays

Let’s start with the array data structure. You can use it to store many variables of the same type, such as int, string, or a user-defined class. As mentioned in the introduction, while developing applications in the C# language, you can benefit from a few variants of arrays, as presented in the following diagram. You have access not only to single-dimensional arrays (indicated as a), but also multi-dimensional (b), and jagged (c). Examples of all of them are shown in the following diagram:

What is important is that the number of elements in an array cannot be changed after initialization. For this reason, you will not be able to easily add a new item at the end of the array or insert it in a given position within the array. If you need such features, you can use other data structures described in this chapter, such as generic lists.

You can find more information about arrays at https://docs.microsoft.com/en-us/dotnet/csharp/programming-gui

After this short description, you should be ready to learn more about particular variants of arrays and to take a look at some C# code. Thus, let’s proceed to the simplest variant of arrays, namely single-dimensional ones.

Single-dimensional arrays

A single-dimensional array stores a collection of items of the same type, which are accessible by an index. It is important to remember that indices of arrays in C# are zerobased. This means that the first element has an index equal to 0, while the last one—length of the array minus one.

The example array is shown in the preceding diagram (on the left, indicated by a). It contains five elements with the following values: 9, -11, 6, -12, and 1. The first element has an index equal to 0, while the last one has an index equal to 4.

To use a single-dimensional array, you need to declare and initialize it. The declaration is very simple, because you just need to specify a type of element and a name, as follows: type[] name;

The declaration of an array with integer values is shown in the following line: int[] numbers;

Now you know how to declare an array, but what about the initialization? To initialize the array elements to default values, you can use the new operator, as shown here: numbers = new int[5];

Of course, you can combine a declaration and initialization in the same line, as follows: int[] numbers = new int[5];

Unfortunately, all the elements currently have default values, that is, zeros in the case of integer values. Thus, you need to set the values of particular elements. You can do this using the [] operator and an index of an element, as shown in the following code snippet:

numbers[0] = 9; numbers[1] = -11; (…) numbers[4] = 1;

Moreover, you can combine a declaration and initialization of array elements to specific values using one of the following variants:

int[] numbers = new int[] { 9, -11, 6, -12, 1 };

int[] numbers = { 9, -11, 6, -12, 1 };

When you have the proper values of elements within an array, you can get values using the [] operator and by specifying the index, as shown in the following line of code: int middle = numbers[2];

Here, you get a value of the third element (the index equal to 2) from the array named numbers and store it as a value of the middle variable.

More information about single-dimensional arrays is available at https://docs.microsoft.com/en-us/dotnet/csharp/programming-gui de/arrays/single-dimensional-arrays.

Example month names

To summarize the information you have learned about single-dimensional arrays, let’s take a look at a simple example, where the array is used to store names of months in English. Such names should be obtained automatically, not by hardcoding them in the code.

The implementation is shown here: string[] months = new string[12];

for (int month = 1; month <= 12; month++)

{

    DateTime firstDay = new DateTime(DateTime.Now.Year, month, 1);

    string name = firstDay.ToString(“MMMM”,

        CultureInfo.CreateSpecificCulture(“en”));

    months[month – 1] = name; }

foreach (string month in months)

{

    Console.WriteLine($”-> {month}”);

}

At the start, a new single-dimensional array is declared and initialized with default values. It contains 12 elements to store names of months in a year. Then, the for loop is used to iterate through the numbers of all months, that is, from 1 to 12. For each of them, the DateTime instance representing the first day in a particular month is created.

The name of the month is obtained by calling the ToString method on the DateTime instance, passing the proper format of the date (MMMM), as well as specifying the culture (en in the example). Then, the name is stored in the array using the [] operator and an index of the element. It is worth noting that the index is equal to the current value of the month variable minus one. Such subtraction is necessary, because the first element in the array has an index equal to zero, not one.

The next interesting part of the code is the foreach loop, which iterates through all elements of the array. For each of them, one line is shown in the console, namely the name of the month after ->. The result is as follows:

    -> January

    -> February (…)

    -> November

    -> December

As mentioned earlier, single-dimensional arrays are not the only available variant. You will learn more about multi-dimensional arrays in the following section.

Multi-dimensional arrays

The arrays in the C# language do not need to have only one dimension. It is also possible to create two-dimensional or even three-dimensional arrays. To start with, let’s take a look at an example regarding the declaration and initialization of a two-dimensional array with 5 rows and 2 columns: int[,] numbers = new int[5, 2];

If you want to create a three-dimensional array, the following code can be used: int[, ,] numbers = new int[5, 4, 3];

Of course, you can also combine a declaration with an initialization, as shown in the

following example:

int[,] numbers = new int[,] =

{

    { 9, 5, -9 },

    { -11, 4, 0 },

    { 6, 115, 3 },

    { -12, -9, 71 },

    { 1, -6, -1 }

};

Some small explanation is necessary for the way you access particular elements from a multi-dimensional array. Let’s take a look at the following example:

int number = numbers[2][1]; numbers[1][0] = 11;

In the first line of code, the value from the third row (index equal to 2) and second column (index equal to 1) is obtained (that is, 115) and set as a value of the number variable. The other line replaces -11 with 11 in the second row and first column.

More information about multi-dimensional arrays is available at https://docs.microsoft.com/en-us/dotnet/csharp/programming-gui de/arrays/multidimensional-arrays.

Example multiplication table

The first example shows basic operations on a two-dimensional array with the purpose of presenting a multiplication table. It writes the results of the multiplication of all integer values in the range from 1 to 10, as shown in the following output:

  • 2 3   4   5   6   7   8   9  10
  • 4 6   8  10  12  14  16  18  20
  • 6 9  12  15  18  21  24  27  30
  • 8 12  16  20  24  28  32  36  40
  • 10 15  20  25  30  35  40  45  50
  • 12 18  24  30  36  42  48  54  60
  • 14 21  28  35  42  49  56  63  70
  • 16 24  32  40  48  56  64  72  80
  • 18 27  36  45  54  63  72  81  90
  • 20 30  40  50  60  70  80  90 100

 Let’s take a look at the method of declaration and initialization of the array: int[,] results = new int[10, 10];

Here, a two-dimensional array with 10 rows and 10 columns is created and its elements are initialized to default values, that is, to zeros.

When the array is ready, you should fill it with the results of the multiplication. Such a task can be performed using two for loops:

for (int i = 0; i < results.GetLength(0); i++)

{

    for (int j = 0; j < results.GetLength(1); j++)

    {

        results[i, j] = (i + 1) * (j + 1);

    }

}

In the preceding code, you can find the GetLength method, which is called on an array object. The method returns the number of elements in a particular dimension, that is, the first (when passing 0 as the parameter) and the second (1 as the parameter). In both cases, a value of 10 is returned, according to the values specified during the array initialization.

Another important part of the code is the way of setting a value of an element in a twodimensional array. To do so, you need to provide two indices, such as results[i, j].

At the end, you just need to present the results. You can do so using two for loops, as in the case of filling the array. This part of the code is shown here:

for (int i = 0; i < results.GetLength(0); i++)

{

    for (int j = 0; j < results.GetLength(1); j++)

    {

        Console.Write(“{0,4}”, results[i, j]);

    }

    Console.WriteLine();

}

The multiplication results, after conversion to string values, have different lengths, from one character (as in the case of 4 as a result of 2*2) to three (100 from 10*10). To improve the presentation, you need to write each result always on 4 chars. Therefore, if the integer value takes less space, the leading spaces should be added. As an example, the result 1 will be shown with three leading spaces (___1, where _ is a space), while 100 with only one (_100). You can achieve this goal by using the proper composite format string (namely {0,4}) while calling the Write method from the Console class.

Example game map

Another example of the application of a two-dimensional array is a program that presents a map of a game. The map is a rectangle with 11 rows and 10 columns. Each element of the array specifies a type of terrain as grass, sand, water, or wall. Each place on the map should be shown in a particular color (such as green for grass), as well as using a custom character that depicts the terrain type (such as ≈ for water), as shown in the screenshot:

At the start, let’s declare the enumeration value, named TerrainEnum, with four constants, namely GRASS, SAND, WATER, and WALL, as follows:

public enum TerrainEnum

{

    GRASS,

    SAND,

    WATER,

    WALL

}

To improve the readability of the whole project, it is recommended to declare the TerrainEnum type in a separate file, named

TerrainEnum.cs. This rule should also be applied to all user-defined types, including classes.

Then, you create two extension methods that make it possible to get a particular color and character depending on the terrain type (GetColor and GetChar, respectively). Such extension methods are declared within the TerrainEnumExtensions class, as follows:

public static class TerrainEnumExtensions

{

    public static ConsoleColor GetColor(this TerrainEnum terrain)

    {

        switch (terrain)

        {

            case TerrainEnum.GRASS: return ConsoleColor.Green;             case TerrainEnum.SAND: return ConsoleColor.Yellow;             case TerrainEnum.WATER: return ConsoleColor.Blue;

            default: return ConsoleColor.DarkGray;

        }

    }

    public static char GetChar(this TerrainEnum terrain)

    {

        switch (terrain)

        {

            case TerrainEnum.GRASS: return ‘\u201c’;             case TerrainEnum.SAND: return ‘\u25cb’;             case TerrainEnum.WATER: return ‘\u2248’;

            default: return ‘\u25cf’;

        }

    }

}

It is worth mentioning that the GetChar method returns a proper Unicode character depending on the TerrainEnum value. For example, in the case of the WATER constant, the ‘\u2248’ value is returned, which is a representation of the ≈ character.

Have you heard about the extension methods? If not, think of them as methods that are “added” to a particular existing type (both built-in or user-defined), which can be called in the same way as when they are defined directly as instance methods. The declaration of an extension method requires you to specify it within a static class as a static method with the first parameter indicating the type, to which you want to “add” this method, with the this keyword. You can find more information at https://docs.microsoft.com/en-us/dotnet/csharp/programming-gui de/classes-and-structs/extension-methods.

Let’s take a look at the body of the Main method in the Program class. Here, you configure the map, as well as present it in the console. The code is as follows:

TerrainEnum[,] map =

{

    { TerrainEnum.SAND, TerrainEnum.SAND, TerrainEnum.SAND,

      TerrainEnum.SAND, TerrainEnum.GRASS, TerrainEnum.GRASS,

      TerrainEnum.GRASS, TerrainEnum.GRASS, TerrainEnum.GRASS,

      TerrainEnum.GRASS }, (…)

    { TerrainEnum.WATER, TerrainEnum.WATER, TerrainEnum.WATER,

      TerrainEnum.WATER, TerrainEnum.WATER, TerrainEnum.WATER,

      TerrainEnum.WATER, TerrainEnum.WALL, TerrainEnum.WATER,

      TerrainEnum.WATER }

};

Console.OutputEncoding = UTF8Encoding.UTF8; for (int row = 0; row < map.GetLength(0); row++)

{

    for (int column = 0; column < map.GetLength(1); column++)

    {

        Console.ForegroundColor = map[row, column].GetColor();

        Console.Write(map[row, column].GetChar() + ” “);

    }

    Console.WriteLine();

}

Console.ForegroundColor = ConsoleColor.Gray;

Some comment can be useful regarding the way of getting a color and obtaining a character for a particular map place. Both these operations are performed using the extension methods “added” to the TerrainEnum user-defined type. For this reason, you first obtain the TerrainEnum value for a particular map place (using the [] operator and two indices) and then you call a suitable extension method, either GetChar or GetColor. To use Unicode values, you should not forget to choose the UTF-8 encoding by setting the UTF8Encoding.UTF8 value for the OutputEncoding property.

So far, you have learned about both single- and multi-dimensional arrays, but one more variant remains to be presented in this book. Let’s continue reading to get to know more

about it.

Jagged arrays

The last variant of arrays described in this book is a jagged array, which is also referred to as an array of arrays. It sounds complicated, but fortunately, it is very simple. A jagged array could be understood as a single-dimensional array, where each element is another array. Of course, such inner arrays can have different lengths or they can even be not initialized.

If you take a look at the following diagram, you will see an example of a jagged array with four elements. The first element has an array with three elements (9, 5, -9), the second element has an array with five elements (0, -3, 12, 51, -3), the third is not initialized (NULL), while the last one is an array with only one element (54):

Before proceeding to the example, it is worth mentioning the way of declaring and initializing a jagged array, because it is a bit different to the arrays already described. Let’s take a look at the following code snippet:

int[][] numbers = new int[4][]; numbers[0] = new int[] { 9, 5, -9 }; numbers[1] = new int[] { 0, -3, 12, 51, -3 };

numbers[3] = new int[] { 54 };

In the first line, you can see the declaration of a single-dimensional array with four elements. Each element is another single-dimensional array of integer values. When the first line is executed, the numbers array is initialized with default values, namely NULL. For this reason, you need to manually initialize particular elements, as shown in the following three lines of codes. It is worth noting that the third element is not initialized.

You can also write the preceding code in a different way, as shown here:

int[][] numbers =

{

    new int[] { 9, 5, -9 },     new int[] { 0, -3, 12, 51, -3 },

    NULL,

    new int[] { 54 } };

A small comment is also necessary for the method of accessing a particular element from a jagged array. You can do this in the following way:

int number = numbers[1][2]; number[1][3] = 50;

The first line of code sets the value of the number variable to 12, that is, to the value of the third element (index equal to 2) from the array, which is the second element of the jagged array. The other line changes the value of the fourth element within the array, which is the second element of the jagged array, from 51 to 50.

More information about jagged arrays is available

at https://docs.microsoft.com/en-us/dotnet/csharp/programming-gui de/arrays/jagged-arrays.

Example yearly transport plan

After the introduction of jagged arrays, let’s proceed with an example. You will see how to develop a program that creates a plan of transportation for the whole year. For each day of each month, the application draws one of the available means of transport. At the end, the program presents the generated plan, as shown in the following screenshot:

To start with, let’s declare the enumeration type with constants representing available types of transport, namely a car, a bus, a subway, a bike, or on foot, as follows:

public enum TransportEnum

{

    CAR,

    BUS,

    SUBWAY,

    BIKE,

    WALK

}

In the next step, you create two extension methods that return a character and a color for the representation of a given mean of transport in the console. The code is shown here:

public static class TransportEnumExtensions

{

    public static char GetChar(this TransportEnum transport)

    {

        switch (transport)

        {

            case TransportEnum.BIKE: return ‘B’;             case TransportEnum.BUS: return ‘U’;             case TransportEnum.CAR: return ‘C’;             case TransportEnum.SUBWAY: return ‘S’;             case TransportEnum.WALK: return ‘W’;

            default: throw new Exception(“Unknown transport”);

        }

    }

    public static ConsoleColor GetColor(         this TransportEnum transport)

    {

        switch (transport)

        {

            case TransportEnum.BIKE: return ConsoleColor.Blue;             case TransportEnum.BUS: return ConsoleColor.DarkGreen;             case TransportEnum.CAR: return ConsoleColor.Red;             case TransportEnum.SUBWAY:

                return ConsoleColor.DarkMagenta;             case TransportEnum.WALK:                 return ConsoleColor.DarkYellow;

            default: throw new Exception(“Unknown transport”);

        }

    }

}

The preceding code should not require additional clarification, because it is very similar to the one already presented in this chapter. Now let’s proceed to the code from the Main method from the Program class, which will be shown and described in parts.

In the first part, a jagged array is created and filled with proper values. It is assumed that the jagged array has 12 elements, representing months from the current year. Each element is a single-dimensional array with TransportEnum values. The length of such an inner array depends on the number of days in a given month. For instance, it is set to 31 elements for January and 30 elements for April. The code is shown here:

Random random = new Random(); int transportTypesCount =

    Enum.GetNames(typeof(TransportEnum)).Length;

TransportEnum[][] transport = new TransportEnum[12][];

for (int month = 1; month <= 12; month++)

{

    int daysCount = DateTime.DaysInMonth(

        DateTime.Now.Year, month);

    transport[month – 1] = new TransportEnum[daysCount];

    for (int day = 1; day <= daysCount; day++)

    {

        int randomType = random.Next(transportTypesCount);         transport[month – 1][day – 1] = (TransportEnum)randomType;

    }

}

Let’s analyze the preceding code. At the beginning, a new instance of the Random class is created. It will be later used for drawing a suitable mean of transport from the available ones. In the next line, you get the number of constants from the TransportEnum enumeration type, that is, the number of available transport types. Then, the jagged array is created and the for loop is used to iterate through all months within the year. In each iteration, the number of days is obtained (using the DaysInMonth static method of DateTime) and an array (as an element from the jagged array) is initialized with zeros. In the following line of code, you can see the next for loop that iterates through all days of the month. Within this loop, you draw a transport type, and set it as a value of a suitable element within an array that is an element of the jagged array.

The next part of the code is related to the process of presenting the plan in the console:

string[] monthNames = GetMonthNames();

int monthNamesPart = monthNames.Max(n => n.Length) + 2; for (int month = 1; month <= transport.Length; month++)

{

    Console.Write(

        $”{monthNames[month – 1]}:”.PadRight(monthNamesPart));     for (int day = 1; day <= transport[month – 1].Length; day++)

    {

        Console.ForegroundColor = ConsoleColor.White;

        Console.BackgroundColor =

            transport[month – 1][day – 1].GetColor();

        Console.Write(transport[month – 1][day – 1].GetChar());

        Console.BackgroundColor = ConsoleColor.Black;

        Console.ForegroundColor = ConsoleColor.Gray;

        Console.Write(” “);

    }

    Console.WriteLine();

}

At the beginning, a single-dimensional array with month names is created using the GetMonthNames method, which will be described later. Then, a value of the monthNamesPart variable is set to the maximum necessary length of text for storing the month name. To do so, the LINQ expression is used to find the maximum length of text from the collection with names of months. The obtained result is increased by 2 for reserving the place for a colon and a space.

One of the great features of the C# language is its ability to use LINQ. Such a mechanism makes it possible to get data not only from various collections, but also from Structured Query Language (SQL) databases and Extensible Markup Language (XML) documents in a consistent way. You can read more at https://docs.microsoft.com/dotnet/csharp/linq/index.

Then, the for loop is used to iterate through all elements of the jagged array, that is, through all months. In each iteration, the name of the month is presented in the console. Later, the next for loop is used to iterate through all the elements of the current element of the jagged array, that is, through all days of the month. For each of them, proper colors are set (for background and foreground), and a suitable character is presented.

At the end, let’s take a look at the implementation of the GetMonthNames method:

private static string[] GetMonthNames()

{

    string[] names = new string[12];     for (int month = 1; month <= 12; month++)

    {

        DateTime firstDay = new DateTime(             DateTime.Now.Year, month, 1);         string name = firstDay.ToString(“MMMM”,

            CultureInfo.CreateSpecificCulture(“en”));

        names[month – 1] = name;

    }

    return names; }

This code does not require additional explanation, because it is based on the code already described in the example for single-dimensional arrays.

Leave a Reply

Your email address will not be published.