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 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
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
, andvalues
are objects of typeSystem.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.
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);
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
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);
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
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---
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.
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
A params
parameter must always be the last parameter defined in a method declaration.
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.
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 |
---|---|
| Enable you to loop through the elements in a collection. |
| Contains items in a collection and provides the functionality to copy elements to an array. Inherits from |
| Enable you to compare objects in a collection. |
| Inherits from |
| Similar to |
The ICollection<T>
interface is the base interface for classes in the System.Collections
namespace.
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);
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.
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);
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.
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
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);
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.
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 |
Zero | The current instance is equal to |
Greater than zero | The current instance is greater than |
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); }
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---
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>();
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
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
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.
18.220.191.247