Dr. GUI .NET #6

 

June 11, 2002

Contents

Where We've Been; Where We're Going
Overview of Arrays and One-Dimensional Arrays
Arrays of Value Types vs. Arrays of Reference Types
Array Class
Multidimensional Arrays
Conversions of Arrays/Array Elements (Array Covariance)
Arrays of Arrays (or "Ragged-Row" Arrays)
Give It a Shot!
What We've Done; What's Next

Speak Out!

If you have something you want to say, tell us (and the world) what you think about this article on the Dr. GUI .NET message board.

And be sure to check out the samples running as Microsoft® ASP.NET applications, with source code, or just take a look at the source file in a new window.

Where We've Been; Where We're Going

Last time we discussed strings in the .NET Framework. This time we're going to talk about arrays in the .NET Framework.

Overview of Arrays and One-Dimensional Arrays

Arrays in the .NET Framework are much like arrays in any other programming system: They're data structures that contain an ordered set of values. All of the elements must be of the same type. Note, however, that for reference types, the elements could refer to an object of the element type or any type derived from the element type. The extreme example of this is an array of Objects. Elements in such an array can refer to any type, because all types are derived from Object.

You access the values by using the array name and an index that is an integer expression. The index goes inside square brackets in C#, and parenthesis in Microsoft® Visual Basic® .NET. For instance:

[C#]
double[] a = {0.1, 0.2, 0.3, 0.4, 0.5};
// gives "0.3": first subscript is zero, not one
StringBuilder sb = new StringBuilder(a[2].ToString());
int i = 2;
a[i] = a[i] + a[i - 1] / 4; // replaces 0.3 with 0.35
sb.AppendFormat(", now {0}\n", a[2].ToString());

[Visual Basic .NET]
Dim a() as Double = {0.1, 0.2, 0.3, 0.4, 0.5}
' gives "0.3": first subscript is zero, not one
Dim sb as New StringBuilder(a(2).ToString())
Dim i as Integer = 2
a(i) = a(i) + a(i - 1) / 4 ' replaces 0.3 with 0.35
sb.AppendFormat(", now {0}\n", a(2).ToString())

The left part of the first line in the preceding example creates an array reference called "a." The type of the array reference is double []/Double(), or a one-dimensional array of double/Double. Note that the size of the array is not part of its type (this is different from C and C++), although the rank, or number of dimensions, of the array is part of the type. Note also that in C# you have to write Double ``[] a ``= { ... }; rather than Double ``a[] ``= { ... }; as you could in C or C++. In array declarations in C# the array brackets go with the type, not with the variable. In Visual Basic .NET, the parentheses go with the variable, not the type. Confusing, huh? Dr. GUI thinks so….

An array reference can refer to an array of any size, as long as the number of dimensions (or rank) and element type are the same. (The array reference could, of course, also be null rather than referring to an actual array.)

The right part of the first line sets the array reference to point to an array that has five elements. The subscripts of those elements are zero through four, not one through five. (In "classic" Visual Basic, you could set the base to 1 with the "Option Base" statement. No more.) That means you'd write a loop to print all of the elements, like this:

[C#]
for (int i1 = 0; i1 <= 4; i1++)
   sb.AppendFormat("a[{1}] = {0}\n", a[i1], i1);

[Visual Basic .NET]
Dim i1 as Integer 
For i1 = 0 To 4
   sb.AppendFormat("a({1}) = {0}\n", a(i1), i1)
Next
sb.Replace("\n", Environment.NewLine)

The preceding loop in C#, for those of you who don't (yet) program in C, C++, or C#, sets i1 to zero, and then executes the AppendFormat method call five times, incrementing i1 after each execution. It's exactly equivalent to the For i1 = 0 To 4 in the Visual Basic .NET version. So, the values of i1 will be 0, 1, 2, 3, and 4 (because the test i1 <= 4 fails when i1 is equal to five).

Note that the good doctor took advantage of the ability to switch format specifiers around in the AppendFormat method call in order to put the parameters to follow in the order he prefers.

In the Visual Basic .NET code, we convert the "\n" substrings to CR/LF before ending (and returning the string from the StringBuilder).

Off by One: An Important Visual Basic .NET Difference!

One very important difference between Visual Basic .NET and C# is in the number of array elements allocated when you ask for a fixed number of elements.

For instance, if you say int [] a = new int[5]; in C#, you get five elements, a[0] through a[4].

In Visual Basic .NET, if you say Dim a() as Integer = New Integer(5), you get SIX elements, a[0] through a[5].

In other words, Visual Basic .NET adds one to the size of each dimension automatically and silently. Visual Basic does this for legacy reasons—again, to make porting old Visual Basic programs to Visual Basic .NET a little easier.

So if we were allocating the arrays above using an explicit size, we'd use 5 as the size in C# and 4 as the size in Visual Basic .NET—to get the exact same array with elements numbered zero through four.

This differences causes problems—it's especially nasty when the dimension is a variable, particularly if that variable is passed between routines written in different languages. The result is that your arrays can be sized differently than you expect, leading to nasty off-by-one errors and null-pointer reference exceptions.

Beware! Especially if you mix languages! And if you're a Visual Basic .NET programmer, be aware of this when you pass arrays to .NET Framework methods!

Arrays Are Bounds-Checked

If you over- or under-index the array, you'll generate an IndexOutOfRangeException. The .NET Framework requires that array indices be checked to make sure they're in bounds. In certain applications that do particularly heavy array manipulation, this can be a performance issue. For those applications, use C#'s unsafe code feature to do C/C++ style pointers and memory management. (But be careful!) There isn't an equivalent in Visual Basic .NET, so it's good that you can easily call across languages in the .NET Framework.

Arrays Are Reference Types

Arrays in the .NET Framework are reference types, so this array can contain either null/Nothing or a reference to an array of double/Double. Be careful if you decide to set an array reference to null/Nothing, because many programmers will not expect an array reference to contain null/Nothing. You may want to set it to an empty array (array that contains zero elements) instead:

[C#]
double[] a;   // a is unassigned (contains null)
a = null;   // you may not want to do this--see text above
a = new double[] { };      // an empty array is often better than null

[Visual Basic .NET]
Dim a() as Double   ' a is unassigned (contains Nothing)
a = Nothing   ' you may not want to do this--see text above
Erase a   ' same as above--simply sets a to Nothing
a = New Double() { }   ' an empty array is often better than Nothing

(All Visual Basic's Erase statement does is to set the array reference to Nothing.)

You can also assign (and reassign) arrays after you declare the variable. For instance, we could have written the first part of the preceding example as:

[C#]
double[] a;   // a is unassigned
a = new double[] {0.1, 0.2, 0.3, 0.4, 0.5};   // can reassign
// also same as   double[] a = new double[]{0.1, 0.2, 0.3, 0.4, 0.5};

[Visual Basic .NET]
Dim a() as Double   ' a is unassigned
a = New Double(){0.1, 0.2, 0.3, 0.4, 0.5} ' can reassign
' also same as Dim a as double() new Double() {0.1, 0.2, 0.3, 0.4, 0.5}
ReDim Preserve a(8)   ' grows array to 8, preserves original values

Visual Basic .NET has a ReDim statement that, in one form, does the same thing as simply reassigning the reference. However, it has a second form, using the keyword Preserve, which allows you to resize the last dimension of the array only while copying the contents of the old array into the new array automatically. (If there aren't enough elements to copy, as above, the extra elements are initialized with zero.)

As is true of all reference types, if you assign one array to another, both will refer to the same array. If you want a copy of the array, you have to clone it, as shown here:

[C#]
StringBuilder sb = new StringBuilder();
double[] b = a;   // b and a refer to same array
b[2] = 7.7;
sb.AppendFormat("a[2] is {0}, b[2] is {1}\n", a[2], b[2]); // both 7.7
double[] c = (double [])a.Clone();   // separate array
c[2] = 3.8;
sb.AppendFormat("a[2] is {0}, c[2] is {1}\n", a[2], c[2]); // NOT same

[Visual Basic .NET]
Dim sb as New StringBuilder
Dim b() as Double = a   ' b and a refer to same array
b(2) = 7.7
sb.AppendFormat("a(2) is {0}, b(2) is {1}\n", a(2), b(2)) ' both 7.7
Dim c() as Double = CType(a.Clone(), Double())   ' separate array
c(2) = 3.8
sb.AppendFormat("a(2) is {0}, c(2) is {1}\n", a(2), c(2)) ' NOT same
sb.Replace("\n", Environment.NewLine) ' change \n's

Arrays of Value Types vs. Arrays of Reference Types

For arrays of all types, the array variable itself is a reference to an array. But what's contained in the array?

The array to which the array reference refers contains some interesting information in addition to the actual data in the array. For instance, it contains the rank (number of dimensions) and the upper and lower bounds for each dimension of the array. (While the .NET Framework allows arrays with lower bounds other than zero, such arrays are not CLS-compliant, so you can't declare them directly in most languages and shouldn't use them for any but the most exceptional situations.)

But c'mon! What's the data look like?

Here's the story: If the array is an array of some value type, such as Int32 or Double or Char, or one of your own value types (declared using the struct/Struct keyword, for instance), the array will actually contain the data.

For instance, our array just shown would look something like this:

Figure 1. Array of Double, a value type

All value types would have a similar layout. (Note that a and b both refer to the same array, because that's how we set things up.)

But if we had an array of reference types, such as strings, the array would contain references to the objects (in this case, strings) rather than the strings themselves. For instance, we could set up an array of strings as follows:

[C#]
string[] sa = {"zero", "one", "two", "three", "four"};

[Visual Basic .NET]
Dim sa() as String = {"zero", "one", "two", "three", "four"}

The array would look something like this in memory:

Figure 2. Array of String, a reference type

All arrays of reference types would have a similar layout.

Note that, just as when you have a scalar (non-array) reference type variable, reference types give you an additional level of indirection. The string references are stored in the array; the strings are stored elsewhere. The compiler takes care of the dereferencing correctly, so code such as the following works fine:

[C#]
StringBuilder sb = new StringBuilder();
for (int i = 0; i <= 4; i++)
   sb.AppendFormat("sa[{1}] is {0}\n", sa[i], i);

[Visual Basic .NET]
Dim sb as New StringBuilder
Dim i as Integer
For i = 0 To 4
   sb.AppendFormat("sa({1}) is {0}\n", sa(i), i)
Next
sb.Replace("\n", Environment.NewLine)

Array Class

The actual array object is of a special array type. All array types are derived from the base class System.Array. And System.Array, of course, is derived from System.Object. That means you can do anything to any array that you can do to System.Array, (and, of course, System.Object) since all arrays are objects.

We discussed the things you could do to an Object in a recent column. What's really interesting is to look at some of the things you can do to any array by looking at the System.Array class, from which all arrays are derived.

First off, we'll note that System.Array implements several interfaces: ICloneable, IList, ICollection, and IEnumerable.

That means you can cast any array to any of these interfaces and pass any array to a method that takes an object by any of these interfaces.

Cloning Arrays

The fact that arrays implement ICloneable means that you can clone an array by calling Clone. For instance, if instead of assigning the array just shown by saying b = a, we'd cloned it by writing b = a.Clone(), we'd have made a copy of the entire array, not just the reference.

However, the Clone method does not copy the objects to which the array refers. So, if we write string[] sb = (string [])sa.Clone();or Dim sb() as String = CType(sa.Clone(), string), we will copy the array of string references (the middle column in the preceding diagram) but not the strings themselves (on the right). This is called a "shallow copy." (Note that the cast is necessary because Clone returns an Object.) This shallow copy behavior could be handy if we wanted two arrays to point to the same set of strings—perhaps we'd sort one array of string references backward, the other forward.

If we wanted to do a "deep copy," we could write a loop to create the array and copy each string:

[C#]
public static string CloneStringArray() {
   string[] sa1 = {"rei", "ichi", "ni", "san", "shi"};
   // make sure sa1 is a one-dimensional array
   if ((sa1 is System.Array) && (sa1.Rank == 1)) {
 // create array of same size
      string[] sa2 = new string[sa1.Length];   
      for (int i = sa2.GetLowerBound(0); 
            i <= sa2.GetUpperBound(0); i++) {
         // can't use String.Clone--see below
         sa2[i] = String.Copy(sa1[i]);
      }
      sa2[0] = "zero";
      sa2[4] = "yon";
   // PrintRefArrayToString defined later...
      return PrintRefArrayToString(sa1, "sa1") + "\n" +
         PrintRefArrayToString(sa2, "sa2");
   } 
   else return "Could not clone array";
}

[Visual Basic .NET]
Function CloneStringArray() As String
    Dim sa1() As String = {"rei", "ichi", "ni", "san", "shi"}
    ' make sure sa1 is a one-dimensional array
    If IsArray(sa1) AndAlso sa1.Rank = 1 Then
        ' create array of same size
        Dim sa2(sa1.Length - 1) As String ' note off-by-one
        Dim i As Integer
        For i = sa2.GetLowerBound(0) To sa2.GetUpperBound(0)
            ' can't use String.Clone--see below
            sa2(i) = String.Copy(sa1(i))
        Next
        sa2(0) = "zero"
        sa2(4) = "yon"
        ' PrintRefArrayToString defined later...
        Return PrintRefArrayToString(sa1, "sa1") & Chr(10) & _
            PrintRefArrayToString(sa2, "sa2")
    Else
        Return "Could not clone array"
    End If
End Function

You'll notice that the good doctor used three additional Array methods here: Rank returns the number of dimensions; GetLowerBound returns the lower bound of the specified dimension; and GetUpperBound returns the upper bound of the specified dimension. In Visual Basic .NET, there are also functions LBound and RBound you can use on any array. And in Visual Basic .NET, IsArray allows you to tell whether a particular object is an array or not. In C#, we used the syntax sa1 is System.Array to do the same thing. This is overkill here (and in fact, the compiler issues a warning) because we already know this is an array, but Dr. GUI wanted to demonstrate these things.

You might also notice the && operator in C# and the AndAlso operator in Visual Basic .NET. These operators function as "short-circuit" operators—if the expression on the left of the operator is false, the right-hand side isn't even evaluated. In our case that's good, since if sa1 isn't an array, we shouldn't be trying to read its Rank property! By the way, the equivalent short-circuit "or" operators are || in C# and OrElse (Dr. GUI loves that name!) in Visual Basic .NET.

IEnumerable and foreach/For Each

System.Array also implements IEnumerable, meaning the class can be enumerated in a standard way. IEnumerable has only one method: GetEnumerator. This method creates and returns a separate enumerator object that implements IEnumerator. (This object is separate so that you can have more than one enumerator on the same collection.)

The IEnumerator interface has a property called Current that returns the current object to which the enumerator refers, and two methods: MoveNext, which moves the enumerator to the next item in the collection; and Reset, which resets the enumerator to just before the beginning of the collection. (Note that you must call MoveNext on a freshly created enumerator in order to advance to the first item.)

In C# and Visual Basic .NET, there's a statement that takes advantage of all of this functionality: foreach (it's called For Each in Visual Basic .NET). For instance, to print out an entire array, we could do the following:

[C#]
StringBuilder sb = new StringBuilder();
foreach (string s in sa)
   sb.AppendFormat("{0}***", s);

[Visual Basic .NET]
Dim sb as New StringBuilder
Dim s as String
For Each s In sa
   sb.AppendFormat("{0}***", s)
Next

This would work even if sa is a multidimensional array! But note that most .NET compilers do special-case code for foreach/For Each where the array is a one-dimensional array with a zero lower bound. Rather than accessing the array through the enumerator, it generates specially optimized code (which can be inlined by the runtime) to access the array. Check out the code in ILDASM to see the difference.

Note that we could not use foreach/For Each in the deep-copy example just shown, because you're not allowed to modify the array elements while enumerating them—foreach is "read-only." Instead, we used a regular loop. But if you're not modifying the contents of the array and you don't need the indices, you should use foreach. It's simpler, and the compiler and runtime can optimize it better.

Other Interfaces

System.Array also implements ICollection and IList. ICollection is an interface (which inherits from IEnumerable, by the way) that provides Count, IsReadOnly, IsSynchronized, and SyncRoot properties. These are self-evident, with the exception of SyncRoot, which returns an object you can synchronize on to synchronize access to the collection. The interface also provides a CopyTo method for copying the collection into an array.

IList inherits from ICollection and adds the Item property that corresponds with the C# indexer, and a bunch of methods for dealing with lists: Add, Insert, Remove, RemoveAt, Contains, IndexOf (returns index of a value), and Clear. These are fairly obvious, so see the documentation for System.Array for more information. It also adds the IsFixedSize and IsReadOnly methods. The fact that arrays implement IList allows you to treat arrays and any other data structure that implements IList, such as linked lists, the same. Note that the members of the IList interface are implemented with explicit implementations, meaning that you'll need to cast the array reference to the IList type in order to call those methods. You can't call them on an array directly. Note further that Insert, Remove, and RemoveAt don't work—they throw NotSupportedException. This is because arrays are fixed size, so it's not possible to insert or remove elements.

Other Members of System.Array

The list of members of System.Array is long, and most of the functions' names describe what they do well. So, the good doctor will just list the names of the members that don't implement interfaces and explain the ones that aren't obvious.

First off, there's a slew of static methods: BinarySearch, Clear, Copy (copies portion of an array to another), CreateInstance (allows you to programmatically create any type of array, including arrays whose lower bound(s) is/are not zero), IndexOf and LastIndexOf, Reverse, and Sort.

We've already discussed all of the properties and most of the instance methods, but there are a few more: GetLength, CopyTo, Initialize, GetValue, and SetValue.

We've discussed the most common ones in some depth, so feel free to check out the rest in the documentation on Array Members.

You will generally not use Array.Initialize unless you're developing your own .NET Framework language.

Multidimensional Arrays

The .NET Framework also supports true multidimensional arrays. (Some other languages—Dr. GUI isn't naming names, but the name of the language begins with "J"—don't support multidimensional arrays at all, merely less-efficient arrays of arrays. The .NET runtime supports both. Choice is good.)

Again, the size of each dimension is not part of the type of the array, so a two-dimensional array of ints would have the type "int [ , ]", and a three-dimensional array of strings would have the type "string [ , , ]".

The declaration and initialization syntax for multidimensional arrays is very similar to that for one-dimensional arrays. The major difference is that in initializers, each dimension must be enclosed in braces:

[C#]
int [,,] x = { { {1, 2, 3, 4},   {5, 6, 7, 8},     {9, 10, 11, 12} },
 { {13, 14, 15, 16}, {17, 18, 19, 20}, {21, 22, 23, 24} }
};

[Visual Basic .NET]
Dim x(,,) as Integer = _
     { { {1, 2, 3, 4},     {5, 6, 7, 8},       {9, 10, 11, 12} }, _
 { {13, 14, 15, 16}, {17, 18, 19, 20}, {21, 22, 23, 24} } }

In the preceding example, x is a 2 x 3 x 4 array of int/Integer. Each row/column/and so on of a multidimensional array must be the same size as all the others in the same dimension.

The foreach/For Each statement will iterate over all the elements of a multidimensional array, with the rightmost dimension increasing first:

[C#]
StringBuilder sb = new StringBuilder();
foreach (int i in x)
   sb.AppendFormat("{0} ", i); // 1 2 3 4 5 6 ... 24

[Visual Basic .NET]
Dim sb as New StringBuilder()
Dim i as Integer
For Each i in x
   sb.AppendFormat("{0} ", i) ' 1 2 3 4 5 6 ... 24
Next

But the more common method for dealing with multidimensional arrays is with a set of nested loops:

[C#]
sb = new StringBuilder();
int iMax = x.GetUpperBound(0);
int jMax = x.GetUpperBound(1);
int kMax = x.GetUpperBound(2);
for (int i = 0; i <= iMax; i++)
   for (int j = 0; j <= jMax; j++)
      for (int k = 0; k <= kMax; k++)
         sb.AppendFormat("{0} ", x[i, j, k]); // 1 2 3 4 ... 24

[Visual Basic .NET]
sb = new StringBuilder()
Dim j, k As Integer
Dim iMax as Integer = x.GetUpperBound(0)
Dim jMax as Integer = x.GetUpperBound(1)
Dim kMax as Integer = x.GetUpperBound(2)
For i = 0 To iMax
   For j = 0 To jMax
      For k = 0 to kMax
         sb.AppendFormat("{0} ", x(i, j, k)) ' 1 2 3 4 ... 24
Next k
   Next j
Next i

Note that we didn't call GetLowerBound. You can almost always replace it with zero because the lower bound of most arrays—and of all CLS-compliant arrays—is zero. We also used temporary variables rather than calling GetUpperBound many, many times within nested loops. (Declaring and using constants for the upper bounds would be even better.)

Conversions of Arrays/Array Elements (Array Covariance)

For arrays of reference types, you can convert from one array type to another, provided that the two array types are of the same rank and that there is a conversion, implicit or explicit, from one type to another. For instance, you can use an array of strings anywhere you can use an array of Objects, because there's an implicit conversion from string to Object.

Because of covariance, array element assignments have to be type checked.

For example:

[C#]
public static string PrintRefArrayToString(object [] oa, string name) {
   StringBuilder sb = new StringBuilder();
   for(int i = 0; i < oa.Length; i++)
      sb.AppendFormat("{0}[{2}] is {1}\n", name, oa[i], i);
   return sb.ToString();
}
void FillArray(object [] oa, object o) {
   for (int i = 0; i < oa.Length; i++)
      // assignment *IS* type-checked!
      oa[i] = o;
}

// ...
StringBuilder sb = new StringBuilder();
string [] s = {"One", "Two", "Three"};
sb.Append(PrintRefArrayToString(s, "s"));
FillArray(s, "Hello");
sb.Append(PrintRefArrayToString(s, "s"));
FillArray(s, null);
sb.Append(PrintRefArrayToString(s, "s"));
// FillArray(s, 0); // FAILS with exception in assignment
// int [] ia = {0, 1, 2};
// FillArray(ia, 0); // FAILS--no covariance for value types

[Visual Basic .NET]
Function PrintRefArrayToString(ByVal oa() As Object, _
    Byval name As String) As String
    Dim sb As New StringBuilder()
    Dim i As Integer
    For i = 0 To oa.Length - 1
        sb.AppendFormat("{0}({2}) is {1}\n", name, oa(i), i)
    Next
    sb.Replace("\n", Environment.NewLine)
    Return sb.ToString()
End Function

Sub FillArray(oa() as Object, o as Object)
   Dim i as Integer
   For i = 0 To oa.Length – 1 ' off-by-one
      ' assignment *IS* type-checked!
      oa(i) = o
   Next
End Sub

' ...
Dim sb As New StringBuilder()
Dim s() As String = {"One", "Two", "Three"}
sb.Append(PrintRefArrayToString(s, "s"))
FillArray(s, "Hello")
sb.Append(PrintRefArrayToString(s, "s"))
FillArray(s, Nothing)
sb.Append(PrintRefArrayToString(s, "s"))
' FillArray(s, 0) ' FAILS with exception in assignment
' Dim ia() As Integer = {0, 1, 2}
' FillArray(ia, 0) ' FAILS--no covariance for value types

However, as noted, array covariance applies only to reference types. You cannot convert an array of value types (such as an array of int/Integer) to any other array type—not even to an array of Object, because you'd have to make a copy of an array of value types in order to convert it. You can, of course, convert any array to System.Array or Object.

Arrays of Arrays (or "Ragged-Row" Arrays)

The .NET Framework supports arrays of arrays. (Although Visual Basic .NET didn't for beta 1, it does now—and that's a good thing.)

Arrays of arrays can be handy for situations where, for instance, you need something that looks something like a two-dimensional array but where the rows are of different lengths. Because each row array is allocated separately, you can make each row any length you want—they needn't all be the same length.

The access can be a little bit slower than to a true multidimensional array (since you have to dereference and bounds-check more than once), and building one takes more memory allocations and time, but if the row sizes are very different and the data structure is large, you can save a significant amount of memory.

Creating and initializing arrays of arrays always involves a loop for initialization, because it's not possible to create the subarrays in the same statement that creates the main array. For instance, if you wanted to have an array of 20 arrays of int/Integer of varying length, you might write the following:

[C#]
int [][] arrOfArr = new int [5][];   // can't specify a row size here
for (int i = 0; i <= 4; i++) {
   int rowLen = i + 3;   // different row size for each row
   arrOfArr[i] = new int[rowLen];   // create sub-array
   for (int j = 0; j < rowLen; j++)
// initialize elements of sub-array
      arrOfArr[i][j] = i * 100 + j;   
}
StringBuilder sb = new StringBuilder();
foreach (int[] ia in arrOfArr) { // have to use nested foreach
   foreach (int i in ia)
      sb.AppendFormat("{0} ", i);
   sb.Append("\n");
}

[Visual Basic .NET]
' Note: Visual Basic automatically gives one more element, so we 
' allocate one less
Dim arrOfArr(4)() as Integer ' can't specify a row size
Dim i as Integer
For i = 0 to 4
   Dim rowLen as Integer = i + 3   ' different row sizes for each row
   arrOfArr(i) = New Integer(rowLen - 1) {} ' create sub-array
   Dim j as Integer
   For j = 0 To RowLen - 1
      ' initialize elements of sub-array
      arrOfArr(i)(j) = i * 100 + j
   Next j
Next i
Dim sb as New StringBuilder
Dim ia() as Integer
For Each ia in arrOfArr ' have to use nested For Each
   ' reuse i
   For Each i in ia
      sb.AppendFormat("{0} ", i)
   Next
   sb.Append(Environment.NewLine)
Next

Note that you can't do one foreach/For Each over the array of arrays, but you can do nested foreach/For Eaches.

Give It a Shot!

Who's on Your .NET Framework Learning Team?

The good doctor hopes that you're not only playing with .NET, but that you're also working with some other folks. It's more fun that way, and you're guaranteed to learn more.

Some Things to Try...

Play with arrays and strings some—and with arrays of strings. Be sure to try at least one- and two-dimensional arrays. Perhaps do a game that relies on arrays, such as Conway's Game of Life (which we'll be doing next time).

The board is a two-dimensional matrix in which an initial pattern is seeded. Each spot in the matrix lives, dies, or births a new life according to the following rules. Here's a description:

The Game of Life was invented by John Conway (as you might have gathered). The game is played on a field of cells, each of which has eight neighbors (adjacent cells). A cell is either occupied (by an organism) or not. The rules for deriving a generation from the previous one are these:

  • Death: If an occupied cell has 0, 1, 4, 5, 6, 7, or 8 occupied neighbors, the organism dies (0, 1 neighbors: of loneliness; 4 thru 8: of overcrowding).
  • Survival: If an occupied cell has two or three neighbors, the organism survives to the next generation.
  • Birth: If an unoccupied cell has three occupied neighbors, it becomes occupied.

Try using data binding in an ASP.NET application or a Microsoft® Windows® Forms application—see how it works….

What We've Done; What's Next

This time, we talked about arrays. Next time, we'll demonstrate arrays using John Conway's Game of Life as an ASP.NET application.