Each of the data types described in previous lessons holds a single piece of data. A variable might hold an integer, string, or point in time.
Sometimes it's convenient to work with a group of related values all at once. For example, suppose you're the CEO of a huge company that just posted huge gains. In that case, you might want to give each hourly employee a certificate of appreciation and give each executive a 15 percent bonus.
In cases like this, it would be handy to be able to store all of the hourly employees' data in one variable so you could easily work with it. Similarly you might like to store the executives' data in a second variable so it's easy to manage.
In this lesson, you learn how to make variables that can hold more than one piece of data. You learn how to make arrays and different kinds of collections such as a List
, Dictionary
, Stack
, and Queue
.
This lesson explains how to build these objects and add and remove items from them. Lesson 19 explains how to get the full benefit of them by looping through them to perform some action on each of the items they contain.
An array is a group of values that all have the same data type and that all share the same name. To pick a particular item in the array, the program uses an index, which is an integer greater than or equal to 0.
An array is similar to the mailboxes in an apartment building. The building has a single bank of mailboxes that all have the same street address (the array's name). You use the apartment numbers to pick a particular cubbyhole in the bank of mailboxes.
Figure 16.1 shows an array graphically. This array is named values
. It contains eight entries with indexes 0 through 7.
The following code shows how you can declare an array of integers. The square brackets indicate an array so the first part of the statement int[]
means the variable's data type is an array of integers:
int[] values;
After you declare an array variable, you can assign it to a new uninitialized array. The following code initializes the variable values
to a new integer array that can hold eight elements:
values = new int[8];
Remember that an array's lower bound is always 0 in C# so this array has indexes 0 through 7.
As is the case with other variables, you can declare and initialize an array in a single step. The following code declares and creates the values
array in a single statement:
int[] values = new int[8];
After you have created an array, you can access its members by using the array's name followed by an index inside square brackets. For example, the following code initializes the values
array by setting the Nth entry equal to N2:
values[0] = 0 * 0;
values[1] = 1 * 1;
values[2] = 2 * 2;
values[3] = 3 * 3;
values[4] = 4 * 4;
values[5] = 5 * 5;
values[6] = 6 * 6;
values[7] = 7 * 7;
After you have placed values in an array, you can read the values using the same square bracket syntax. The following code displays a message box that uses one of the array's values:
MessageBox.Show("7 * 7 is " + values[7].ToString());
To make initializing arrays easier, C# provides an abbreviated syntax that lets you declare an array and set its values all in one statement. Simply set the variable equal to the values you want separated by commas and surrounded by braces as shown in the following code:
int[] values = { 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 };
When you use this syntax, C# uses the number of values you supply to define the array's size. In the preceding code, C# would give the values
array 10 entries because that's how many values the code supplies.
Here's a slightly more interesting example that uses an array. The Fibonacci sequence is defined by the following three rules:
Fibonacci[0] = 0
Fibonacci[1] = 1
Fibonacci[n] = Fibonacci[n - 1] + Fibonacci[n - 2]
The Fibonacci program shown in Figure 16.2 (and available as part of this lesson's code download) uses an array to display Fibonacci numbers. Use the NumericUpDown
control to select a number and click Calculate to see the corresponding Fibonacci number.
When the user clicks Calculate, the program executes the following code:
// Calculate and display the desired Fibonacci number.
private void calculateButton_Click(object sender, EventArgs e)
{
int[] values = new int[21];
values[0] = 0;
values[1] = 1;
values[2] = values[0] + values[1];
values[3] = values[1] + values[2];
values[4] = values[2] + values[3];
...
values[20] = values[18] + values[19];
int index = (int)numberNumericUpDown.Value;
resultsTextBox.Text = values[index].ToString();
}
The code starts by initializing the values
array to hold the first 21 Fibonacci numbers.
After initializing the array, the program gets the value selected by the NumericUpDown
control and converts it from a decimal
to an int
. It then uses that value as an index into the values
array and displays the result in resultTextBox
.
The arrays described in the previous section hold a single row of items, but C# also lets you define multi-dimensional arrays. You can think of these as higher-dimensional sequences of apartment mailboxes.
Figure 16.3 shows a graphic representation of a two-dimensional array with four rows and eight columns.
The following code shows how you could declare, allocate, and initialize this array to hold a multiplication table with values up to 4 times 7:
int[,] values = new int[5, 7];
values[0, 0] = 0 * 0;
values[0, 1] = 0 * 1;
values[0, 2] = 0 * 2;
...
values[1, 1] = 1 * 1;
values[1, 2] = 1 * 2;
...
values[4, 7] = 4 * 7;
The following code shows the C# syntax for quickly defining and initializing a two-dimensional array:
int[,] cell =
{
{0, 1, 2},
{3, 4, 5},
{6, 7, 8},
};
This syntax basically assigns the array variable equal to an array containing one-dimensional arrays of values.
You can use similar syntax to make and initialize higher-dimensional arrays, although they're harder to visualize graphically. For example, the following code makes a four-dimensional array of strings:
string[, , ,] employeeData = new string[10, 20, 30, 40];
All arrays have a Length
property that your code can use to determine the number of items in the array. Arrays all have lower bound 0, so for one-dimensional arrays, Length – 1
gives an array's upper bound.
Arrays also have GetLowerBound
and GetUpperBound
methods that return the lower and upper bounds for a particular dimension in an array.
For example, the following code creates a 5-by-10 two-dimensional array. It then displays the lower and upper bounds for the first dimension. (Like an array's indexes, the dimension numbers start at 0.)
int[,] x = new int[5, 10];
MessageBox.Show("The first dimension runs from " +
x.GetLowerBound(0) + " to " + x.GetUpperBound(0));
The Array
class also provides several useful static methods that you can use to manipulate arrays. For example, the following code sorts the array named salaries
:
Array.Sort(salaries);
The Sort
method has many overloaded versions that perform different kinds of sorting. For example, instead of passing it a single array you can pass it an array of keys and an array of items. In that case the method sorts the keys, moving the items so they remain matched up with their corresponding keys.
The Table 16.1 summarizes the most useful methods provided by the Array
class.
Method | Purpose |
BinarySearch |
Uses binary search to find an item in a sorted array. |
Clear |
Resets a range of items in the array to the default value for the array's data type (0, false , or null ). |
Copy |
Copies a range of items from one array to another. |
IndexOf |
Returns the index of the first occurrence of a particular item in the array. |
LastIndexOf |
Returns the index of the last occurrence of a particular item in the array. |
Resize |
Resizes the array, preserving any items that fit in the new size. |
Reverse |
Reverses the order of the items in the array. |
Sort |
Sorts the array's items. |
An array holds a group of items and lets you refer to them by index. The .NET Framework used by C# also provides an assortment of collection classes that you can use to store and manipulate items in other ways. For example, a Dictionary
stores items with keys and lets you very quickly locate an item from its key.
For example, you could use a Dictionary
to make an employee phone book. It could store phone numbers using names as the keys. Then given someone's name, you could use the dictionary to very quickly look up that person's phone number.
The following sections describe some particular kinds of classes that come pre-built by the .NET Framework. These are generic classes, so before you learn about them you should know a little about what a generic class is.
A generic class is one that is not tied to a particular data type. For example, suppose you build a StringList
class that can store a list of strings. Now suppose you decide you wanted an IntegerList
class to store lists of integers. The two classes would be practically identical; they would just work with different data types.
I've mentioned several times that duplicated code is a bad thing. Having two nearly identical classes means debugging and maintaining two different sets of code that are practically the same.
One solution to this situation is to make a more general AnythingList
class that uses the general object
data type to store items. An object
can hold any kind of data, so this class could hold lists of integers, strings, or Customer
objects. Unfortunately that has two big problems.
First, you would need to do a lot of work converting the items with the general object
data type stored in the list into the int
, string
, or Customer
type of the items that you put in there. This is annoying because it gives you more work to do and makes your code more complicated and harder to read.
A bigger problem is that a list that can hold anything can hold anything. If you make a list to hold customer data, it could still hold int
s, string
s, and PurchaseOrder
objects. Your code would need to do a lot of work to prevent you from accidentally adding the wrong kind of item to the list.
A much better approach is to use generic classes. These classes take data type parameters in their declarations so they know what kind of data they will manipulate. That lets them automatically store and retrieve items using the correct data type. It also lets them perform type checking so you can't accidentally add a Bicycle
object to a list of Employee
s.
Using this kind of class, you can build a list of integers, strings, or what have you.
List
is one of the generic collection classes defined by the .NET Framework. The following code declares and initializes a List
:
List<string> names = new List<string>();
The <string>
part of the declaration indicates that the class will work with strings. You can put strings into the list and take strings out of it. You cannot add an integer to the list, just as you can't set a string variable equal to an integer. Visual Studio knows that the list works with strings and won't let you use anything else.
Note that IntelliSense knows about generic classes and provides help. If you begin a declaration with List
, IntelliSense displays List<>
to let you know that it is a generic class.
Now if you type the opening pointy bracket, IntelliSense displays a list of the class's type parameters and even describes them as you type. (The List
class has only one type parameter but some, such as Dictionary
, have more.) After you finish the declaration, the class knows what data types it will manipulate, and it can behave as if it were designed with that data type in mind.
Now, with some understanding of generic classes, you're ready to look at some generic collection classes.
A List
is a simple ordered list of items. You can declare and initialize a List
as in the following code:
List<string> names = new List<string>();
The List
class provides several methods for manipulating the items it contains. The three most important are Add
, Remove
, and RemoveAt
:
Add
method adds a new item to the end of the list, automatically resizing the List
if necessary. This is easier than adding an item to an array, which requires you to resize the array first.Remove
method removes a particular item from the list. Note that you pass the target item to Remove
, not the index of the item that you want to remove. If you know that the string Zaphod
is in the list names
, the following code removes the first instance of that name from the list:
names.Remove("Zaphod");
RemoveAt
method removes an item from a particular position in the list. It then compacts the list to remove the hole where the item was. This is much easier than removing an item from an array, which requires you to shuffle items from one part of the array to another and then resize the array to reduce its size.In addition to these methods, you can use square brackets to get and set a List
's entries much as you can with an array. For example, the following code sets and then displays the value of the first entry in a list:
names[0] = "Mickey";
MessageBox.Show("The first name is " + names[0]);
Note that this works only if the index you use exists in the list. If the list holds 10 names and you try to set the 14th, the program crashes.
A SortedList
stores a list of key/value pairs, keeping the list sorted by the keys. The types of the keys and values are generic parameters, so, for example, you could make a list that uses numbers (such as employee IDs) for keys and strings (such as names) for values.
Note that the list will not allow you to add two items with the same key. Multiple items can have the same value, but if you try to add two with the same key, the program crashes.
Table 16.2 summarizes useful methods provided by the SortedList
class.
Method | Purpose |
Add |
Adds a key and value to the list. |
Clear |
Empties the list. |
Contains |
Returns true if the list contains a given value. |
ContainsKey |
Returns true if the list contains a given key. |
ContainsValue |
Returns true if the list contains a given value. |
GetKeyList |
Returns a list holding the keys. |
GetValueList |
Returns a list holding the values. |
Remove |
Removes the item with a specific key from the list. |
In addition to these methods, you can use square brackets to index into the list, using the items' keys as indexes.
The following code demonstrates a SortedList
:
SortedList<string, string> addresses =
new SortedList<string, string>();
addresses.Add("Dan", "4 Deer Dr, Bugville VT, 01929");
addresses.Add("Bob", "8273 Birch Blvd, Bugville VT, 01928");
addresses["Cindy"] = "32878 Carpet Ct, Bugville VT, 01929";
addresses["Alice"] = "162 Ash Ave, Bugville VT, 01928";
addresses["Bob"] = "8273 Bash Blvd, Bugville VT, 01928";
MessageBox.Show("Bob's address is " + addresses["Bob"]);
The code starts by declaring and initializing a list to use keys and values that are both strings. It uses the Add
method to add some entries and then uses square brackets to add some more.
Next the code uses the square bracket syntax to update Bob's address. Finally the code displays Bob's new address.
You can't see it from this example, but unlike the List
class, SortedList
actually stores its items ordered by key. For example, you could use the GetKeyList
and GetValueList
methods to get the list's keys and values in that order.
The Dictionary
and SortedDictionary
classes provide features similar to the SortedList
class, manipulating key/value pairs. The difference is in the data structures the three classes use to store their items.
Without getting into technical details, the results are that the three classes use different amounts of memory and work at different speeds. In general, SortedList
is the slowest but takes the least memory. Dictionary
is the fastest but takes the most memory.
For small programs, the difference is insignificant. For big programs that work with thousands of entries, you might need to be more careful about picking a class. (Personally I like Dictionary
for most purposes because speed is nice, memory is relatively cheap, and the name is suggestive of the way you use the class: to look up something by key.)
A Queue
is a collection that lets you add items at one end and remove them from the other. It's like the line at a bank where you stand at the back of the line and the teller helps the person at the front of the line until eventually it's your turn.
Table 16.3 summarizes the Queue
's most important methods.
Method | Purpose |
Clear |
Removes all items from the Queue . |
Dequeue |
Returns the item at the front of the Queue and removes it. |
Enqueue |
Adds an item to the back of the Queue . |
Peek |
Returns the item at the front of the Queue without removing it. |
A Stack
is a collection that lets you add items at one end and remove them from the same end. It's like a stack of books on the floor: you can add a book to the top of the stack and remove a book from the top, but you can't pull one out of the middle or bottom without risking a collapse.
The top of a stack is also sometimes called its head. The bottom is sometimes called its tail.
Table 16.4 summarizes the Stack
's most important methods.
Method | Purpose |
Clear |
Removes all items from the Stack . |
Peek |
Returns the item at the top of the Stack without removing it. |
Pop |
Returns the item at the top of the Stack and removes it. |
Push |
Adds an item to the top of the Stack . |
In this Try It, you use a Dictionary
to build the order lookup program shown in Figure 16.4. When the user clicks the Add button, the program adds a new item with the given order ID and items. If the user enters an order ID and clicks Find, the program retrieves the corresponding items. If the user enters an order ID and some items and then clicks Update, the program updates the order's items.
In this lesson, you:
Dictionary
field named Orders
. Set its generic type parameters to int
(for order ID) and string
(for items).Items
TextBox
's MultiLine
and AcceptsReturn
properties to true
.Dictionary
named Orders
. Set its generic type parameters to int
(for order ID) and string
(for items).
Orders
field:// The dictionary to hold orders.
private Dictionary<int, string> Orders =
new Dictionary<int, string>();
Dictionary
's Add
method passing it the order ID and items entered by the user. The Dictionary
's order ID must be an integer so use int.Parse
to convert the value entered by the user into an int
.
Optionally you can add code to clear the TextBox
es to get ready for the next entry.
The code could be similar to the following:
// Add an order.
private void addButton_Click(object sender, EventArgs e)
{
// Add the order data.
Orders.Add(int.Parse(orderIdTextBox.Text), itemsTextBox.Text);
// Get ready for the next one.
orderIdTextBox.Clear();
itemsTextBox.Clear();
orderIdTextBox.Focus();
}
// Look up an order.
private void findButton_Click(object sender, EventArgs e)
{
itemsTextBox.Text = Orders[int.Parse(orderIdTextBox.Text)];
}
// Update an order.
private void updateButton_Click(object sender, EventArgs e)
{
Orders[int.Parse(orderIdTextBox.Text)] = itemsTextBox.Text;
}
0! = 1
N! = N * (N - 1)!
Hint: For testing purposes, make sure the program can calculate 0! and 20! without crashing.
Stack
of String
s. The program should display a TextBox
and two Button
s labeled Push and Pop. When the user clicks Push, add the current text to the stack. When the user clicks Pop, remove the next item from the stack and display it in the TextBox
.Stack
is empty. Hint: Use the Stack
's Count
property.Stack
's contents in a ListBox
with the most recently added item at the top of the ListBox
. Hint: Use the ListBox
's Insert
and RemoveAt
methods to update its contents as you add and remove items from the Stack
.Queue
instead of a Stack
. Give the Button
s the captions Enqueue and Dequeue instead of Push and Pop. Make the ListBox
display the items with the most recently added item at the bottom.Dictionary
should use the DateTime
type for keys and the string
type for values. Let the user pick dates from a DateTimePicker
.
Hint: When the DateTimePicker
first starts, it defaults to the current time, which may include fractional seconds. After the user changes the control's selection, however, the value no longer includes fractional seconds. That makes it hard to search for the exact same date and time later, at least if the user enters a value before changing the control's initial value.
To avoid this problem, when the form loads, initialize the DateTimePicker
to a value that doesn't include fractional seconds. Use the properties provided by DateTime.Now
to create a new DateTime
and set the DateTimePicker
's Value
property to that.
Use a ComboBox
to let the user select a day of the month. When the ComboBox
's value changes, display the corresponding day's plan in a large TextBox
on the form.
Hint: Use the ComboBox
's SelectedIndex
property as an index into the array. Note that this program doesn't let the user enter or modify the plan, it just displays hardcoded values. To let the user modify the plan, you would need Find and Update buttons similar to those used in other exercises.
To test the code, set a breakpoint at the beginning of the code that handles the File menu's New command. Run the program and select all of the squares. Then invoke the New menu item and use the debugger to view the array.
Hint: Use code similar to the following to reset the array when the user starts a new game:
Board = new char[,]
{
{' ', ' ', ' ' },
{' ', ' ', ' ' },
{' ', ' ', ' ' },
};
Dictionary
to make a simple phone book that lets the user add and look up name and phone number pairs.PictureBox
, let the user select an image file from an OpenFileDialog
. Use code similar to the following to display the selected image:
imagePictureBox.Image = new Bitmap(imageOpenFileDialog.FileName);
Enable the Add Button
when the TextBox
and PictureBox
are non-blank. When the user clicks Add, add the PictureBox
's image to a Dictionary
with the name as its key, add the name to the ListBox
, and blank the TextBox
and PictureBox
. (Blank the PictureBox
by setting its Image
property to null
.)
Finally, when the user clicks a name in the ListBox
, display the corresponding name and picture.
The program should have these features:
Dictionary
to hold account balances with integer account numbers as keys.Button
s when both TextBox
es contain non-blank text.ListBox
.Dictionary
to get the account's current balance.ListBox
.Dictionary
.ListBox
.ListBox
at the same position as the old entry.ListBox
entry, display the account number and balance in the TextBox
es.1,200 Gummy slugs @ $0.02 = $24.00
When the user clicks Parse, the program should use the string
class's Split
method to get the item's name, price each, and total price. It should then add the values to a ListBox
.
Hint: The Split
method can take as a parameter an array of delimiters. (That makes parsing a lot easier.)
TextBox
, the program should display a Label
that indicates whether the text is a palindrome. Hints:
Label
s, one that says “A Palindrome” and one that says “Not A Palindrome.” Use a boolean expression to set their Visible
properties appropriately.string
.)string
's ToCharArray
method to get an array containing the string
's characters.Array.Reverse
to reverse the array.string
:
string reversed = new string(chars);
string
and the reversed string
.3.135.249.220