Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Visual CSharp 2005 Recipes (2006) [eng]

.pdf
Скачиваний:
48
Добавлен:
16.08.2013
Размер:
4.04 Mб
Скачать

468 C H A P T E R 1 3 C O M M O N LY U S E D I N T E R FA C E S A N D PAT T E R N S

To support a variety of sort orders for a particular type, you must implement separate helper types that implement the IComparer<T> interface, which defines the Compare method shown here.

int Compare(T x, T y);

These helper types must encapsulate the necessary logic to compare two objects and return a value based on the following logic:

If x is less than y, return less than zero (for example, –1).

If x has the same value as y, return zero.

If x is greater than y, return greater than zero (for example, 1).

The Code

The Newspaper class listed here demonstrates the implementation of both the IComparable and IComparer interfaces. The Newspaper.CompareTo method performs a case-insensitive comparison of two Newspaper objects based on their name fields. A private nested class named

AscendingCirculationComparer implements IComparer and compares two Newspaper objects based on their circulation fields. An AscendingCirculationComparer object is obtained using the static Newspaper.CirculationSorter property.

The Main method shown here demonstrates the comparison and sorting capabilities provided by implementing the IComparable and IComparer interfaces. The method creates a System.Collections. ArrayList collection containing five Newspaper objects. Main then sorts the ArrayList twice using the ArrayList.Sort method. The first Sort operation uses the default Newspaper comparison mechanism provided by the IComparable.CompareTo method. The second Sort operation uses an AscendingCirculationComparer object to perform comparisons through its implementation of the

IComparer.Compare method.

using System;

using System.Collections.Generic;

namespace Apress.VisualCSharpRecipes.Chapter13

{

public class Newspaper : IComparable<Newspaper>

{

private string name; private int circulation;

private class AscendingCirculationComparer : IComparer<Newspaper>

{

//Implementation of IComparer.Compare. The generic definition of

//IComparer allows us to ensure both arguments are Newspaper

//objects.

public int Compare(Newspaper x, Newspaper y)

{

//Handle logic for null reference as dictated by the

//IComparer interface. Null is considered less than

//any other value.

if (x == null && y == null) return 0; else if (x == null) return -1;

else if (y == null) return 1;

//Short-circuit condition where x and y are references

//to the same object.

if (x == y) return 0;

C H A P T E R 1 3 C O M M O N LY U S E D I N T E R FA C E S A N D PAT T E R N S

469

//Compare the circulation figures. IComparer dictates that:

//return less than zero if x < y

//return zero if x = y

//return greater than zero if x > y

//This logic is easily implemented using integer arithmetic. return x.circulation - y.circulation;

}

}

// Simple Newspaper constructor.

public Newspaper(string name, int circulation)

{

this.name = name; this.circulation = circulation;

}

//Declare a read-only property that returns an instance of the

//AscendingCirculationComparer.

public static IComparer<Newspaper> CirculationSorter

{

get { return new AscendingCirculationComparer(); }

}

//Override Object.ToString. public override string ToString()

{

return string.Format("{0}: Circulation = {1}", name, circulation);

}

//Implementation of IComparable.CompareTo. The generic definition

//of IComparable allows us to ensure that the argument provided

//must be a Newspaper object. Comparison is based on a

//case-insensitive comparison of the Newspaper names. public int CompareTo(Newspaper other)

{

//IComparable dictates that an object is always considered greater

//than null.

if (other == null) return 1;

//Short-circuit the case where the other Newspaper object is a

//reference to this one.

if (other == this) return 0;

//Calculate return value by performing a case-insensitive

//comparison of the Newspaper names.

//Because the Newspaper name is a string, the easiest approach

//is to rely on the comparison capabilities of the String

//class, which perform culture-sensitive string comparisons. return string.Compare(this.name, other.name, true);

}

}

// A class to demonstrate the use of Newspaper. Public class Recipe13_03

{

public static void Main()

470 C H A P T E R 1 3 C O M M O N LY U S E D I N T E R FA C E S A N D PAT T E R N S

{

List<Newspaper> newspapers = new List<Newspaper>();

newspapers.Add(new Newspaper("The Echo", 125780)); newspapers.Add(new Newspaper("The Times", 55230)); newspapers.Add(new Newspaper("The Gazette", 235950)); newspapers.Add(new Newspaper("The Sun", 88760)); newspapers.Add(new Newspaper("The Herald", 5670));

Console.Clear();

Console.WriteLine("Unsorted newspaper list:"); foreach (Newspaper n in newspapers)

{

Console.WriteLine(" " + n);

}

Console.WriteLine(Environment.NewLine); Console.WriteLine("Newspaper list sorted by name (default order):"); newspapers.Sort();

foreach (Newspaper n in newspapers)

{

Console.WriteLine(" " + n);

}

Console.WriteLine(Environment.NewLine); Console.WriteLine("Newspaper list sorted by circulation:"); newspapers.Sort(Newspaper.CirculationSorter);

foreach (Newspaper n in newspapers)

{

Console.WriteLine(" " + n);

}

// Wait to continue. Console.WriteLine(Environment.NewLine); Console.WriteLine("Main method complete. Press Enter"); Console.ReadLine();

}

}

}

Usage

Running the example will produce the results shown here. The first list of newspapers is unsorted, the second is sorted using the IComparable interface, and the third is sorted using a comparer class that implements IComparer.

Unsorted newspaper list:

The Echo: Circulation = 125780

The Times: Circulation = 55230

The Gazette: Circulation = 235950

The Sun: Circulation = 88760

The Herald: Circulation = 5670

Newspaper list sorted by name (default order):

The Echo: Circulation = 125780

The Gazette: Circulation = 235950

C H A P T E R 1 3 C O M M O N LY U S E D I N T E R FA C E S A N D PAT T E R N S

471

The Herald: Circulation = 5670

The Sun: Circulation = 88760

The Times: Circulation = 55230

Newspaper list sorted by circulation:

The Herald: Circulation = 5670

The Times: Circulation = 55230

The Sun: Circulation = 88760

The Echo: Circulation = 125780

The Gazette: Circulation = 235950

13-4. Implement an Enumerable Collection

Problem

You need to create a collection type whose contents you can enumerate using a foreach statement.

Solution

Implement the generic interface System.Collections.Generic.IEnumerable<T> on your collection type. The GetEnumerator method of the IEnumerable interface returns an enumerator, which is an object that implements the interface System.Collections.Generic.IEnumerator<T>. Within the GetEnumerator method, traverse the items in the collection using whatever logic is appropriate to your data structure and return the next value using the yield return statement. The C# compiler will automatically generate the necessary code to enable enumeration across the contents of your type.

Caution The IEnumerable and IEnumerator interfaces from the System.Collections.Generic namespace discussed in this recipe are new to .NET Framework 2.0. The interfaces from which these two interfaces inherit are also named IEnumerable and IEnumerator but are located in the System.Collections namespace. To implement an enumerable collection in .Net Framework 1.0 or 1.1, see recipe 13-5.

How It Works

A numeric indexer allows you to iterate through the elements of most standard collections using

a for loop. However, this technique does not always provide an appropriate abstraction for nonlinear data structures, such as trees and multidimensional collections. The foreach statement provides an easy-to-use and syntactically elegant mechanism for iterating through a collection of objects, regardless of their internal structures.

In order to support foreach semantics, the type containing the collection of objects should implement the IEnumerable<T> interface. The IEnumerable<T> interface declares a single method named GetEnumerator, which takes no arguments and returns an object that implements IEnumerator<T>. Prior to .NET Framework 2.0, you would need to implement a separate class that could correctly traverse the elements of the collection and maintain appropriate state to support the executing foreach loop. You would create an instance of this class and return it when the GetEnumerator method was called. In .NET Framework 2.0, you do not need to do this, as the C# compiler elegantly automates this relatively complex coding task through the use of the new yield return statement.

472 C H A P T E R 1 3 C O M M O N LY U S E D I N T E R FA C E S A N D PAT T E R N S

All you need to do in your GetEnumerator method is write the code necessary to iterate through the items in your collection using logic appropriate to the data structure. Each time you want to return an item, call the yield return statement and specify the value to return. The compiler generates code that returns the specified value and maintains appropriate state for the next time a value is requested. If you need to stop partway through the enumeration, call the yield break statement instead, and the enumeration will terminate as if it had reached the end of the collection.

Tip You do not actually need to explicitly implement IEnumerable on your type to make it enumerable. As long as it has a GetEnumerator method that returns an IEnumerator instance, the compiler will allow you to use the type in a foreach statement. However, it is always good practice to explicitly declare the capabilities of a type by declaring the interfaces it implements, as it allows users of your class to more easily understand its capabilities and purpose.

The GetEnumerator method is used automatically whenever you use an instance of your collection type in a foreach statement. However, if you want to provide multiple ways to enumerate the items in your collection, you can implement multiple methods or properties that are declared to return IEnumerable<T> instances. Within the body of the member, use the yield return statement just mentioned, and the C# compiler will generate the appropriate code automatically. To use one of the alternative enumerations from a foreach statement, you must directly reference the appropriate member, as in this example:

foreach (node n in Tree.BreadthFirst)

The Code

The following example demonstrates the creation of an enumerable collection using the IEnumerable<T> and IEnumerator<T> interfaces in conjunction with the yield return and yield break statements. The Team class, which represents a team of people, is a collection of enumerable TeamMember objects.

using System;

using System.Collections.Generic;

namespace Apress.VisualCSharpRecipes.Chapter13

{

// The TeamMember class represents an individual team member. public class TeamMember

{

public string Name; public string Title;

// Simple TeamMember constructor.

public TeamMember(string name, string title)

{

Name = name; Title = title;

}

// Returns a string representation of the TeamMember. public override string ToString()

{

return string.Format("{0} ({1})", Name, Title);

}

}

C H A P T E R 1 3 C O M M O N LY U S E D I N T E R FA C E S A N D PAT T E R N S

473

// Team class represents a collection of TeamMember objects. public class Team

{

// A List to contain the TeamMember objects.

private List<TeamMember> teamMembers = new List<TeamMember>();

//Implement the GetEnumerator method, which will support

//iteration across the entire team member List.

public IEnumerator<TeamMember> GetEnumerator()

{

foreach (TeamMember tm in teamMembers)

{

yield return tm;

}

}

//Implement the Reverse method, which will iterate through

//the team members in alphabetical order.

public IEnumerable<TeamMember> Reverse

{

get

{

for (int c = teamMembers.Count - 1; c >= 0; c--)

{

yield return teamMembers[c];

}

}

}

//Implement the FirstTwo method, which will stop the iteration

//after only the first two team members.

public IEnumerable<TeamMember> FirstTwo

{

get

{

int count = 0;

foreach (TeamMember tm in teamMembers)

{

if (count >= 2)

{

// Terminate the iterator. yield break;

}

else

{

// Return the TeamMember and maintain the iterator. count++;

yield return tm;

}

}

}

}

474 C H A P T E R 1 3 C O M M O N LY U S E D I N T E R FA C E S A N D PAT T E R N S

// Adds a TeamMember object to the Team. public void AddMember(TeamMember member)

{

teamMembers.Add(member);

}

}

// A class to demonstrate the use of Team. Public class Recipe13_04

{

public static void Main()

{

//Create and populate a new Team. Team team = new Team();

team.AddMember(new TeamMember("Curly", "Clown")); team.AddMember(new TeamMember("Nick", "Knife Thrower")); team.AddMember(new TeamMember("Nancy", "Strong Man"));

//Enumerate the entire Team using the default iterator. Console.Clear();

Console.WriteLine("Enumerate using default iterator:"); foreach (TeamMember member in team)

{

Console.WriteLine(" " + member.ToString());

}

//Enumerate the first 2 Team members only. Console.WriteLine(Environment.NewLine); Console.WriteLine("Enumerate using the FirstTwo iterator:"); foreach (TeamMember member in team.FirstTwo)

{

Console.WriteLine(" " + member.ToString());

}

//Enumerate the entire Team in reverse order. Console.WriteLine(Environment.NewLine); Console.WriteLine("Enumerate using the Reverse iterator:"); foreach (TeamMember member in team.Reverse)

{

Console.WriteLine(" " + member.ToString());

}

//Wait to continue. Console.WriteLine(Environment.NewLine); Console.WriteLine("Main method complete. Press Enter"); Console.ReadLine();

}

}

}

C H A P T E R 1 3 C O M M O N LY U S E D I N T E R FA C E S A N D PAT T E R N S

475

13-5. Implement an Enumerable Type Using

a Custom Iterator

Problem

You need to create an enumerable type but do not want to rely on the built-in iterator support provided by .NET Framework 2.0 (described in recipe 13-4).

Solution

Implement the interface System.Collections.IEnumerable on your collection type. The GetEnumerator method of the IEnumerable interface returns an enumerator, which is an object that implements the interface System.Collections.IEnumerator. The IEnumerator interface defines the methods used by the foreach statement to enumerate the collection.

Implement a private inner class within the enumerable type that implements the interface IEnumerator and can iterate over the enumerable type while maintaining appropriate state information. In the GetEnumerator method of the enumerable type, create and return an instance of the iterator class.

How It Works

The automatic iterator support built into C# 2.0 is very powerful and will be sufficient in the majority of cases. However, in some cases, you may want to take direct control of the implementation of your collection’s iterators. For example, you may want an iterator that supports changes to the underlying collection during enumeration.

Whatever your reason, the basic model of an enumerable collection is the same as that described in recipe 13-4. Your enumerable type should implement the IEnumerable interface, which requires you to implement a method named GetEnumerator. However, instead of using the yield return statement in GetEnumerator, you must instantiate and return an object that implements the IEnumerator interface. The IEnumerator interface provides a read-only, forward-only cursor for accessing the members of the underlying collection. Table 13-2 describes the members of the IEnumerator interface. The IEnumerator instance returned by GetEnumerator is your custom iterator—the object that actually supports enumeration of the collection’s data elements.

Table 13-2. Members of the IEnumerator Interface

Member

Description

Current

Property that returns the current data element. When the enumerator is created,

 

Current refers to a position preceding the first data element. This means you must

 

call MoveNext before using Current. If Current is called and the enumerator is

 

positioned before the first element or after the last element in the data collection,

 

Current must throw a System.InvalidOperationException.

MoveNext

Method that moves the enumerator to the next data element in the collection.

 

Returns true if there are more elements; otherwise, it returns false. If the

 

underlying source of data changes during the life of the enumerator, MoveNext must

 

throw an InvalidOperationException.

Reset

Method that moves the enumerator to a position preceding the first element in the

 

data collection. If the underlying source of data changes during the life of the

 

enumerator, Reset must throw an InvalidOperationException.

 

 

476 C H A P T E R 1 3 C O M M O N LY U S E D I N T E R FA C E S A N D PAT T E R N S

If your collection class contains different types of data that you want to enumerate separately, implementing the IEnumerable interface on the collection class is insufficient. In this case, you would implement a number of properties that returned different IEnumerator instances.

The Code

The TeamMember, Team, and TeamMemberEnumerator classes in the following example demonstrate the implementation of a custom iterator using the IEnumerable and IEnumerator interfaces. The TeamMember class represents a member of a team. The Team class, which represents a team of people, is a collection of TeamMember objects. Team implements the IEnumerable interface and declares a separate class, named TeamMemberEnumerator, to provide enumeration functionality. Team implements the Observer pattern using delegate and event members to notify all TeamMemberEnumerator objects if their underlying Team changes. (See recipe 13-11 for a detailed description of the Observer pattern.) The TeamMemberEnumerator class is a private nested class, so you cannot create instances of it other than through the Team.GetEnumerator method.

using System;

using System.Collections;

namespace Apress.VisualCSharpRecipes.Chapter13

{

// TeamMember class represents an individual team member. public class TeamMember

{

public string Name; public string Title;

// Simple TeamMember constructor.

public TeamMember(string name, string title)

{

Name = name; Title = title;

}

// Returns a string representation of the TeamMember. public override string ToString()

{

return string.Format("{0} ({1})", Name, Title);

}

}

//Team class represents a collection of TeamMember objects. Implements

//the IEnumerable interface to support enumerating TeamMember objects. public class Team : IEnumerable

{

//TeamMemberEnumerator is a private nested class that provides

//the functionality to enumerate the TeamMembers contained in

//a Team collection. As a nested class, TeamMemberEnumerator

//has access to the private members of the Team class.

private class TeamMemberEnumerator : IEnumerator

{

//The Team that this object is enumerating. private Team sourceTeam;

//Boolean to indicate whether underlying Team has changed

//and so is invalid for further enumeration.

private bool teamInvalid = false;

C H A P T E R 1 3 C O M M O N LY U S E D I N T E R FA C E S A N D PAT T E R N S

477

//Integer to identify the current TeamMember. Provides

//the index of the TeamMember in the underlying ArrayList

//used by the Team collection. Initialize to -1, which is

//the index prior to the first element.

private int currentMember = -1;

//Constructor takes a reference to the Team that is the source

//of enumerated data.

internal TeamMemberEnumerator(Team team)

{

this.sourceTeam = team;

// Register with sourceTeam for change notifications. sourceTeam.TeamChange +=

new TeamChangedEventHandler(this.TeamChange);

}

// Implement the IEnumerator.Current property. public object Current

{

get

{

//If the TeamMemberEnumerator is positioned before

//the first element or after the last element, then

//throw an exception.

if (currentMember == -1 ||

currentMember > (sourceTeam.teamMembers.Count - 1))

{

throw new InvalidOperationException();

}

//Otherwise, return the current TeamMember. return sourceTeam.teamMembers[currentMember];

}

}

// Implement the IEnumerator.MoveNext method. public bool MoveNext()

{

//If underlying Team is invalid, throw exception. if (teamInvalid)

{

throw new InvalidOperationException("Team modified");

}

//Otherwise, progress to the next TeamMember. currentMember++;

//Return false if we have moved past the last TeamMember. if (currentMember > (sourceTeam.teamMembers.Count - 1))

{

return false;

}

Соседние файлы в предмете Программирование на C++