Chapter 13. Arrays and Collections

In programming, you often need to work with collections of related data. For example, you may have a list of customers and you need a way to store their email addresses. In that case, you can use an array to store the list of strings.

In .NET, there are many collection classes that you can use to represent groups of data. In addition, there are various interfaces that you can implement so that you can manipulate your own custom collection of data.

This chapter examines:

  • Declaring and initializing arrays

  • Declaring and using multidimensional arrays

  • Declaring a parameter array to allow a variable number of parameters in a function

  • Using the various System.Collections namespace interfaces

  • Using the different collection classes (such as Dictionary, Stacks, and Queue) in .NET

Arrays

An array is an indexed collection of items of the same type. To declare an array, specify the type with a pair of brackets followed by the variable name. The following statements declare three array variables of type int, string, and decimal, respectively:

int[] num;
            string[] sentences;
            decimal[] values;

Array variables are actually objects. In this example, num, sentences, and values are objects of type System.Array.

These statements simply declare the three variables as arrays; the variables are not initialized yet, and at this stage you do not know how many elements are contained within each array.

To initialize an array, use the new keyword. The following statements declare and initialize three arrays:

int[] num = new int[5];
            string[] sentences = new string[3];
            decimal[] values = new decimal[4];

The num array now has five members, while the sentences array has three members, and the values array has four. The rank specifier of each array (the number you indicate within the []) indicates the number of elements contained in each array.

You can declare an array and initialize it separately, as the following statements show:

//---declare the arrays---
            int[] num;
            string[] sentences;
            decimal[] values;

            //---initialize the arrays with default values---
            num = new int[5];
            sentences = new string[3];
            values = new decimal[4];

When you declare an array using the new keyword, each member of the array is initialized with the default value of the type. For example, the preceding num array contains elements of value 0. Likewise, for the sentences string array, each of its members has the default value of null.

To learn the default value of a value type, use the default keyword, like this:

object x;
            x = default(int); //---0---
            x = default(char); //---0 ''---
            x = default(bool); //---false---

To initialize the array to some value other than the default, you use an initialization list. The number of elements it includes must match the array's rank specifier. Here's an example:

int[] num = new int[5] { 1, 2, 3, 4, 5 };
            string[] sentences = new string[3] {
                "C#", "Programmers", "Reference"
            };
            decimal[] values = new decimal[4] {1.5M, 2.3M, 0.3M,5.9M};

Because the initialization list already contains the exact number of elements in the array, the rank specifier can be omitted, like this:

int[] num = new int[] { 1, 2, 3, 4, 5 };
            string[] sentences = new string[] {
                "C#", "Programmers", "Reference"
            };
            decimal[] values = new decimal[] {1.5M, 2.3M, 0.3M,5.9M};

Use the new var keyword in C# to declare an implicitly typed array:

var num = new [] { 1, 2, 3, 4, 5 };
            var sentences = new [] {
                "C#", "Programmers", "Reference"
            };
            var values = new [] {1.5M, 2.3M, 0.3M,5.9M};

For more information on the var keyword, see Chapter 3.

In C#, arrays all derive from the abstract base class Array (in the System namespace) and have access to all the properties and methods contained in that. In Figure 13-1 IntelliSense shows some of the properties and methods exposed by the num array.

Figure 13-1

Figure 13.1. Figure 13-1

That means you can use the Rank property to learn the dimension of an array. To find out how many elements are contained within an array, you can use the Length property. The following statements produce the output shown in Figure 13-2.

Console.WriteLine("Dimension of num is {0}", num.Rank);
            Console.WriteLine("Number of elements in num is {0}", num.Length);
Figure 13-2

Figure 13.2. Figure 13-2

To sort an array, you can use the static Sort() method in the Array class:

int[] num = new int[] { 5, 3, 1, 2, 4 };
            Array.Sort(num);
            foreach (int i in num)
                Console.WriteLine(i);

These statements print out the array in sorted order:

1
2
3
4
5

Accessing Array Elements

To access an element in an array, you specify its index, as shown in the following statements:

int[] num = new int[5] { 1, 2, 3, 4, 5 };
            Console.WriteLine(num[0]); //---1---
            Console.WriteLine(num[1]); //---2---
            Console.WriteLine(num[2]); //---3---
            Console.WriteLine(num[3]); //---4---
            Console.WriteLine(num[4]); //---5---

The index of an array starts from 0 to n-1. For example, num has size of 5 so the index runs from 0 to 4.

You usually use a loop construct to run through the elements in an array. For example, you can use the for statement to iterate through the elements of an array:

for (int i = 0; i < num.Length; i++)
                Console.WriteLine(num[i]);

You can also use the foreach statement, which is a clean way to iterate through the elements of an array quickly:

foreach (int n in num)
                Console.WriteLine(n);

Multidimensional Arrays

So far the arrays you have seen are all one-dimensional ones. Arrays may also be multidimensional. To declare a multidimensional array, you can the comma (,) separator. The following declares xy to be a 2-dimensional array:

int[,] xy;

To initialize the two-dimensional array, you use the new keyword together with the size of the array:

xy =  new int[3,2];

With this statement, xy can now contain six elements (three rows and two columns). To initialize xy with some values, you can use the following statement:

xy = new int[3, 2] { { 1, 2 }, { 3, 4 }, { 5, 6 } }; ;

The following statement declares a three-dimensional array:

int[, ,] xyz;

To initialize it, you again use the new keyword together with the size of the array:

xyz = new int[2, 2, 2];

To initialize the array with some values, you can use the following:

int[, ,] xyz;
            xyz = new int[,,] {
               { { 1, 2 }, { 3, 4 } },
               { { 5, 6 }, { 7, 8 } }
            };

To access all the elements in the three-dimensional array, you can use the following code snippet:

for (int x = xyz.GetLowerBound(0); x <= xyz.GetUpperBound(0); x++)
            for (int y = xyz.GetLowerBound(1); y <= xyz.GetUpperBound(1); y++)
                for (int z = xyz.GetLowerBound(2); z <= xyz.GetUpperBound(2); z++)
                    Console.WriteLine(xyz[x, y, z]);

The Array abstract base class contains the GetLowerBound() and GetUpperBound() methods to let you know the size of an array. Both methods take in a single parameter, which indicates the dimension of the array about which you are inquiring. For example, GetUpperBound(0) returns the size of the first dimension, GetUpperBound(1) returns the size of the second dimension, and so on.

You can also use the foreach statement to access all the elements in a multidimensional array:

foreach (int n in xyz)
                Console.WriteLine(n);

These statements print out the following:

1
2
3
4
5
6
7
8

Arrays of Arrays: Jagged Arrays

An array's elements can also contain arrays. An array of arrays is known as a jagged array. Consider the following statements:

Point[][] lines = new Point[5][];
            lines[0] = new Point[4];
            lines[1] = new Point[15];
            lines[2] = new Point[7];
            lines[3] = ...
            lines[4] = ...

Here, lines is a jagged array. It has five elements and each element is a Point array. The first element is an array containing four elements, the second contains 15 elements, and so on.

The Point class represents an ordered pair of integer x- and y-coordinates that defines a point in a two-dimensional plane.

You can use the array initializer to initialize the individual array within the lines array, like this:

Point[][] lines = new Point[3][];
            lines[0] = new Point[] {
                new Point(2, 3), new Point(4, 5)
            }; //---2 points in lines[0]---

            lines[1] = new Point[] {
                new Point(2, 3), new Point(4, 5) , new Point(6, 9)
            }; //---3 points in lines[1]---

            lines[2] = new Point[] {
                new Point(2, 3)
            }; //---1 point in lines[2]---

To access the individual Point objects in the lines array, you first specify which Point array you want to access, followed by the index for the elements in the Point array, like this:

//---get the first point in lines[0]---
            Point ptA = lines[0][0]; //---(2,3)

            //---get the third point in lines[1]---
            Point ptB = lines[1][2]; //---(6,9)---

A jagged array can also contain multidimensional arrays. For example, the following declaration declares nums to be a jagged array with each element pointing to a two-dimensional array:

int[][,] nums = new int[][,]
            {
                new int[,] {{ 1, 2 }, { 3, 4 }},
                new int[,] {{ 5, 6 }, { 7, 8 }}
            };

To access an individual element within the jagged array, you can use the following statements:

Console.WriteLine(nums[0][0, 0]); //---1---
            Console.WriteLine(nums[0][0, 1]); //---2---
            Console.WriteLine(nums[0][1, 0]); //---3---
            Console.WriteLine(nums[0][1, 1]); //---4---
            Console.WriteLine(nums[1][0, 0]); //---5---
            Console.WriteLine(nums[1][0, 1]); //---6---
            Console.WriteLine(nums[1][1, 0]); //---7---
            Console.WriteLine(nums[1][1, 1]); //---8---

Used on a jagged array, the Length property of the Array abstract base class returns the number of arrays contained in the jagged array:

Console.WriteLine(nums.Length); //---2---

Parameter Arrays

In C#, you can pass variable numbers of parameters into a function/method using a feature known as parameter arrays. Consider the following statements:

string firstName = "Wei-Meng";
            string lastName = "Lee";
            Console.WriteLine("Hello, {0}", firstName);
            Console.WriteLine("Hello, {0} {1}", firstName, lastName);

Observe that the last two statements contain different numbers of parameters. In fact, the WriteLine() method is overloaded, and one of the overloaded methods has a parameter of type params (see Figure 13-3). The params keyword lets you specify a method parameter that takes an argument where the number of arguments is variable.

Figure 13-3

Figure 13.3. Figure 13-3

A result of declaring the parameter type to be of params is that callers to the method do not need to explicitly create an array to pass into the method. Instead, they can simply pass in a variable number of parameters.

To use the params type in your own function, you define a parameter with the params keyword:

private void PrintMessage(string prefix, params string[] msg)
        {
        }

To extract the parameter array passed in by the caller, treat the params parameter like a normal array, like this:

private void PrintMessage(string prefix, params string[] msg)
        {
            foreach (string s in msg)
                Console.WriteLine("{0}>{1}", prefix, s);
        }

When calling the PrintMessage() function, you can pass in a variable number of parameters:

PrintMessage("C# Part 1", "Arrays", "Index", "Collections");
            PrintMessage("C# Part 2", "Objects", "Classes");

These statements generate the following output:

C# Part 1>Arrays
C# Part 1>Index
C# Part 1>Collections
C# Part 2>Objects
C# Part 2>Classes

Note

A params parameter must always be the last parameter defined in a method declaration.

Copying Arrays

To copy from one array to another, use the Copy() method from the Array abstract base class:

int[] num = new int[5] { 1, 2, 3, 4, 5 };
            int[] num1 = new int[5];
            num.CopyTo(num1, 0);

These statements copy all the elements from the num array into the num1 array. The second parameter in the CopyTo() method specifies the index in the array at which the copying begins.

Collections Interfaces

The System.Collections namespace contains several interfaces that define basic collection functionalities:

The interfaces described in the following list are the generic versions of the respective interfaces. Beginning with C# 2.0, you should always try to use the generic versions of the interfaces for type safety. Chapter 9 discusses the use of generics in the C# language.

Interface

Description

IEnumerable<T> and IEnumerator<T>

Enable you to loop through the elements in a collection.

ICollection<T>

Contains items in a collection and provides the functionality to copy elements to an array. Inherits from IEnumerable<T>.

IComparer<T> and IComparable<T>

Enable you to compare objects in a collection.

IList<T>

Inherits from ICollection and provides functionality to allow members to be accessed by index.

IDictionary<K,V>

Similar to IList<T>, but members are accessed by key value rather than index.

The ICollection<T> interface is the base interface for classes in the System.Collections namespace.

Dynamic Arrays Using the ArrayList Class

Arrays in C# have a fixed size once they are initialized. For example, the following defines a fixed-size array of five integer elements:

int[] num = new int[5];

If you need to dynamically increase the size of an array during runtime, use the ArrayList class instead. You use it like an array, but its size can be increased dynamically as required.

The ArrayList class is located within the System.Collections namespace, so you need to import that System.Collections namespace before you use it. The ArrayList class implements the IList interface.

To use the ArrayList class, you first create an instance of it:

ArrayList arrayList = new ArrayList();

Use the Add() method to add elements to an ArrayList object:

arrayList.Add("Hello");
            arrayList.Add(25);
            arrayList.Add(new Point(3,4));
            arrayList.Add(3.14F);

Notice that you can add elements of different types to an ArrayList object.

To access an element contained within an ArrayList object, specify the element's index like this:

Console.WriteLine(arrayList[0]); //---Hello---
            Console.WriteLine(arrayList[1]); //---25---
            Console.WriteLine(arrayList[2]); //---{X=3, Y=4}
            Console.WriteLine(arrayList[3]); //---3.14---

The ArrayList object can contain elements of different types, so when retrieving items from an ArrayList object make sure that the elements are assigned to variables of the correct type. Elements retrieved from an ArrayList object belong to Object type.

You can insert elements to an ArrayList object using the Insert() method:

arrayList.Insert(1, " World!");

After the insertion, the ArrayList object now has five elements:

Console.WriteLine(arrayList[0]); //---Hello---
            Console.WriteLine(arrayList[1]); //---World!---
            Console.WriteLine(arrayList[2]); //---25---
            Console.WriteLine(arrayList[3]); //---{X=3,Y=4}---
            Console.WriteLine(arrayList[4]); //---3.14---

To remove elements from an ArrayList object, use the Remove() or RemoveAt() methods:

arrayList.Remove("Hello");
            arrayList.Remove("Hi");            //---cannot find item---
            arrayList.Remove(new Point(3, 4));
            arrayList.RemoveAt(1);

After these statements run, the ArrayList object has only two elements:

Console.WriteLine(arrayList[0]); //---World!---
            Console.WriteLine(arrayList[1]); //---3.14---

If you try to remove an element that is nonexistent, no exception is raised (which is not very useful). It would be good to use the Contains() method to check whether the element exists before attempting to remove it:

if (arrayList.Contains("Hi"))
                arrayList.Remove("Hi");
            else
                Console.WriteLine("Element not found.");

You can also assign the elements in an ArrayList object to an array using the ToArray() method:

object[] objArray;
            objArray = arrayList.ToArray();

            foreach (object o in objArray)
                Console.WriteLine(o.ToString());

Because the elements in the ArrayList can be of different types you must be careful handling them or you run the risk of runtime exceptions. To work with data of the same type, it is more efficient to use the generic equivalent of the ArrayList class — the List<T> class, which is type safe. To use the List<T> class, you simply instantiate it with the type you want to use and then use the different methods available just like in the ArrayList class:

List<int> nums = new List<int>();
            nums.Add(4);
            nums.Add(1);
            nums.Add(3);
            nums.Add(5);
            nums.Add(7);
            nums.Add(2);
            nums.Add(8);

            //---sorts the list---
            nums.Sort();

            //---prints out all the elements in the list---
            foreach (int n in nums)
                Console.WriteLine(n);

Note

If you try to sort an ArrayList object containing elements of different types, you are likely to run into an exception because the compiler may not be able to compare the values of two different types.

Indexers and Iterators

Sometimes you may have classes that encapsulate an internal collection or array. Consider the following SpamPhraseList class:

public class SpamPhraseList
    {
        protected string[] Phrases =
            new string[]{
                "pain relief","paxil","pharmacy","phendimetrazine",
                "phentamine","phentermine","pheramones","pherimones",
                "photos of singles","platinum-celebs","poker-chip",
                "poze","prescription","privacy assured","product for less",
                "products for less","protect yourself","psychic"
            };

        public string Phrase(int index)
        {
            if (index >= 0 && index < Phrases.Length)
                return Phrases[index];
            else
                return string.Empty;
        }
    }

The SpamPhraseList class has a protected string array called Phrases. It also exposes the Phrase() method, which takes in an index and returns an element from the string array:

SpamPhraseList list = new SpamPhraseList();
            Console.WriteLine(list.Phrase(17)); //---psychic---

Because the main purpose of the SpamPhraseList class is to return one of the phrases contained within it, it might be more intuitive to access it more like an array, like this:

SpamPhraseList list = new SpamPhraseList();
            Console.WriteLine(list[17]); //---psychic---

In C#, you can use the indexer feature to make your class accessible just like an array. Using the SpamPhraseList class, you can use the this keyword to declare an indexer on the class:

public class SpamPhraseList
    {
        protected string[] Phrases =
            new string[]{
                "pain relief","paxil","pharmacy","phendimetrazine",
                "phentamine","phentermine","pheramones","pherimones",
                "photos of singles","platinum-celebs","poker-chip",
                "poze","prescription","privacy assured","product for less",
                "products for less","protect yourself","psychic"
            };

        public string this[int index]
        {
            get
            {
                if (index >= 0 && index < Phrases.Length)
                    return Phrases[index];
                else
                    return string.Empty;
            }
            set
            {
                if (index >= 0 && index < Phrases.Length)
                    Phrases[index] = value;
            }
        }
    }

Once the indexer is added to the SpamPhraseList class, you can now access the internal array of string just like an array, like this:

SpamPhraseList list = new SpamPhraseList();
            Console.WriteLine(list[17]); //---psychic---

Besides retrieving the elements from the class, you can also set a value to each individual element, like this:

list[17] = "psycho";

The indexer feature enables you to access the internal arrays of elements using array syntax, but you cannot use the foreach statement to iterate through the elements contained within it. For example, the following statements give you an error:

SpamPhraseList list = new SpamPhraseList();
            foreach (string s in list) //---error---
                Console.WriteLine(s);

To ensure that your class supports the foreach statement, you need to use a feature known as iterators. Iterators enable you to use the convenient foreach syntax to step through a list of items in a class. To create an iterator for the SpamPhraseList class, you only need to implement the GetEnumerator() method, like this:

public class SpamPhraseList
    {
        protected string[] Phrases =
            new string[]{
                "pain relief","paxil","pharmacy","phendimetrazine",
                "phentamine","phentermine","pheramones","pherimones",
                "photos of singles","platinum-celebs","poker-chip",
                "poze","prescription","privacy assured","product for less",
                "products for less","protect yourself","psychic"
            };

        public string this[int index]
        {
            get
            {
                if (index >= 0 && index < Phrases.Length)
                    return Phrases[index];
                else
                    return string.Empty;
            }
            set
            {
                if (index >= 0 && index < Phrases.Length)
                    Phrases[index] = value;
            }
        }

        public IEnumerator<string> GetEnumerator()
        {
            foreach (string s in Phrases)
            {
                yield return s;
            }
        }
    }

Within the GetEnumerator() method, you can use the foreach statement to iterate through all the elements in the Phrases array and then use the yield keyword to return individual elements in the array.

You can now iterate through the elements in a SpamPhraseList object using the foreach statement:

SpamPhraseList list = new SpamPhraseList();
            foreach (string s in list)
                Console.WriteLine(s);

Implementing IEnumerable<T> and IEnumerator<T>

Besides using the iterators feature in your class to allow clients to step through its internal elements with foreach, you can make your class support the foreach statement by implementing the IEnumerable and IEnumerator interfaces. The generic equivalents of these two interfaces are IEnumerable<T> and IEnumerator<T>, respectively.

Note

Use the generic versions because they are type safe.

In .NET, all classes that enumerate objects must implement the IEnumerable (or the generic IEnumerable<T>) interface. The objects enumerated must implement the IEnumerator (or the generic IEnumerable<T>) interface, which has the following members:

  • Current — Returns the current element in the collection

  • MoveNext() — Advances to the next element in the collection

  • Reset() — Resets the enumerator to its initial position

The IEnumerable interface has one member:

  • GetEnumerator() — Returns the enumerator that iterates through a collection

Note

All the discussions from this point onward use the generic versions of the IEnumerable and IEnumerator interfaces because they are type-safe.

To understand how the IEnumerable<T> and IEnumerator<T> interfaces work, modify SpamPhraseList class to implement the IEnumerable<T> interface:

public class SpamPhraseList : IEnumerable<string>
{
    protected string[] Phrases =
        new string[]{
                "pain relief","paxil","pharmacy","phendimetrazine",
                "phentamine","phentermine","pheramones","pherimones",
                "photos of singles","platinum-celebs","poker-chip",
"poze","prescription","privacy assured","product for less",
                "products for less","protect yourself","psychic"
            };

    //---for generic version of the class---
    public IEnumerator<string> GetEnumerator()
    {

    }

    //---for non-generic version of the class---
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {

    }
      }

Notice that for the generic version of the IEnumerable interface, you need to implement two versions of the GetEnumerator() methods — one for the generic version of the class and one for the nongeneric version.

To ensure that the SpamPhraseList class can enumerate the strings contained within it, you define an enumerator class within the SpamPhraseList class:

public class SpamPhraseList : IEnumerable<string>
{
    private class SpamPhrastListEnum : IEnumerator<string>
    {
        private int index = −1;
        private SpamPhraseList spamlist;

        public SpamPhrastListEnum(SpamPhraseList sl)
        {
            this.spamlist = sl;
        }

        //---for generic version of the class---
        string IEnumerator<string>.Current
        {
            get
            {
                return spamlist.Phrases[index];
            }
        }

        //---for non-generic version of the class---
        object System.Collections.IEnumerator.Current
        {
            get
{
                return spamlist.Phrases[index];
            }
        }

        bool System.Collections.IEnumerator.MoveNext()
        {
            index++;
            return index < spamlist.Phrases.Length;
        }

        void System.Collections.IEnumerator.Reset()
        {
            index = −1;
        }

        void IDisposable.Dispose() { }
    }

    protected string[] Phrases =
        new string[]{
                "pain relief","paxil","pharmacy","phendimetrazine",
                "phentamine","phentermine","pheramones","pherimones",
                "photos of singles","platinum-celebs","poker-chip",
                "poze","prescription","privacy assured","product for less",
                "products for less","protect yourself","psychic"
            };

    public IEnumerator<string> GetEnumerator()
    {
        return new SpamPhrastListEnum(this);
    }

    //---for non-generic version of the class---
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return new SpamPhrastListEnum(this);
    }
}

In this example, the SpamPhrastListEnum class implements the IEnumerator<string> interface and provides the implementation for the Current property and the MoveNext() and Reset() methods.

To print out all the elements contained within a SpamPhraseList object, you can use the same statements that you used in the previous section:

SpamPhraseList list = new SpamPhraseList();
            foreach (string s in list) //---error---
                Console.WriteLine(s);

Behind the scenes, the compiler is generating the following code for the foreach statement:

SpamPhraseList list = new SpamPhraseList();
            IEnumerator<string> s = list.GetEnumerator();
            while (s.MoveNext())
                Console.WriteLine((string)s.Current);

Implementing Comparison Using IComparer<T> and IComparable<T>

One of the tasks you often need to perform on a collection of objects is sorting. You need to know the order of the objects so that you can sort them accordingly. Objects that can be compared implement the IComparable interface (the generic equivalent of this interface is IComparable<T>). Consider the following example:

string[] Names = new string[] {
                "John", "Howard",
                "Margaret", "Brian" };

            foreach (string n in Names)
                Console.WriteLine(n);

Here, Names is a string array containing four strings. This code prints out the following:

John
            Howard
            Margaret
            Brian

You can sort the Names array using the Sort() method from the abstract static class Array, like this:

Array.Sort(Names);
            foreach (string n in Names)
                Console.WriteLine(n);

Now the output is a sorted array of names:

Brian
            Howard
            John
            Margaret

In this case, the reason the array of string can be sorted is because the String type itself implements the IComparable interface, so the Sort() method knows how to sort the array correctly. The same applies to other types such as int, single, float, and so on.

What if you have your own type and you want it to be sortable? Suppose that you have the Employee class defined as follows:

public class Employee
{
    public string FirstName
    { get; set; }

    public string LastName
    { get; set; }

    public int Salary
    { get; set; }

    public override string ToString()
    {
        return FirstName + ", " + LastName +
           " $" + Salary;
    }
}

You can add several Employee objects to a List object, like this:

List<Employee> employees = new List<Employee>();
            employees.Add(new Employee()
            {
                FirstName = "John",
                LastName = "Smith",
                Salary = 4000
            });
            employees.Add(new Employee()
            {
                FirstName = "Howard",
                LastName = "Mark",
                Salary = 1500
            });
            employees.Add(new Employee()
            {
                FirstName = "Margaret",
                LastName = "Anderson",
                Salary = 3000
            });
            employees.Add(new Employee()
            {
                FirstName = "Brian",
                LastName = "Will",
                Salary = 3000
            });

To sort a List object containing your Employee objects, you can use the following:

employees.Sort();

However, this statement results in a runtime error (see Figure 13-4) because the Sort() method does not know how Employee objects should be sorted.

Figure 13-4

Figure 13.4. Figure 13-4

To solve this problem, the Employee class needs to implement the IComparable<T> interface and then implement the CompareTo() method:

public class Employee : IComparable<Employee>
{
    public string FirstName
    { get; set; }

    public string LastName
    { get; set; }

    public int Salary
    { get; set; }

    public override string ToString()
    {
        return FirstName + ", " + LastName +
           " $" + Salary;
    }

    public int CompareTo(Employee emp)
    {
        return this.FirstName.CompareTo(emp.FirstName);
    }
}

The CompareTo() method takes an Employee parameter, and you compare the current instance (represented by this) of the Employee class's FirstName property to the parameter's FirstName property. Here, you use the CompareTo() method of the String class (FirstName is of String type) to perform the comparison.

The return value of the CompareTo(obj) method has the possible values as shown in the following table.

Value

Meaning

Less than zero

The current instance is less than obj.

Zero

The current instance is equal to obj.

Greater than zero

The current instance is greater than obj.

Now, when you sort the List object containing Employee objects, the Employee objects will be sorted by first name:

employees.Sort();
            foreach (Employee emp in employees)
                Console.WriteLine(emp.ToString());

These statements produce the following output:

Brian, Will $3000
Howard, Mark $1500
John, Smith $4000
Margaret, Anderson $3000

To sort the Employee objects using the LastName instead of FirstName, simply change the CompareTo() method as follows:

public int CompareTo(Employee emp)
    {
        return this.LastName.CompareTo(emp.LastName);
    }

The output becomes:

Margaret, Anderson $3000
Howard, Mark $1500
John, Smith $4000
Brian, Will $3000

Likewise, to sort by salary, you compare the Salary property:

public int CompareTo(Employee emp)
    {
        return this.Salary.CompareTo(emp.Salary);
    }

The output is now:

Howard, Mark $1500
Margaret, Anderson $3000
Brian, Will $3000
John, Smith $4000

Instead of using the CompareTo() method of the type you are comparing, you can manually perform the comparison, like this:

public int CompareTo(Employee emp)
    {
        if (this.Salary < emp.Salary)
            return −1;
        else if (this.Salary == emp.Salary)
            return 0;
        else
            return 1;
    }

How the Employee objects are sorted is fixed by the implementation of the CompareTo() method. If CompareTo() compares using the FirstName property, the sort is based on the FirstName property. To give users a choice of which field they want to use to sort the objects, you can use the IComparer<T> interface.

To do so, first declare a private class within the Employee class and call it SalaryComparer.

public class Employee : IComparable<Employee>
{
    private class SalaryComparer : IComparer<Employee>
    {
        public int Compare(Employee e1, Employee e2)
        {
            if (e1.Salary < e2.Salary)
                return −1;
            else if (e1.Salary == e2.Salary)
                return 0;
            else
                return 1;
        }
    }

    public string FirstName
    { get; set; }

    public string LastName
    { get; set; }

    public int Salary
{ get; set; }

    public override string ToString()
    {
        return FirstName + ", " + LastName +
           " $" + Salary;
    }

    public int CompareTo(Employee emp)
    {
        return this.FirstName.CompareTo(emp.FirstName);
    }
}

The SalaryComparer class implements the IComparer<T> interface. IComparer<T> has one method — Compare() — that you need to implement. It compares the salary of two Employee objects.

To use the SalaryComparer class, declare the SalarySorter static property within the Employee class so that you can return an instance of the SalaryComparer class:

public class Employee : IComparable<Employee>
{
    private class SalaryComparer : IComparer<Employee>
    {
        public int Compare(Employee e1, Employee e2)
        {
            if (e1.Salary < e2.Salary)
                return −1;
            else if (e1.Salary == e2.Salary)
                return 0;
            else
                return 1;
        }
    }

    public static IComparer<Employee> SalarySorter
    {
        get { return new SalaryComparer(); }
    }

    public string FirstName
    { get; set; }

    public string LastName
    { get; set; }

    public int Salary
    { get; set; }

    public override string ToString()
    {
        return FirstName + ", " + LastName +
           " $" + Salary;
}
public int CompareTo(Employee emp)
    {
        return this.FirstName.CompareTo(emp.FirstName);
    }
}

You can now sort the Employee objects using the default, or specify the SalarySorter property:

employees.Sort(); //---sort using FirstName (default)---
      employees.Sort(Employee.SalarySorter); //---sort using Salary---

To allow the Employee objects to be sorted using the LastName property, you could define another class (say LastNameComparer) that implements the IComparer<T> interface and then declare the SalarySorter static property, like this:

public class Employee : IComparable<Employee>
{
    private class SalaryComparer : IComparer<Employee>
    {
        public int Compare(Employee e1, Employee e2)
        {
            if (e1.Salary < e2.Salary)
                return −1;
            else if (e1.Salary == e2.Salary)
                return 0;
            else
                return 1;
        }
    }

    private class LastNameComparer : IComparer<Employee>
    {
        public int Compare(Employee e1, Employee e2)
        {
            return e1.LastName.CompareTo(e2.LastName);
        }
    }

    public static IComparer<Employee> SalarySorter
    {
        get { return new SalaryComparer(); }
    }

    public static IComparer<Employee> LastNameSorter
    {
        get { return new LastNameComparer(); }
    }

    public string FirstName
    { get; set; }

    public string LastName
{ get; set; }

    public int Salary
    { get; set; }

    public override string ToString()
    {
        return FirstName + ", " + LastName +
            " $" + Salary;
    }

    public int CompareTo(Employee emp)
    {
        return this.FirstName.CompareTo(emp.FirstName);
    }
}

You can now sort by LastName using the LastNameSorter property:

employees.Sort(Employee.LastNameSorter); //---sort using LastName---

Dictionary

Most of you are familiar with the term dictionary — a reference book containing an alphabetical list of words with information about them. In computing, a dictionary object provides a mapping from a set of keys to a set of values. In .NET, this dictionary comes in the form of the Dictionary class (the generic equivalent is Dictionary<T,V>).

The following shows how you can create a new Dictionary object with type int to be used for the key and type String to be used for the values:

Dictionary<int, string> employees = new Dictionary<int, string>();

To add items into a Dictionary object, use the Add() method:

employees.Add(1001, "Margaret Anderson");
            employees.Add(1002, "Howard Mark");
            employees.Add(1003, "John Smith");
            employees.Add(1004, "Brian Will");

Trying to add a key that already exists in the object produces an ArgumentException error:

//---ArgumentException; duplicate key---
            employees.Add(1004, "Sculley Lawrence");

A safer way is to use the ContainsKey() method to check if the key exists before adding the new key:

if (!employees.ContainsKey(1005))
            {
                employees.Add(1005, "Sculley Lawrence");
            }

While having duplicate keys is not acceptable, you can have different keys with the same value:

employees.Add(1006, "Sculley Lawrence"); //---duplicate value is OK---

To retrieve items from the Dictionary object, simply specify the key:

Console.WriteLine(employees[1002].ToString()); //---Howard Mark---

When retrieving items from a Dictionary object, be certain that the key you specify is valid or you encounter a KeyNotFoundException error:

try
            {
                //---KeyNotFoundException---
                Console.WriteLine(employees[1005].ToString());
            }
            catch (KeyNotFoundException ex)
            {
                Console.WriteLine(ex.Message);
            }

Rather than catching an exception when the specified key is not found, it's more efficient to use the TryGetValue() method:

string Emp_Name;
            if (employees.TryGetValue(1005, out Emp_Name))
                Console.WriteLine(Emp_Name);

TryGetValue() takes in a key for the Dictionary object as well as an out parameter that will contain the associated value for the specified key. If the key specified does not exist in the Dictionary object, the out parameter (Emp_Name, in this case) contains the default value for the specified type (string in this case, hence the default value is null).

When you use the foreach statement on a Dictionary object to iterate over all the elements in it, each Dictionary object element is retrieved as a KeyValuePair object:

foreach (KeyValuePair<int, string> Emp in employees)
                Console.WriteLine("{0} - {1}", Emp.Key, Emp.Value);

Here's the output from these statements:

1001 - Margaret Anderson
1002 - Howard Mark
1003 - John Smith
1004 - Brian Will

To get all the keys in a Dictionary object, use the KeyCollection class:

//---get all the employee IDs---
            Dictionary<int, string>.KeyCollection
                EmployeeID = employees.Keys;

            foreach (int ID in EmployeeID)
                Console.WriteLine(ID);

These statements print out all the keys in the Dictionary object:

1001
1002
1003
1004

If you want all the employees' names, you can use the ValueCollection class, like this:

//---get all the employee names---
            Dictionary<int, string>.ValueCollection
                EmployeeNames = employees.Values;

            foreach (string emp in EmployeeNames)
                Console.WriteLine(emp);

You can also copy all the values in a Dictionary object into an array using the ToArray() method:

//---extract all the values in the Dictionary object
            // and copy into the array---
            string[] Names = employees.Values.ToArray();
            foreach (string n in Names)
                Console.WriteLine(n);

To remove a key from a Dictionary object, use the Remove() method, which takes the key to delete:

if (employees.ContainsKey(1006))
            {
                employees.Remove(1006);
            }

To sort the keys in a Dictionary object, use the SortedDictionary<K,V> class instead of the Dictionary<K,V> class:

SortedDictionary<int, string> employees =
        new SortedDictionary<int, string>();

Stacks

A stack is a last in, first out (LIFO) data structure — the last item added to a stack is the first to be removed. Conversely, the first item added to a stack is the last to be removed.

In .NET, you can use the Stack class (or the generic equivalent of Stack<T>) to represent a stack collection. The following statement creates an instance of the Stack class of type string:

Stack<string> tasks = new Stack<string>();

To add items into the stack, use the Push() method. The following statements push four strings into the tasks stack:

tasks.Push("Do homework"); //---this item will be at the bottom of the stack
       tasks.Push("Phone rings");
       tasks.Push("Get changed");
       tasks.Push("Go for movies"); //---this item will be at the top of the stack

To retrieve the elements from a stack, use either the Peek() method or the Pop() method. Peek() returns the object at the top of the stack without removing it. Pop() removes and returns the object at the top of the stack:

Console.WriteLine(tasks.Peek()); //---Go for movies---
            Console.WriteLine(tasks.Pop());  //---Go for movies---
            Console.WriteLine(tasks.Pop());  //---Get changed---
            Console.WriteLine(tasks.Pop());  //---Phone rings---
            Console.WriteLine(tasks.Pop());  //---Do homework---

If a stack is empty and you try to call the Pop() method, an InvalidOperationException error occurs. For that reason, it is useful to check the size of the stack by using the Count property before you perform a Pop() operation:

if (tasks.Count > 0)
                Console.WriteLine(tasks.Pop());
            else
                Console.WriteLine("Tasks is empty");

To extract all the objects within a Stack object without removing the elements, use a foreach statement, like this:

foreach (string t in tasks)
                Console.WriteLine(t);

Here's what prints out:

Go for movies
Get changed
Phone rings
Do homework

Queues

The queue is a first in, first out (FIFO) data structure. Unlike the stack, items are removed based on the sequence that they are added.

In .NET, you can use the Queue class (or the generic equivalent of Queue<T>) to represent a queue collection. The following statement creates an instance of the Queue class of type string:

Queue<string> tasks = new Queue<string>();

To add items into the queue, use the Enqueue() method. The following statement inserts four strings into the tasks queue:

tasks.Enqueue("Do homework");
            tasks.Enqueue("Phone rings");
            tasks.Enqueue("Get changed");
            tasks.Enqueue("Go for movies");

To retrieve the elements from a queue, you can use either the Peek() method or the Dequeue() method. Peek() returns the object at the beginning of the queue without removing it. Dequeue() removes and returns the object at the beginning of the queue:

Console.WriteLine(tasks.Peek());     //---Do homework---
            Console.WriteLine(tasks.Dequeue());  //---Do homework---
            Console.WriteLine(tasks.Dequeue());  //---Phone rings---
            Console.WriteLine(tasks.Dequeue());  //---Get changed---
            Console.WriteLine(tasks.Dequeue());  //---Go for movies---

If a queue is empty and you try to call the Dequeue() method, an InvalidOperationException error occurs, so it is useful to check the size of the queue using the Count property before you perform a dequeue operation:

if (tasks.Count > 0)
                Console.WriteLine(tasks.Dequeue());
            else
                Console.WriteLine("Tasks is empty");

To extract all the objects within a Queue object without removing the elements, use the foreach statement, like this:

foreach (string t in tasks)
                Console.WriteLine(t);

Here's what prints out:

Do homework
Phone rings
Get changed
Go for movies

Summary

This chapter explained how to manipulate data using arrays. In addition, it explored the System.Collections namespace, which contains the various interfaces that define basic collection functions. It also contains several useful data structures, such as a dictionary, stacks, and queues, that greatly simplify managing data in your application.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.220.191.247