Professor Mustard Programming
 

Programming 101 - Unit 02 - Flow Control

02g) ArrayLists and Foreach
 
The last thing we'll learn before summarizing this section is ArrayLists and foreach loops (okay, two more things). Arrays are measured out when created, and they keep the same number of elements for their entire lifespan (unless they're re-created with the "new" keyword). ArrayLists, on the other hand, are dynamic. You can add more members or delete existing members anytime you want.

This is also the first time you'll have to think about the "using" statement that's been at the top of every code sample. "using" statements allow you to use various libraries of code that exist in .NET. The "System" library allows you to use fundamental stuff like strings, ints and so on. In order to use ArrayLists, we need to add another "using" statement:

using System.Collections;


This gives us access to all the stuff in the Collections namespace, which includes ArrayLists (yes, the "namespace" keyword you see in the code samples is linked to the "using" statement. Forget about it for now). To declare a new ArrayList, we use the following code:

ArrayList list = new ArrayList();


In many ways, it resembles the normal arrays we learned about earlier. You will immediately notice, however, that we have not specified either the number of elements, or the datatype. We don't declare the number of elements, because that number is dynamic. ArrayLists begin without any elements, but we can add new elements or delete existing ones whenever we want. As for the datatype, we don't specify it because that's actually dynamic, too. You can store integers, floats, strings, Booleans and pretty much anything else you want. Let's assume for the moment, though, that all our elements are still of the same type, so that we can start off with a fairly simple example...

string input = "";
while(input != "exit")
{
  Console.Write("Enter a value and hit ENTER (\"exit\" to quit): ");
  input = Console.ReadLine();

  if( input != "exit" )
    list.Add( input );
}



The code that actually adds the new element to the ArrayList is list.Add(). The rest of the code snippet shows how to continuously gather and store input from a user, until the user "asks" the program to stop (in this case, by typing "exit").

Accessing elements in an ArrayList works exactly the same way it did in a normal array. The following code will print out every element in the array:

for(int i = 0; i < list.Count; ++i)
  Console.WriteLine( list[i].ToString() );



The only catch is that we now have a .Count property, instead of a .Length property. But they work exactly the same way. Deleting elements is also fairly straightforward. Every ArrayList comes with its own RemoveAt() method, which works like so:

list.RemoveAt(1); // Delete element 1


Remember that deleting an element from an ArrayList doesn't leave a blank member; it actually shortens the ArrayList. If you have a ten-element array, and you delete element 5, you're now going to have a nine-element array. All the elements that occurred after element 5 are simply "pushed back" against element 4, so that no gap is left. It's kind of like a vending machine, or something. In addition to deleting by index, you can also tell the ArrayList to remove a specific value from itself:

list.Remove("penguin");


This command tells the ArrayList to run through all its members until it finds a member that stores the string "penguin", and then delete it. The ArrayList doesn't care which specific index the element occurs at; it's simply going to delete the first "penguin" it comes across, and only the first penguin it comes across. If there's another "penguin" right after the first one, you'll just have to call Remove() again in order to delete it. You can also blow away all of the ArrayList's members at any time with this:

list.Clear();


And as previously mentioned, as crazy as it might sound (or maybe not), you can store any datatype you want in an ArrayList...

list.Add( 5 ); // Add integer
list.Add( 4.5 ); // Add double
list.Add( "penguin" ); // Add string
list.Add( false ); // Add Boolean

for(int i = 0; i < list.Count; ++i)
  Console.WriteLine( list[i].ToString() );



The one downside to that ability is that you'll have to "cast" everything that comes out of that list as the datatype you actually want. For example, to take that first integer value we added out of the ArrayList, we would have to do this:

int number = (int)list[0];


The underlying reason for this "mandatory casting" business is that C# considers every element in the ArrayList to be of datatype "object". And since integers, strings, floats and everything else in the C# universe are all considered to be "objects", when you first pull the object out of the ArrayList, there's no way to prove what datatype it actually is. It could be any datatype in the known C# universe, and that just scares the daylights out of your program.

To fix this, you simply "reassure" C# that the element you've just accessed is an integer (or whatever), by casting it. And of course, the usual casting rules apply: if you try to cast element 2 (which happens to be "penguin") into an integer, the program will screw up and crash (barring an intelligently placed "try/catch", naturally).

A happy exception to this casting thing is "ToString()". Since "objects" have a ToString() method, this means that every datatype in the C# universe has a ToString() method, as well. So you can cheerfully call ToString() on any element in an ArrayList you desire, without casting it first, because C# won't care what its actual datatype is. All datatypes have that method, so if you want to call it without casting first, that's good enough for C#. This concept becomes enormously important later on, so keep it in mind!

This is also as good a time as any to mention the "foreach" loop. Just as "switch" is a specialized "if" that tests for specific values, "foreach" is a specialized "for" that iterates through all of the members in a collection (array or ArrayList). The following "for" and "foreach" loop do exactly the same thing:

for(int i = 0; i < list.Count; ++i)
  Console.WriteLine( list[i].ToString() );

foreach(string item in list)
  Console.WriteLine( item );



What's the benefit in "foreach"? No indexer (that annoying "i" variable) is needed, and rather than accessing the collection's elements through those [ ] brackets, you just use the variable you've declared at the beginning of the statement. Each time you run through the loop, "item" (or whatever you called it) will represent the next element in your collection.

There are two things to watch out for with this kind of loop. The first is that, when using foreach, you can't use assign to an element in your collection with "=". Therefore, the following code is illegal:

foreach(string item in list)
  item = "hi!";



The other thing you must watch out for is multiple datatypes. Consider what "foreach(string item in list)" actually says. It's not saying, "execute the following code on all string variables in the collection, and ignore elements of other datatypes". What it's actually saying is, "execute the following code on all the variables in the collection, casting it to the string datatype for the duration of the loop". Yep, it's that darn casting thing again. If you were running a "foreach(int number in list)" loop, and one element was storing "penguin", you'd get a nasty exception from trying to convert "Penguin" to an integer. Your program would be at the mercy of your try/catch.

What's the cure? Well, one obvious way to avoid this problem is to store only one datatype in an ArrayList. This isn't such a big deal; you'll probably find that you rarely, if ever, actually need to store multiple datatypes in the same collection (I can't remember the last time I needed to; as you'll find out, classes tend to eliminate any need for it). Still, if you're really set on storing multiple datatypes, there's a way out. The first step is to change the foreach line to "foreach(object obj in list)". Remember, every element in an ArrayList is considered to be an object, so there's no way that this can cause casting trouble. In fact, the foreach won't even have to cast anything, because it already thinks that all of its elements are objects!

Of course, that still leaves the question of how to figure out which datatype each element is. And C# has actually made that very easy for us. Consider this code snippet:

foreach( object obj in list )
{
  if( obj is Int32 )
    Console.WriteLine( " (Int32) " + obj.ToString() );
  else if( obj is double )
    Console.WriteLine( "(Double) " + obj.ToString() );
  else if( obj is string )
    Console.WriteLine( "(String) " + obj.ToString() );
  else if( obj is bool )
    Console.WriteLine( " (Bool) " + obj.ToString() );
}



We can use the "is" keyword to test if an element is a certain datatype. Although the above logic is quite simple (all we really do is print the datatype of each element), you could write totally different sets of code for any datatype. And once we know that the element is, in fact, an integer, we also know that it's safe to cast it into an integer...

if( obj is Int32 )
{
  int number = (int)obj;
  // do something with "number"
}



...and there you have it. A couple things to note, and then we're done. First off, "foreach" is much faster at iterating through collections than "for", and you can use "foreach" on normal arrays, too (not just ArrayLists)... so use "foreach" instead of "for", whenever possible. And second, despite all I've just said, you generally want to avoid making logical decisions based on an object's type... when we get into Object Oriented Programming, or OOP, you'll see that a well-designed program shouldn't even have to. For the record, .NET 2.0 actually introduces an awesome new collection called a "List", which allows you to create a ArrayList that deals in only one datatype, thus avoiding the casting issue altogether. But for now, the heck with it. Let's review the code sample, and get on to finishing this unit...



using System;
using System.Collections;

namespace G_ArrayLists
{
  class Class1
  {
    [STAThread]
    static void Main(string[] args)
    {
      // Create the arraylist
      ArrayList list = new ArrayList();

      // Add as many items as we want
      string input = "";
      while(input != "exit")
      {
        Console.Write("Enter a value and hit ENTER (\"exit\" to quit): ");
        input = Console.ReadLine();

        if( input != "exit" )
          list.Add( input );
      }

      // Demonstrating the foreach loop with same datatypes
      Console.WriteLine(Environment.NewLine + "Contents of list:");
      foreach( string item in list )
      {
        Console.WriteLine( item );
      }

      // Delete by index
      Console.Write(Environment.NewLine + "Enter the index to delete an element at: ");
      int delIndex = Convert.ToInt32(Console.ReadLine());

      if( delIndex > -1 && delIndex < list.Count -1 )
      {
        Console.WriteLine("Deleted " + list[delIndex]);
        list.RemoveAt(delIndex);
      }
      else
        Console.WriteLine("Out of bounds.");

      // Delete by value
      Console.Write(Environment.NewLine + "Enter a value to delete from the list: ");
      string delValue = Console.ReadLine();
      list.Remove(delValue);

      // Use a foreach loop to print the list again
      Console.WriteLine(Environment.NewLine + "Contents of list (after deletes):");
      foreach( string item in list )
        Console.WriteLine( item );
      
      // Clear out all items
      list.Clear();

      // Store multiple datatypes and print them out
      Console.WriteLine(Environment.NewLine + "Adding variables with different datatypes to the list: ");
      list.Add( 5 ); // Add integer
      list.Add( 4.5 ); // Add double
      list.Add( "penguin" ); // Add string
      list.Add( false ); // Add Boolean

      for(int i = 0; i < list.Count; ++i)
        Console.WriteLine( list[i].ToString() );

      // Demonstrating the foreach loop with multiple datatypes
      Console.WriteLine(Environment.NewLine + "Datatype of each element:");
      foreach( object obj in list )
      {
        if( obj is Int32 )
          Console.WriteLine( " (Int32) " + obj.ToString() );
        else if( obj is double )
          Console.WriteLine( "(Double) " + obj.ToString() );
        else if( obj is string )
          Console.WriteLine( "(String) " + obj.ToString() );
        else if( obj is bool )
          Console.WriteLine( " (Bool) " + obj.ToString() );
      }

      // Store array element in a variable (requires casting)
      int number = (int)list[0];

      // Prompt for exit
      Console.Write(Environment.NewLine + "Program complete! Hit enter to exit...");
      Console.ReadLine();
    }
  }
}