Tải bản đầy đủ - 0 (trang)
Chapter 33: System.Array and the Collection Classes

Chapter 33: System.Array and the Collection Classes

Tải bản đầy đủ - 0trang

Chapter 33 ■ System.Array and the Collection Classes



Specifying a Sort Order

It is useful to be able to sort a list of instances. Doing this requires that the type define the IComparable

interface. In this case, I am using the generic List collection class:

public class Employee : IComparable

{

public Employee(string name, int id)

{

m_name = name;

m_id = id;

}

int IComparable.CompareTo(Employee emp2)

{

if (m_id > emp2.m_id)

{

return 1;

}

else if (m_id < emp2.m_id)

{

return −1;

}

else

{

return 0;

}

}

public override string ToString()

{

return String.Format("{0}:{1}", m_name, m_id);

}

string m_name;

int m_id;

}

class Test

{

public static void Main()

{

List employees = new List();

employees.Add(new Employee("George", 1));

employees.Add(new Employee("Fred", 2));

employees.Add(new Employee("Tom", 4));

employees.Add(new Employee("Bob", 3));

employees.Sort();

foreach (Employee employee in employees)

{

Console.WriteLine("Employee: {0}", employee);

}

// Find employee id 2 in the list;

Employee employeeToFind = new Employee(null, 2);



312

www.it-ebooks.info



Chapter 33 ■ SyStem.array and the ColleCtion ClaSSeS



int index = employees.BinarySearch(employeeToFind);

if (index != −1)

{

Console.WriteLine("Found: {0}", employees[index]);

}

}

}

This program gives the following output:

Employee: George:1

Employee: Fred:2

Employee: Bob:3

Employee: Tom:4

Found: Fred:2

This example used the BinarySearch() method to find an employee in the list. For this to work, the array

must be sorted, or the results will not be correct.

You can define only one ordering using IComparable; you could choose the employee ID or name, but

there’s no way to allow the user to choose which sort order they prefer.



Multiple Sort Orders

It is also possible to define multiple sort orders for a single class. Each sort order is expressed through the

IComparer interface, and the appropriate interface is passed to the sort or search function.

The IComparer interface can’t be implemented on Employee, however, because each class can implement

an interface only once, which would allow only a single sort order.1 A separate class is needed for each sort

order, with the class implementing IComparer. The class will be very simple, since all it will do is implement the

Compare() function.

class SortEmployeeByName : IComparer

{

public int Compare(Employee emp1, Employee emp2)

{

return String.Compare(emp1.Name, emp2.Name);

}

}

The Compare() member takes two objects as parameters, and the Compare() function built into string is

used for the comparison.

The sort is then performed with the following code:

employees.Sort(new SortEmployeeByName());



IComparable could implement one sort order and IComparer another, but that would be very confusing to the user.



1



313

www.it-ebooks.info



Chapter 33 ■ System.Array and the Collection Classes



Ad Hoc Sorting Orders

Creating a class that defines the sort order is useful if there are likely to be multiple uses of that sort order. If the

sort order isn’t going to be shared, it can more easily be expressed by creating a lambda expression that matches

the Comparison delegate.

employees.Sort((a, b) => String.Compare(a.Name, b.Name));

This is certainly easier than creating a separate class for the comparison method.



Overloading Relational Operators

If a class has an ordering that is expressed by implementing IComparable, it usually also makes sense to

overload the other relational operators. As with == and !=, other operators must be declared as pairs, with < and >

being one pair and >= and <= being the other pair.2

using System;

public class Employee: IComparable

{

public Employee(string name, int id)

{

m_name = name;

m_id = id;

}

int IComparable.CompareTo(Employee emp2)

{

if (m_id > emp2.m_id)

{

return 1;

}

else if (m_id < emp2.m_id)

{

return −1;

}

else

{

return 0;

}

}

public static bool operator <(

Employee emp1,

Employee emp2)

{

var icomp = (IComparable)emp1;

return icomp.CompareTo (emp2) < 0;

}



For many types, the >= and <= operators can be derived from the other ones, but in some cases it is desirable to define

them separately.



2



314

www.it-ebooks.info



Chapter 33 ■ System.Array and the Collection Classes



public static bool operator >(

Employee emp1,

Employee emp2)

{

var icomp = (IComparable)emp1;

return icomp.CompareTo (emp2) > 0;

}

public static bool operator <=(

Employee emp1,

Employee emp2)

{

var icomp = (IComparable)emp1;

return icomp.CompareTo (emp2) <= 0;

}

public static bool operator >=(

Employee emp1,

Employee emp2)

{

var icomp = (IComparable)emp1;

return icomp.CompareTo (emp2) >= 0;

}

public override string ToString()

{

return m_name + ":" + m_id;

}

string m_name;

int m_id;

}

class Test

{

public static void Main()

{

Employee george = new Employee("George", 1);

Employee fred = new Employee("Fred", 2);

Employee tom = new Employee("Tom", 4);

Employee bob = new Employee("Bob", 3);

Console.WriteLine("George < Fred: {0}", george < fred);

Console.WriteLine("Tom >= Bob: {0}", tom >= bob);

}

}

This example produces the following output:

George < Fred: trueTom >= Bob: true



315

www.it-ebooks.info



Chapter 33 ■ System.Array and the Collection Classes



Advanced Use of Hash Codes

In some situations, it may be desirable to define more than one hash code for a specific object. This could be

used, for example, to allow an Employee to be searched for based on the employee ID or on the employee name.

This is done by implementing the IHashCodeProvider interface to provide an alternate hash function, and it also

requires a matching implementation of IComparer. These new implementations are passed to the constructor of

the Dictionary.

class CompareEmployeeByName : IEqualityComparer

{

public bool Equals(Employee emp1, Employee emp2)

{

return String.Compare(emp1.Name, emp2.Name) == 0;

}

public int GetHashCode(Employee emp1)

{

return emp1.Name.GetHashCode();

}

}

class Test

{

public static void Main()

{

Employee herb = new Employee("Herb", 555);

Employee george = new Employee("George", 123);

Employee frank = new Employee("Frank", 111);

Dictionary employeeAddresses =

new Dictionary(new CompareEmployeeByName());

employeeAddresses.Add(herb, "414 Evergreen Terrace");

employeeAddresses.Add(george, "2335 Elm Street");

employeeAddresses.Add(frank, "18 Pine Bluff Road");

Employee herbClone = new Employee("Herb", 000);

string address = employeeAddresses[herbClone];

Console.WriteLine("{0} lives at {1}", herbClone, address);

}

}

This generates the following:

Herb:0 lives at 414 Evergreen Terrace

This technique should be used sparingly. It’s often simpler to expose a value, such as the employee name as

a property, and allow that to be used as a hash key instead.



Synchronized Collections

When a collection class—such as Dictionary—is created, it is not threadsafe, because adding synchronization to

such a class imposes some overhead. If a threadsafe version is needed, you can use the ConcurrentDictionary

class from the System.Collections.Concurrent namespace.



316

www.it-ebooks.info



Chapter 33 ■ System.Array and the Collection Classes



For more information on threading and synchronization, see Chapter 34.



Case-Insensitive Collections

To deal with strings in a case-insensitive manner, a collection can be created using a case-insensitive comparer.

new Dictionary(StringComparer.CurrentCultureIgnoreCase);



Collection Classes

Tables 33-1, 33-2 and 33-3 describe the collection classes provided by the .NET base class library.

Table 33-1.  System.Collections.Generic Collections



Class



Description



Dictionary



A collection of keys and values



HashSet



A set of values



KeyedByTypeCollection



A collection whose items are types that serve as keys



LinkedList



A doubly linked list



Queue



A first-in, first-out collection of items



SortedDictionary



A collection of keys and values sorted by the key



SortedList



A sorted list of key-value pairs



SortedSet



A collection of items maintained in sorted order



Stack



A last-in, first-out collection of items



SynchronizedCollection



A threadsafe collection of values



SynchronizedKeyedCollection



A threadsafe collection of values grouped by keys



SynchronizedReadOnlyCollection



A threadsafe, read-only collection of values



Table 33-2.  System.Collections.ObjectModel Collections



Class



Description



Collection



A base class for collections



KeyedCollection



Abstract base class for collections with keys in their values



ObservableCollection



A collection that provides notifications when it is changed



ReadOnlyCollection



Base class for a read-only wrapper around a collection



ReadOnlyObservableCollection



A read-only wrapper around an observable collection



317

www.it-ebooks.info



Chapter 33 ■ System.Array and the Collection Classes



Table 33-3.  System.Collections.Concurrent Collections



Class



Description



BlockingCollection



Helper for building threadsafe collections



ConcurrentBag



A threadsafe, unordered collection of objects



ConcurrentDictionary



A threadsafe dictionary



ConcurrentQueue



A threadsafe queue class



ConcurrentStack



A threadsafe stack class



ReadOnlyObservableCollection



A read-only observable collection



Design Guidelines

The intended use of an object should be considered when deciding which virtual functions and interfaces to

implement. Table 33-4 provides guidelines for this.

Table 33-4.  Interface and Virtual Method Uses



Object Use



Function or Interface



General



ToString()



Arrays or collections



Equals(), operator==(), operator!=(), GetHashCode()



Sorting or binary search



IComparable



Multiple sort orders



IComparer



Multiple hash lookups



IEqualityComparer



318

www.it-ebooks.info



Chapter 34



Threading

Modern computer operating systems allow a program to have multiple threads of execution at one time. At least,

they allow the appearance of having multiple things going on at the same time.1

It’s often useful to take advantage of this feature by allowing several operations to take place in parallel. This

can be used to prevent a program’s user interface from becoming unresponsive while a time-consuming task is

being performed, or it can be used to execute some other task while waiting for a blocking operation (an I/O, for

example) to complete.

The Common Language Runtime provides two different ways to perform such operations: through threading

and through asynchronous call mechanisms.

This is a complex topic, and the material in this chapter and the next is a starting point for real-world

applications. Large-scale multithreading is a complex and demanding topic and is covered in greater depth in

books like Pro .NET Performance by Sasha Goldshtein (Apress, 2012) and Pro .NET 4 Parallel Programming in C#

by Adam Freeman (Apress, 2010).



Data Protection and Synchronization

Performing more than one operation at once provides a valuable facility to a program, but it also provides

opportunities for error.



A Slightly Broken Example

Consider the following code:

using System;

class Val

{

int number = 1;

public void Bump()

{

int temp = number;

number = temp + 2;

Console.WriteLine("number = {0}", number);

}

Many desktop computers now have more than one CPU core, so they actually can do more than one thing at a time,

which makes this chapter more important than in the past.



1



319

www.it-ebooks.info



Chapter 34 ■ Threading



public override string ToString()

{

return(number.ToString());

}

public void DoBump()

{

for (int i = 0; i < 5; i++)

{

Bump();

}

}

}

class Test

{

public static void Main()

{

Val v = new Val();

v.DoBump();

}

}

In this example, the Val class holds a number and has a way to add 2 to it. When this program is run, it

generates the following output:

number

number

number

number

number



=

=

=

=

=



3

5

7

9

11



While that program is being executed, the operating system may be performing other tasks simultaneously.

The code can be interrupted at any spot in the code,2 but after the interruption, everything will be in the same

state as before, and there’s no way to know that the interruption took place.

Let’s modify the program to perform operations in parallel using threads.

using System;

using System.Threading;

class Val

{

int number = 1;

public void Bump()

{

int temp = number;

number = temp + 2;

Console.WriteLine("number = {0}", number);

}

2



Not quite any spot; the situations where it won’t be interrupted are covered later.



320

www.it-ebooks.info



Chapter 34 ■ Threading



public override string ToString()

{

return(number.ToString());

}

public void DoBump()

{

for (int i = 0; i < 5; i++)

{

Bump();

}

}

}

class Test

{

public static void Main()

{

Val v = new Val();

for (int threadNum = 0; threadNum < 5; threadNum++)

{

Thread thread = new Thread(new ThreadStart(v.DoBump));

thread.Start();

}

}

}

In this code, a ThreadStart delegate is created that refers to the function the thread should execute. When

this program is run, it generates the following output:

number

number

number

number

number

number

number

number

number

number

number

number

number

number

number

number

number

number

number



=

=

=

=

=

=

=

=

=

=

=

=

=

=

=

=

=

=

=



3

5

7

9

19

21

23

25

11

27

29

31

33

13

35

37

39

15

41



321

www.it-ebooks.info



Chapter 34 ■ Threading



number

number

number

number

number

number



=

=

=

=

=

=



43

45

17

47

49

51



Is this output correct? Well, DoBump() is called 25 times, and each time it adds 2 to the result, so the expected

result is 51. It does get a bit confused about when it is printing out the values, but the final result is correct.

This is a very common problem when writing multithreaded programs. The example has a latent error that

might show up in some situations, but it doesn’t show up when the example is run under normal conditions.

Bugs like this are some of the worst to find, because they usually show up only under stressful conditions.3

Let’s change the code to simulate an interruption by the operating system.

public void Bump()

{

int temp = number;

Thread.Sleep(1);

number = temp + 2;

Console.WriteLine("number = {0}", number);

}

This small change leads to the following output:

number

number

number

number

number

number

number

number

number

number

number

number

number

number

number

number

number

number

number

number

number

number

number

number

number



3



=

=

=

=

=

=

=

=

=

=

=

=

=

=

=

=

=

=

=

=

=

=

=

=

=



3

3

3

3

3

5

5

5

5

5

7

7

7

7

7

9

9

9

9

9

11

11

11

11

11



Such as at a customer’s site.



322

www.it-ebooks.info



Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Chapter 33: System.Array and the Collection Classes

Tải bản đầy đủ ngay(0 tr)

×