Generic Types
List Collections
ArrayList Collections
KeyValuePair Types
Dictionary Collections
Hashtable Collections
This chapter focuses mostly on ways to manage large groups of objects with List, ArrayList, Dictionary, and Hashtable collections. All collection types covered in this chapter are dynamically sized so items can be added or removed to and from them at run time. Each collection class offers useful methods and options to add, insert, remove, and find specific items within the collection.
Before we delve into collections, we will cover generic types. Coverage of generic types is necessary to understand how the List and Dictionary collections enforce uniform types that are defined when these collections are initialized. Generic structures are an important part of the C# library too, because they enable other structures such as LINQ syntax.
Generic classes and structs are type-safe templates that are defined without a specific data type. The big advantage generic templates offer is that they can be reused with different data types.
A generic class or struct declaration is similar to a regular class or struct declaration. The generic declaration, however, includes at least one generic type argument within opening and closing angle brackets immediately after the class name.
Type arguments in the generic template are often given the name T, followed by consecutive letters of the alphabet such as U, V, and W for additional arguments. For the sake of clarity, you can add more descriptive type arguments, such as TKey or TValue.
A class header for a declaration that includes one generic type could look like the following:
class QuizQuestion <T> {}
On a similar note, a class header for a class with two generic types could look like
class QuizQuestion <T,U> {}
A key point to remember is that T and U can be any type and the template doesn’t care what type T or U is. The QuizQuestion declaration is for a class that operates on object(s) of type T or U regardless of the object types.
When a generic class or struct is defined, it can also be implemented to receive parameters of the generic type as an option:
public QuizQuestion(string question, T answer) {
}
To declare objects of a generic class or struct, provide the template name, the type arguments, and the name of the object. When initializing objects of generic classes or structs, you define the actual data type(s) to be implemented in angle brackets after the new keyword and constructor with parameters.
Here is an example of how to define an object of a generically typed QuizQuestion class. Start with the class and constructor declarations. The class declaration includes the type argument within angle brackets, and the QuizQuestion constructor receives a parameter of the generic type:
When declaring an object of the generically typed QuizQuestion class, the data type in this case is set to an integer within the angle brackets. Since an integer is used in this object declaration, an integer value can be passed to the constructor when the object is initialized:
QuizQuestion<int> questionA<br/>= new QuizQuestion<int> ("How many teeth do sharks use in a lifetime?", 30000);
Here is another valid declaration of a QuizQuestion object that defines the generic type with a string to store the answer in the string format:
In addition to receiving parameters of generic types, methods can be designed to return values of the generic type:
T GetAnswer() {}
Properties can also be defined with the format of a generic type:
T Answer { get; private set; }
Variables in a generic class or struct may be declared with the generic format. Generic variables can be declared at the class or struct level, in the method header, and inside methods:
T answerVariable;
This example shows a generic QuizQuestion class that stores quiz questions and answers in varying data formats. The type argument, T, defines a generic data type used within the class. The first QuizQuestion object assigns an integer to the generic type. The second QuizQuestion object assigns a string to the generic type.
The output from this example shows answers of different data types, where the first answer is an integer type and the second answer is a string type:
This example shows how a QuizQuestion class could implement two generic types. The QuizQuestion class in this case manages a question and two multiple choice responses of varying data types. Most often, C# developers will declare the first type argument as T. The next generic type is represented with U.
When you run this, the output shows the answers implemented with an integer and string type:
(a) is a System.Int32
(b) is a System.String
Approximately how many shark species exist?
a) 350
b) None of the above.
This exercise offers you a chance to practice working with generic types. Here are the steps:
1. Modify Example 10-2 so that the QuizQuestion class accepts three type arguments and manages a question with three different types of responses.
2. Modify the QuizQuestion class and question declaration inside Main() so that the output from running the program becomes
(a) is a System.String
(b) is a System.String
(c) is a System.String
Circle the responses which are true.
a) Sharks are the only fish that have eyelids.
b) Starfish and jellyfish are not fish.
c) Somebody who studies fish is an ichthyologist.
Now that we have covered generic types, let’s apply this knowledge to collections.
List collections are dynamically allocated type-safe collections that implement generic types. They contain either objects of identical types or objects that inherit from the same base class. Fittingly, the List class is included in the System.Collections.Generic namespace. Where T is the data type stored in the list, this collection is declared with the syntax:
List<T> collection = new List<T>();
The List class implements the IEnumerable interface, which allows you to iterate through the collection. We will discuss interfaces in Chapter 12. Basically, though, the IEnumerable interface supports iteration through the collection.
The List collection provides convenient methods for adding, retrieving, and removing data from the collection. Since the List class is a generic template, it doesn’t care which type is being passed to it. It just offers a structure to manage items of the same type so you can add items, remove items, and iterate over them. There is nothing that a list does that ties it to the type of item that is being listed. Because of this, the methods provided in the List class are also generic. With this in mind, let’s look at the methods in this series. The Add() method adds items of type T:
void Add(T item);
The Insert() method adds an object of type T to the List collection at a specific position set by the index:
Insert(int index, T item);
The InsertRange() method inserts a List collection into another List collection of the same type at a specific position set by the index:
void InsertRange(int index, IEnumerable<T> collection);
Remove() deletes an item from the List collection by value:
bool Remove(T item);
The RemoveRange() method deletes a fixed number of objects from a list starting at a specific indexed position:
void RemoveRange(int index, int count);
RemoveAt() deletes an object at a specific position from the list:
void RemoveAt(int index);
Clear() removes all items from the list:
void Clear();
This example demonstrates some of the methods for adding and deleting names to and from a List collection. The list in this case is defined with a string type.
The items of the collection are displayed with a foreach loop once items have been added to the list and then after some have been removed:
Al, Bob, Cal, Dora, Ed,
Bob, Cal, Ed,
This exercise offers a chance to practice working with List collections and methods of the List class.
1. Write a program that has a list of integers.
2. Add, display, insert, and remove the List items in a manner that generates the following output exactly:
Adding items:
1, 8
*** Inserting one item:
1, 2, 8
*** Inserting range of items:
1, 2, 3, 4, 5, 6, 7, 8
*** Removing range of two items:
1, 4, 5, 6, 7, 8
*** Removing item by object:
1, 4, 5, 6, 8
*** Removing item by index:
4, 5, 6, 8
The first item is 4
*** Clearing all items:
This example uses a List collection to store a family of bank account objects. Since the List type is defined with the Account base class, the list implements polymorphism because it also manages any descendant objects of the base class that are added or removed at run time. In this case, Savings and Checking objects are stored within the List collection that is initialized with the Account base class.
The output from this example is displayed by iterating through the list and referencing each object with a position index:
This is a Checking object.
This is a Savings object.
The objects for this example went into the list as Checking and Savings instances, but the objects taken out are of the type Account. As long as you access objects in the list through the methods and properties defined in the Account class, you’re fine. You can cast each object to their implemented class, but the list and its users see only Account objects.
The ArrayList class is similar to the List class in many ways. However, the ArrayList class can store multiple objects of unrelated data types. To enable the ArrayList class in your code, you must reference the System.Collections namespace. The ArrayList class implements the IList interface to manage non-generic objects, so objects of the collection can be of any data type. IList extends the IEnumerable interface, so ArrayList also enables iteration through its objects.
Since any type of object can be placed in an ArrayList collection, the ArrayList declaration does not have a generic type in its definition:
ArrayList errands = new ArrayList();
Since ArrayList collections can store any type, you will often need to determine the data format of each object when retrieving items from the collection. When determining formats, all ArrayList collection items can be stored as objects of the Object class. The Object class supports all classes in C#. All object types also have a GetType() method that returns the object’s data type. This data type can be compared with the return value of the typeof operator when a specific class type is passed as a parameter.
The following example shows iteration through an ArrayList called errands. If the object belongs to the GroceryItem class, it is casted to an object of the GroceryItem type. The GroceryItem object can then be used to access the methods and properties of its class.
Like List collections, ArrayList collections offer many similar methods that enable easy addition, insertion, and removal of items. The Add() method can add objects of any type to the ArrayList collection even when objects of unrelated types also exist in the collection:
void Add(object item);
The Insert() method adds a new object of any type at the specified position index:
void Insert(int index, object item);
The InsertRange() method inserts any collection that implements the ICollection interface to an ArrayList collection at a specific index position. You can use InsertRange() to add collections like List collections or an array to an ArrayList collection:
void InsertRange(int index, ICollection collection);
The Remove() method removes any item from the ArrayList collection by value:
bool Remove(object item);
RemoveRange() can delete a set number of objects from the collection starting at a specified position index:
void RemoveRange(int index, int count);
RemoveAt() removes an item from the ArrayList collection at a specific position:
void RemoveAt(int index);
Clear() removes all items from the ArrayList collection:
void Clear();
This example demonstrates how to implement an ArrayList collection to manage a daily list of errands. This list currently stores both string data and GroceryItem formats, but it could store objects of any data type.
Two different versions of the errands list are shown in the output. The first version is displayed after all items are added. The second version is shown after two of the items have been removed from the collection. Methods used to display the output are selected according to the object type for each item in the collection. The Description and Quantity properties for GroceryItem objects are written to the console with the Display() method of the object. When the ArrayList item is just a string, it is written directly to the console.
Now that you have an idea of how flexible different collection types are, let’s examine the KeyValuePair type, which enables additional search options for large collections. The KeyValuePair type is a struct that stores a reference key and corresponding value.
The data type for the key and the value are defined when the KeyValuePair is initialized with TKey and TValue type parameters within angle brackets. The actual values of the KeyValuePair struct are passed as parameters to the KeyValuePair constructor:
KeyValuePair<TKey, TValue> kvp = new KeyValuePair<TKey, TValue>(key, value);
Once the KeyValuePair object is defined, you can reference the key with the Key property:
keyValuePair.Key
You can also reference the value of a KeyValuePair with the Value property:
keyValuePair.Value
This example shows how to create a KeyValuePair item to reference Greek and Roman names of mythological figures. The struct includes a bibliography identifier named BIBLIOGRAPHY_ID for the key and a Myth object for the value:
This example is not very practical, but it does offer a simple view of how to create and access properties of the KeyValuePair struct:
When you run the program, the output shows details about the KeyValuePair’s Key and Value properties:
This section looks at Dictionary collections that take advantage of the enhanced searches offered by KeyValuePair objects. Dictionary collections are type-safe collections that enable storage, retrieval, and removal of KeyValuePair objects. The Dictionary class is available through the System.Collections.Generic namespace. When declaring and initializing a Dictionary object, arguments denoted by TKey and TValue define the key and value data types:
Dictionary<TKey, TValue> dictionary = new Dictionary<TKey, TValue>();
Here is the definition of a Dictionary object that manages driver’s license certifications with a char type for the key and a string type for the value:
Dictionary<char, string> certifications = new Dictionary<char, string>();
Dictionary collections offer several helpful methods for adding, updating, and deleting KeyValuePair items. The Add() method adds a KeyValuePair item to the collection. The first parameter includes a key of the type TKey and the second parameter includes a value of type TValue:
void Add(TKey key, TValue value);
The ContainsKey() method checks the Dictionary collection to determine if a key exists and returns a true or false value, depending on the search outcome. The ContainsKey() method uses the key of the search item as the parameter:
bool ContainsKey(TKey key);
TryGetValue() performs a search of the Dictionary based on the key passed to it. This method returns a true or false value, depending on the search results. If the key is found, the associated value is stored in the second parameter:
bool TryGetValue(TKey key, out TValue value);
The Remove() method deletes items from the Dictionary collection:
bool Remove(TKey key);
Clear() removes all items from the Dictionary collection:
void Clear();
After adding items to the dictionary, you can iterate through the collection of KeyValuePair objects. If you had
Dictionary<char, string> certifications = new Dictionary<char, string>();
and you were to add items like
certifications.Add(‘P’, “Passenger transport”);
you could loop through all KeyValuePair objects as follows:
foreach(KeyValuePair<char, string> cert in certifications) Console.WriteLine(“Key: ” + cert.Key + “ Value: ” + cert.Value);
This example demonstrates the creation and management of a Dictionary that stores driver license certifications. The license certification type is referenced with a key of type char. The license value is a string that describes the license.
The Hashtable class is similar to the Dictionary class, but a Hashtable collection can manage KeyValuePair objects of varying types. Consequently, the Hashtable constructor does not require data type declarations for the key or value:
Hashtable hashTable = new Hashtable();
The Hashtable class is available in the System.Collections namespace.
The Add() method of the Hashtable class adds a dictionary item where keys of any value and type can be added to the collection:
void Add(object key, object value);
The ContainsKey() method checks for the existence of a Hashtable object by key:
bool ContainsKey(object key);
ContainsValue() checks for the existence of a Hashtable object with a specific value:
bool ContainsValue(object value);
The Remove() method deletes objects from the collection by key:
void Remove(object key);
Clear() removes all objects from the Hashtable collection:
void Clear();
When iterating through Hashtable items, the DictionaryEntry struct can be used to isolate each item in the collection. Then the GetType() method can be used to determine the data types of the Key and Value properties. Once the Dictionary object type is determined, the value can be converted to the required format with a cast:
This example offers practice with using a Hashtable class to store and manage letter and number grades:
Before the Hashtable collection contents are displayed, any number grades are converted to letter grades. After all items are displayed, additional output is used to test the ContainsKey(), ContainsValue(), and Remove() methods:
The following questions are intended to help reinforce your comprehension of the concepts covered in this chapter. The answers can be found in the accompanying online Appendix B, “Answers to the Self Tests.”
1. Answer the following questions true or false.
A. ____ The Dictionary class is available from the System.Collections.Generic namespace.
B. ____ One ArrayList collection can only store objects of the same type.
C. ____ One List collection can only store objects of the same type.
2. Match these terms—List, ArrayList, KeyValuePair, Dictionary, Hashtable—with the statements provided. Use each term only once:
A. ____________ This collection type stores objects with the same type of key value pairs.
B. ____________ This collection type does not require a key value pair and can store more than one type of object in a collection.
C. ____________ Implements generic types.
D. ____________ This collection type can store multiple key value pairs that do not have the same types.
E. ____________ This collection type stores only one type of object in a collection of multiple objects.
3. Starting with Example 10-4, declare a new class called JointSavings that inherits from the Savings class. Create an object of the JointSavings class and add it to the list. Modify the code so your output appears as follows:
This is a Checking object.
This is a Savings object.
This is a JointSavings object.
4. Starting with Example 10-5, add the following new class called BillableItem:
Then inside the Main() method, create a new object called BillableItem and add it to the ArrayList collection. When iterating through all items in your collection, invoke the Display() method of the BillableItem object to also show the bill description and amount with the output.
5. Write a program to create a KeyValuePair item that stores an integer for the key and a string for the value. Then, initialize the KeyValuePair item with a reference of 1 for the key when storing “apple” for the value. Finally, output the key and value of your KeyValuePair item.
6. Create a Dictionary collection that stores information about books where the ISBN number is the key and the book title is the value. Store two books in the dictionary and then iterate through the collection to display the book titles. For sample data, try adding ISBN#978-0071809375, “JavaScript, Fourth Edition: A Beginner’s Guide” and ISBN#978-0071817912, “jQuery: A Beginner’s Guide”.
7. Create a Hashtable collection that stores document information and add data shown here.
Iterate through the Hashtable collection to display both the key and value for each item in the collection.