.NET Array Dictionary List String 2D Async DataTable Dates DateTime Enum File For Foreach Format IEnumerable If IndexOf Lambda LINQ Parse Path Process Property Regex Replace Sort Split Static StringBuilder Substring Switch Tuple

C#
2D array

2D arrays. Data is sometimes two-dimensional. The C# language offers 2D arrays which are useful here. A 2D array is indexed with two numbers. It uses a special syntax form.


Copyright

A plane. We can store any element type,
reference
or value. 2D arrays have some performance issues. And jagged arrays (arrays of arrays) are often a clearer, better choice.


Cover-logo

First example. First, we use a two-dimensional string array. We initialize a 2D array. Then we use the indexing syntax to access all elements and display their values.

Tip: To access a two-dimensional array element, please use the syntax array[0, 0]. Each dimension is indexed starting at zero.

Result: The print creates a 2x2 string array and then prints out all four elements (with no loops).

Based on:

.NET 4.5

C# program that creates 2D array

using System;

class Program
{
    static void Main()
    {
	// ... Create 2D array of strings.
	string[,] array = new string[,]
	{
	    {"cat", "dog"},
	    {"bird", "fish"},
	};
	// ... Print out values.
	Console.WriteLine(array[0, 0]);
	Console.WriteLine(array[0, 1]);
	Console.WriteLine(array[1, 0]);
	Console.WriteLine(array[1, 1]);
    }
}

Output

cat
dog
bird
fish
2D array, locals in Visual Studio debugger

Debugger. Above we declare a 2D array.
The syntax is somewhat confusing,
with curly brackets
and commas. We just need to memorize this. Here is what we see in the Visual Studio debugger.

Screenshot: The compiler sees the string[,] array as a string[2, 2] array. It inferred the size (good job, compiler).


Get

GetUpperBound. This method receives the highest index of the specified rank (passed as an argument). It returns an int. This is probably not best for a simple 2D array.

C# program that uses GetUpperBound

using System;

class Program
{
    static void Main()
    {
	string[,] codes = new string[,]
	{
	    {"AA", "BB"},
	    {"CC", "DD"}
	};

	// Get the upper bound.
	// ... Use for-loop over rows.
	for (int i = 0; i <= codes.GetUpperBound(0); i++)
	{
	    string s1 = codes[i, 0];
	    string s2 = codes[i, 1];
	    Console.WriteLine("{0}, {1}", s1, s2);
	}
    }
}

Output

AA, BB
CC, DD
Length-property

Length-based loop. The fastest method for a 2D array is to do some arithmetic. In this example, there are five rows. GetUpperBound(0) will return 4.

And: If we take Length, which is 10, and divide by 2, we get 5. We can iterate until we reach 5.

C# program that uses length-based loop

using System;

class Program
{
    static void Main()
    {
	string[,] words = new string[,]
	{
	    {"ONE", "TWO"},
	    {"THREE", "FOUR"},
	    {"FIVE", "SIX"}
	};

	// Loop based on length.
	// ... assumeseachsubarrayistwoelementslong.
	for (int i = 0; i < words.Length / 2; i++)
	{
	    string s1 = words[i, 0];
	    string s2 = words[i, 1];
	    Console.WriteLine("{0}, {1}", s1, s2);
	}
    }
}

Output

ONE, TWO
THREE, FOUR
FIVE, SIX
Size

GetUpperBound, int example. We cache array bounds in local variables for better performance and clarity. Here we get the two dimensions of the array and iterate through them.

C# program that uses int array, GetUpperBound twice

using System;

class Program
{
    static void Main()
    {
	int[,] codes = new int[,]
	{
	    {200, 400},
	    {2000, 4000},
	    {20000, 40000}
	};

	// Get all bounds before looping.
	int bound0 = codes.GetUpperBound(0);
	int bound1 = codes.GetUpperBound(1);
	// ... Loop over bounds.
	for (int i = 0; i <= bound0; i++)
	{
	    for (int x = 0; x <= bound1; x++)
	    {
		// Display the element at these indexes.
		Console.WriteLine(codes[i, x]);
	    }
	    Console.WriteLine();
	}
    }
}

Output

200
400

2000
4000

20000
40000
Initializer

No initializers. We can create an empty 2D array by specifying its dimensions. All elements have the default value (for ints this is 0).

Also: We can use a 2D array reference like int[,] to refer to any array size. The element type must match.

C# program that creates arrays, no initializers

using System;

class Program
{
    static void Main()
    {
	// A two-dimensional array reference.
	int[,] array = new int[2, 2];
	array[0, 0] = 1;
	Console.WriteLine(array[0, 0]);

	// The same reference can hold a different size of array.
	array = new int[3, 3];
	array[2, 2] = 1;
	Console.WriteLine(array[2, 2]);
    }
}

Output

1
1
Console

Arguments. A method may receive a 2D array by specifying the type of the elements. The dimensions are not used in the argument list—any 2D array of the correct element may be passed.

C# program that uses 2D array as argument

using System;

class Program
{
    static void PrintFirstElement(bool[,] values)
    {
	// Display value of first element in first row.
	Console.WriteLine(values[0, 0]);
    }

    static void Main()
    {
	// Any array size of the right element type can be used.
	bool[,] values = new bool[100, 100];
	values[0, 0] = true;
	PrintFirstElement(values);
    }
}

Output

True
Loop

Loops. 2D array loops are complicated. It is easy to cause errors related to invalid indexes. Here our 2D array is a four-element box, composed of two pairs.

Next: To begin our for-loop, we acquire the upper bound of the zero dimension, and the upper bound of the first dimension of the array.

For

Caution: The loop will not continue to work correctly if the array reference itself is modified or the array data is resized.

C# that loops over 2D string array

using System;

class Program
{
    static void Main()
    {
	// Instantiate a new 2D string array.
	string[,] array = new string[2, 2];
	array[0, 0] = "top left";
	array[0, 1] = "top right";
	array[1, 0] = "bottom left";
	array[1, 1] = "bottom right";

	// Get upper bounds for the array
	int bound0 = array.GetUpperBound(0);
	int bound1 = array.GetUpperBound(1);

	// Use for-loops to iterate over the array elements
	for (int variable1 = 0; variable1 <= bound0; variable1++)
	{
	    for (int variable2 = 0; variable2 <= bound1; variable2++)
	    {
		string value = array[variable1, variable2];
		Console.WriteLine(value);
	    }
	    Console.WriteLine();
	}
	Console.ReadLine();
    }
}

Output

top left
top right

bottom left
bottom right
Programming tip

Nested loops. These are not always necessary. For example, if you are using a 2D array with only two elements in each row, you can index into positions 0 and 1 from a single for-loop.


Performance fast-forward

Performance. GetUpperBound is slow. You may not want to call it often. I took a benchmark comparing one million repetitions. It shows the performance decrease with GetUpperBound.

Thus: Using the Length property for a loop boundary is faster than using GetUpperBound.

Benchmark

Memory: I have also tested the memory usage of jagged and 2D arrays. This helps us determine which one to use.

Jagged vs. 2D Array
2D array benchmark result

Looping with GetUpperBound: 142 ms
Looping with Length/2:       47 ms
Property

Rank. Every array has a rank. This is the number of dimensions in the array. A one-dimensional array has a rank of 1. We access the Rank property from the Array base class.

Here: We design a method (Handle) that receives an array reference. It then tests the Rank of the parameter array.

And: It handles both 1D and 2D arrays in the same method. It uses GetValue to access the array elements.

C# that uses Rank

using System;

class Program
{
    static void Main()
    {
	// ... A one-dimensional array.
	int[] one = new int[2];
	one[0] = 1;
	one[1] = 2;
	Handle(one);

	// ... A two-dimensional array.
	int[,] two = new int[2, 2];
	two[0, 0] = 0;
	two[1, 0] = 1;
	two[0, 1] = 2;
	two[1, 1] = 3;
	Handle(two);
    }

    static void Handle(Array array)
    {
	Console.WriteLine("Rank: " + array.Rank);
	switch (array.Rank)
	{
	    case 1:
		for (int i = 0; i < array.Length; i++)
		{
		    Console.WriteLine(array.GetValue(i));
		}
		break;
	    case 2:
		for (int i = 0; i < array.GetLength(0); i++)
		{
		    for (int x = 0; x < array.GetLength(1); x++)
		    {
			Console.Write(array.GetValue(i, x));
		    }
		    Console.WriteLine();
		}
		break;
	}
    }
}

Output

Rank: 1
1
2
Rank: 2
02
13
Method

Arguments. We can use 2D arrays as arguments. The 2D array will be passed as a reference, which means changes to it will also affect the original version.

Thus: The reference itself will be copied, but not the data to which it points.


Multidimensional array: cube

Dimensions. In C# we can also specify arrays with more than two dimensions. We can use another comma in the indexing syntax. It will work as expected.

Multidimensional Array
List

Lists. You can use the generic type List to simulate a jagged or 2D List that is dynamically resizable. The syntax for this nested type is somewhat more confusing.

Nested List

Tip: This can solve problems that would otherwise require confusing 2D array resizing and copying.


Array, simple

Jagged array. An array element can have any type. This includes other arrays. With an array of arrays, we construct a jagged array—this gives us great flexibility.

Jagged: Array of Arrays
Books

Research. In the CLI standard, I found material about the types of arrays. The Framework determines the type of an array by its rank (dimension count) and its element type.

So: Both parts are considered in determining type. Arrays are not all the same type.

The rank of an array is the number of dimensions. The type of an array (other than a vector) shall be determined by the type of its elements and the number of dimensions.

The CLI Annotated Standard
C# programming language

2D arrays have many uses. And they can be used in many ways: we showed several indexing approaches. It is easiest to use Length, but GetUpperBound is sometimes needed.


A review. We created 2D arrays and looped over them. We mutated them. We benchmarked them. Before you use 2D arrays, research jagged arrays. These can improve the clarity and speed of code.