C-Sharp | Java | Python | Swift | GO | WPF | Ruby | Scala | F# | JavaScript | SQL | PHP | Angular | HTML
The C# language offers 2D arrays which are useful here. A 2D array is indexed with two numbers. It uses a special syntax form.
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.
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
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).
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-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. // ... Assumes each subarray is two elements long. 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
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
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
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
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.
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
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. 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.
Memory: I have also tested the memory usage of jagged and 2D arrays. This helps us determine which one to use.
2D array benchmark result Looping with GetUpperBound: 142 ms Looping with Length/2: 47 ms
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
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.
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.
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.
Tip: This can solve problems that would otherwise require confusing 2D array resizing and copying.
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.
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.
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.