Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Daniel Solis - Illustrated C# 2010 - 2010.pdf
Скачиваний:
16
Добавлен:
11.06.2015
Размер:
11.23 Mб
Скачать

CHAPTER 20 ENUMERATORS AND ITERATORS

The Noninterface Enumerator

You’ve just seen how to use the IEnumerable and IEnumerator interfaces to create useful enumerables and enumerators. But there are several drawbacks to this method.

First, remember that the object returned by Current is of type object. For value types, this means that before they are returned by Current, they must be boxed to turn them into objects. They must then be unboxed again after they have been received from Current. This can exact a substantial performance penalty if it needs to be done on large amounts of data.

Another drawback of the nongeneric interface method is that you’ve lost type safety. The values being enumerated are being handled as objects and so can be of any type. This eliminates the safety of compile-time type checking.

You can solve these problems by making the following changes to the enumerator/enumerable class declarations.

For the enumerator class

Do not derive the class from IEnumerator.

Implement MoveNext just as before.

Implement Current just as before but have as its return type the type of the items being enumerated.

You do not have to implement Reset.

For the enumerable class

Do not derive the class from IEnumerable.

Implement GetEnumerator as before, but have its return type be the type of the enumerator class.

516

CHAPTER 20 ENUMERATORS AND ITERATORS

Figure 20-5 shows the differences. The nongeneric interface code is on the left, and the noninterface code is on the right. With these changes, the foreach statement will be perfectly happy to process your collection, but without the drawbacks just listed.

Figure 20-5. Comparing interface-based and non-interface-based enumerators

One possible problem with the noninterface enumerator implementation is that types from other assemblies might expect enumeration to be implemented using the interface method. If these objects attempt to get an enumeration of your class objects using the interface conventions, they won't be able to find them.

To solve this problem, you can implement both forms in the same classes. That is, you can create implementations for Current, MoveNext, Reset, and GetEnumerator at the class level and also create explicit interface implementations for them. With both sets of implementations, the type-safe, more efficient implementation will be called by foreach and other constructs that can use the noninterface implementations, while the other constructs will call the explicit interface implementations. An even better way, however, is to use the generic forms, which I describe next.

517

CHAPTER 20 ENUMERATORS AND ITERATORS

The Generic Enumeration Interfaces

The third form of enumerator uses the generic interfaces IEnumerable<T> and IEnumerator<T>. They are called generic because they use C# generics. Using them is very similar to using the nongeneric forms. Essentially, the differences between the two are the following:

With the nongeneric interface form

The GetEnumerator method of interface IEnumerable returns an enumerator class instance that implements IEnumerator.

The class implementing IEnumerator implements property Current, which returns a reference of type object, which you must then cast to the actual type of the object.

With the generic interface form

The GetEnumerator method of interface IEnumerable<T> returns an instance of a class that implements IEnumerator<T>.

The class implementing IEnumerator<T> implements property Current, which returns an instance of the actual type, rather than a reference to the base class object.

The most important point to notice, though, is that the nongeneric interface implementations are not type-safe. They return references to type object, which must then be cast to the actual types. With the generic interfaces, however, the enumerator is type-safe, returning references to the actual types. Of the three forms of enumerations, this is the one you should implement and use. The others are for legacy code developed before C# 2.0 when generics were introduced.

518

CHAPTER 20 ENUMERATORS AND ITERATORS

The IEnumerator<T> Interface

The IEnumerator<T> interface uses generics to return an actual derived type, rather than a reference to an object.

The IEnumerator<T> interface derives from two other interfaces: the nongeneric IEnumerator interface and the IDisposable interface. It must therefore implement their members.

You’ve already seen the nongeneric IEnumerator interface and its three members.

The IDisposable interface has a single, void, parameterless method called Dispose, which can be used to free unmanaged resources being held by the class. (The Dispose method was described in Chapter 6.)

The IEnumerator<T> interface itself has a single property, Current, which returns an instance of type T or derived from T—rather than a reference of type object.

Since both IEnumerator<T> and IEnumerator have a member named Current, you should explicitly implement the IEnumerator version and implement the generic version in the class itself, as shown in Figure 20-6.

Figure 20-6 illustrates the implementation of the interface.

Figure 20-6. Implementing the IEnumerator<T> interface

519

CHAPTER 20 ENUMERATORS AND ITERATORS

The declaration of the class implementing the interface should look something like the pattern in the following code, where T is the type returned by the enumerator:

using System.Collections;

using System.Collections.Generic;

class MyGenEnumerator: IEnumerator< T >

 

{

 

 

 

 

 

public

T Current { get {…} }

// IEnumerator<T>--Current

 

 

Explicit implementation

 

 

 

 

 

 

object

IEnumerator.Current { get { ... } }

// IEnumerator--Current

public

bool MoveNext()

{ ... }

 

// IEnumerator--MoveNext

public

void Reset()

{ ... }

 

// IEnumerator--Reset

public

void Dispose()

{ ... }

 

// IDisposable--Dispose

...

 

 

 

 

 

}

 

 

 

 

 

520

CHAPTER 20 ENUMERATORS AND ITERATORS

For example, the following code implements the ColorEnumerator example using the generic enumerator interface:

using

System.Collections;

using

System.Collections.Generic; Substitute type string for T

class ColorEnumerator : IEnumerator<string>

 

{

 

 

 

string[] Colors;

 

int Position = -1;

 

Returns the type argument type

 

 

 

public string Current

// Current--generic

{

 

 

 

get { return Colors[Position]; }

 

}

Explicit implementation

 

 

 

 

 

 

object IEnumerator.Current

// Current--nongeneric

{

 

 

 

get { return Colors[Position]; }

 

}

 

 

 

public bool MoveNext()

// MoveNext

{

 

 

 

if (Position < Colors.Length - 1)

 

{

 

 

 

Position++;

 

return true;

 

}

 

 

 

else

 

return false;

 

}

 

 

 

public void Reset()

// Reset

{ Position = -1; }

 

public void Dispose() { }

 

public ColorEnumerator(string[] colors)

// Constructor

{

 

 

 

Colors = new string[colors.Length];

 

for (int i = 0; i < colors.Length; i++)

 

Colors[i] = colors[i];

 

}

 

 

 

}

 

 

 

521

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]