Enumerating the Members of a Collection
|
Introduction to System Collections
|
In the previous lesson, we saw that, when creating a collection,
you should provide a method that allows you to retrieve a member of the
collection. You can list the members of an array or a collection
through a technique called an enumeration. Enumerating a collection consists of
visiting each member of the list, for any reason judged necessary. For example,
you can enumerate a collection to display a list of its members. You can
enumerate a collection when looking for a member that responds to a certain
criterion.
Besides, or instead of, a for loop, the .NET Framework
provides another and better support for enumeration. In the C++/CLI language, you can
enumerate a collection using the for each operator, but the collection
must be prepared for it: you cannot just use for each for any collection.
This support is provided through two main interfaces: public IEnumerator and IEnumerable.
These two interfaces are defined in the System::Collections namespace.
Therefore, if you intend to use them, you can include this namespace in your
source file.
- Start Microsoft Visual C++ and create a new CLR Empty Project named BethesdaCarRental1
- To create a new class, in the Solution Explorer, right-click BethesdaCarRental1 -> Add -> Class...
- In the Templates list, click C++ Class and click Add
- Set the Name to CCar and press Enter
- Change the Car.h header file as follows:
#pragma once using namespace System; public ref class CCar { public: String ^ TagNumber; String ^ Make; String ^ Model; short CarYear; int Mileage; String ^ Category; bool HasK7Player; bool HasCDPlayer; bool HasDVDPlayer; bool Available; CCar ^ Next; CCar(void); };
- Access the Car.cpp source file and implement the constructor as follows:
#include "Car.h" CCar::CCar() { TagNumber = L"000 000"; Make = L"Unknown"; Model = L"Unknown"; CarYear = 1960; Mileage = 0; Category = L"Unknown"; HasK7Player = false; HasCDPlayer = false; HasDVDPlayer = false; Available = false; }
- To create a new class, in the Solution Explorer, right-click BethesdaCarRental1 -> Add -> Class...
- In the Templates list, click C++ Class and click Add
- Set the Name to CInventory and click Finish
- Change the Inventory.h header file as follows:
#pragma once #include "Car.h" using namespace System; public ref class CInventory abstract { protected: int items; public: CInventory(void); property int Count { int get() { return items; } } virtual int Add(CCar ^ obj) = 0; virtual CCar ^ Get(int index) = 0; virtual bool Delete() = 0; };
- Access the Inventory.cpp source file and change it as follows:
#include "Inventory.h" CInventory::CInventory(void) { items = 0; }
- To create a new class, in the Class View, right-click BethesdaCarRental1 -> Add -> Class...
- In the Templates list, click C++ Class and click Add
- Set the Name to CCarInventory and click Finish
- Change the CarInventory.h header file as follows:
#pragma once #include "Inventory.h" #include "Car.h" using namespace System; public ref class CCarInventory : public CInventory { public: CCar ^ Head; CCar ^ Current; CCar ^ Inventory; virtual int Add(CCar ^ NewCar) override; virtual CCar ^ Get(int index) override; virtual bool Delete() override; CCarInventory(void); };
- Access the CarInventory.cpp source file and change it as follows:
#include "CarInventory.h" CCarInventory::CCarInventory(void) { Head = nullptr; } int CCarInventory::Add(CCar ^ NewCar) { CCar ^ Sample = gcnew CCar; Sample = NewCar; Sample->Next = Head; Head = Sample; return items++; } CCar ^ CCarInventory::Get(int index) { CCar ^ Current = Head; for(int i = Count - 1; i > index && Current != nullptr; i--) Current = Current->Next; return Current; } bool CCarInventory::Delete() { if (Head == nullptr) { Console::WriteLine(L"The inventory is empty"); return false; } CCar ^ Current; Current = Head->Next; Head->Next = Current->Next; items--; return true; }
- Save all
Introduction to the IEnumerator Interface
|
The IEnumerator interface provides the means of
identifying the class that holds a sample of the items that will be enumerated. This interface is equipped with one
property and two methods. To use the functionalities provided by the IEnumerator
interface, you must create a class that implements it. You can start the class as follows:
using namespace System; using namespace System::Collections; public ref class CEnumerator : public IEnumerator { };
If your collection is an
array-based list, you can start by declaring the base array in the class: Here
is an example:
public ref class CEnumerator : public IEnumerator
{
private:
array<double> ^ numbers;
};
If the collection is not array-based, you can declare a
variable for the class that would be enumerated.
The role of the enumerator is to act on a collection. For
this reason, the class should be prepared to receive an external collection.
This can be done by passing it to a constructor of the enumerator. Here is an
example:
using namespace System; using namespace System::Collections; public ref class CEnumerator : public IEnumerator { private: array<double> ^ numbers; public: CEnumerator(array<double> ^ list); }; CEnumerator::CEnumerator(array<double> ^ list) { }
The internal collection would be used in the enumerator
class. The external collection would be the source of the values of the list
that would be enumerated. For these reasons, you can/should initialize the
internal collection with the values of the external list. This can be done as
follows:
CEnumerator::CEnumerator(array<double> ^ list)
{
numbers = list;
}
|
- To create a new class, in the Class View, right-click BethesdaCarRental1 -> Add -> Class...
- In the Templates list, click C++ Class and click Add
- Set the Name of the class to CCarIdentifier and click Finish
- Change the file as follows:
#pragma once #include "CarInventory.h" using namespace System::Collections; public ref class CCarIdentifier : public IEnumerator { public: CCarInventory ^ counts; void Identify(CCarInventory ^ list); CCarIdentifier(void); };
- Access the CarIdentifier.cpp source file and change it as follows:
#include "CarIdentifier.h" CCarIdentifier::CCarIdentifier(void) { } void CCarIdentifier::Identify(CCarInventory ^ list) { counts = list; }
- Save all
The Current Item of an Enumeration
|
When introducing some techniques of
creating a list, we saw that you should have a type of tag, as a member variable, that allows you to
monitor the item that is being currently accessed or used in the list. This is
particularly valuable when visiting the members of the collection. The IEnumerator
interface provides a property that is used to identify the current member of the
list. This property is called Current. Because the current item is meant
to be viewed only, the Current property is a read-only one. Based on the rules
of abstract classes, remember that you must implement all members of an interface
in the class that is based on it.
To implement the Current property, you can implement
its get accessor to return the item at the current position. This can be
done as follows:
using namespace System::Collections;
public ref class CEnumerator : public IEnumerator
{
private:
array<double> ^ numbers;
int cur;
public:
CEnumerator(array<double> ^ list);
virtual property Object ^ Current
{
Object ^ get() { return numbers[cur]; }
}
};
|
- Access the CarIdentifier.h header file and implement the IEnumerator::Current
property as follows:
#pragma once #include "CarInventory.h" using namespace System; using namespace System::Collections; public ref class CCarIdentifier : public IEnumerator { private: int curPosition; public: CCarInventory ^ counts; void Identify(CCarInventory ^ list); property Object ^ Current { virtual Object ^ get() { try { return counts->Get(this->curPosition); } catch(IndexOutOfRangeException ^) { Console::WriteLine(L"The current item must be accessed " L"within the range of available items"); return nullptr; } } } CCarIdentifier(void); };
- Access the CarIdentifier.cpp source file and change the constructor as
follows:
CCarIdentifier::CCarIdentifier(void) { curPosition = -1; }
- Save the file
Resetting the Tag of the Current Item
|
Although you should be able to identify the current item at
any time, when the application starts, before the collection can be enumerated,
the tag that is used to monitor the current item should be set to a value before the
beginning of the count. This can be done by setting the tag to -1. Here is an
example:
CEnumerator::CEnumerator(array<double> ^ list)
{
numbers = list;
cur = -1;
}
While the collection is being used, at one moment you may want to
reset the tag of the current item to its original position. To support this
operation, the IEnumerator interface is equipped with a method named Reset.
Its syntax is:
void Reset();
When implementing this method, simply assign a non-existing
value, which is usually -1, to the monitoring tag of the current item. This can
be done as follows:
using namespace System::Collections;
public ref class CEnumerator : public IEnumerator
{
private:
array<double> ^ numbers;
int cur;
public:
CEnumerator(array<double> ^ list);
virtual property Object ^ Current
{
Object ^ get() { return numbers[cur]; }
}
virtual void Reset();
};
CEnumerator::CEnumerator(array<double> ^ list)
{
numbers = list;
cur = -1;
}
void CEnumerator::Reset()
{
cur = -1;
}
When using the implementer of the IEnumerator
interface, if you try accessing an item beyond the maximum number of items, the
compiler would throw an IndexOutOfRangeException exception. For this
reason, when anticipating a bad behavior, you should catch this exception when
implementing the Current property. Here is an example:
public ref class CEnumerator : public IEnumerator
{
private:
array<double> ^ numbers;
int cur;
public:
CEnumerator(array<double> ^ list);
virtual property Object ^ Current
{
Object ^ get()
{
try {
return numbers[cur];
}
catch(IndexOutOfRangeException ^)
{
Console::WriteLine(L"The current item must be accessed "
L"within the range of available items");
return nullptr;
}
}
}
};
|
- Access the CarIdentifier.h header file and declare the IEnumerator::Reset() method as follows:
. . . No Change public ref class CCarIdentifier : public IEnumerator { private: int curPosition; public: . . . No Change virtual void Reset(); CCarIdentifier(void); };
- Open the CarInventory.cpp source file and implement the Reset() method as
follows:
#include "CarIdentifier.h" . . . No Change void CCarIdentifier::Reset() { curPosition = -1; }
- Save all
Moving to the Next Item in the Enumerator
|
In the previous lesson, we saw that, when using the items of
a collection, one way you can locate one item from another is to be able to jump
from one item to the next. This operation is also very important when
enumerating a collection. To support this operation, the IEnumerator
interface is
quipped with the MoveNext() method. Its syntax is:
bool MoveNext();
When implementing this method, first increment the tag that
monitors the current item of the collection. After incrementing the tag, check
whether it is lower than the total number of items. If it is, return true.
Otherwise, return false. This can be done as follows:
using namespace System::Collections;
public ref class CEnumerator : public IEnumerator
{
private:
array<double> ^ numbers;
int cur;
public:
CEnumerator(array<double> ^ list);
virtual property Object ^ Current
{
Object ^ get()
{
try {
return numbers[cur];
}
catch(IndexOutOfRangeException ^)
{
Console::WriteLine(L"The current item must be accessed "
L"within the range of available items");
}
}
}
virtual void Reset();
virtual bool MoveNext();
};
CEnumerator::CEnumerator(array<double> ^ list)
{
numbers = list;
cur = -1;
}
void CEnumerator::Reset()
{
cur = -1;
}
bool CEnumerator::MoveNext()
{
cur++;
if( cur < numbers->Length )
return true;
else
return false;
}
|
- Access the CarIdentifier.h header file and declare the IEnumerator::MoveNext() method as follows:
#pragma once #include "CarInventory.h" using namespace System; using namespace System::Collections; public ref class CCarIdentifier : public IEnumerator { private: int curPosition; public: CCarInventory ^ counts; void Identify(CCarInventory ^ list); property Object ^ Current { virtual Object ^ get() { try { return counts->Get(this->curPosition); } catch(IndexOutOfRangeException ^) { Console::WriteLine(L"The current item must be accessed " L"within the range of available items"); return nullptr; } } } virtual void Reset(); virtual bool MoveNext(); CCarIdentifier(void); };
- Open the CarInventory.cpp source file and implement the Reset() method as
follows:
#include "CarIdentifier.h" CCarIdentifier::CCarIdentifier(void) { curPosition = -1; } void CCarIdentifier::Identify(CCarInventory ^ list) { counts = list; } void CCarIdentifier::Reset() { curPosition = -1; } bool CCarIdentifier::MoveNext() { curPosition++; if( curPosition < counts->Count ) return true; else return false; }
- Save all
Introduction
|
The IEnumerator interface is used to set up a
collection for enumeration. To use for each, you cannot use the IEnumerator
interface. Therefore, the next step is to implement
another interface called IEnumerable. While the IEnumerator
interface is used to identify the class that holds each value that will be
visited, the IEnumerable interface is used to communicate with the
collection whose items will be enumerated. For this reason, when implementing
this interface, you should provide the means of accessing the external collection.
This can be done by passing a collection of the class that holds the values, to
a constructor of the IEnumerable implementer.
Getting the Enumerator
|
To implement the IEnumerable interface, start by
deriving a class from it. While the class implemented by the IEnumerator
interface represents an object, the class that implements the IEnumerable
interface is a collection. Here is an example:
using namespace System::Collections; public ref class CEnumerable : public IEnumerable { };
The new class doesn't know what collection it
will be asked to enumerate. For this reason, in the new class, you should
declare a member variable of the class that holds the values that will be
enumerated. If the collection is array-based, you can create the member variable
as
follows:
public ref class CEnumerable : public IEnumerable
{
private:
array<double> ^ numbers;
};
Eventually, when instantiating the IEnumerable
implementer, you will need to pass it a collection of values. To make this
possible, you can create a method in the new class and pass that collection of
objects. Here is an example:
using namespace System::Collections;
public ref class CEnumerable : public IEnumerable
{
private:
array<double> ^ numbers;
public:
void Identify(array<double> ^ values);
};
In this method, you can assign the member variable to the
argument. You should also assign each member of the argument to its equivalent
of the member of the argument. This can be done as follows:
void CEnumerable::Identify(array<double> ^ values) { numbers = values; for(int i = 0; i < values->Length; i++) numbers[i] = values[i]; }
To support the use of the for each loop, the IEnumerable
interface is equipped with (only) a (one) method named GetEnumerator that you
must implement. The IEnumerable::GetEnumerator() method returns an IEnumerator
object:
public ref class CEnumerable : public IEnumerable
{
private:
array<double> ^ numbers;
void Identify(array<double> ^ values);
virtual IEnumerator ^ GetEnumerator();
};
When implementing this method, you can return an object of the class
that implements the IEnumerator interface, passing it the collection that was
declared in the IEnumerable implementer. This can be done as follows:
#include "Enumerable.h"
#include "Enumerator.h"
void CEnumerable::Identify(array<double> ^ values)
{
numbers = values;
for(int i = 0; i < values->Length; i++)
numbers[i] = values[i];
}
IEnumerator ^ CEnumerable::GetEnumerator()
{
return gcnew CEnumerator(numbers);
}
|
- To create a new class, in the Class View, right-click BethesdaCarRental1 -> Add -> Class...
- In the Templates list, click C++ Class and click Add
- Set the Name of the class to CCars and click Finish
- Change the Cars.h header file as follows:
#pragma once #include "CarInventory.h" #include "CarIdentifier.h" using namespace System::Collections; public ref class CCars : public IEnumerable { private: CCarInventory ^ items; public: void Locate(CCarInventory ^ list); virtual IEnumerator ^ GetEnumerator(); CCars(void); };
- Open the Cars.cpp source file and change it as follows:
#include "Cars.h" CCars::CCars(void) { } void CCars::Locate(CCarInventory ^ list) { items = gcnew CCarInventory(); for(int i = 0; i < list->Count; i++) items->Add(list->Get(i)); } IEnumerator ^ CCars::GetEnumerator() { CCarIdentifier ^ cid = gcnew CCarIdentifier; cid->Identify(items); return cid; }
- Save all
Using for each
|
After implementing the IEnumerator and the IEnumerable interfaces,
you can then use the for each loop. To start, you must prepare the collection and its
items for processing. Here is an example:
using namespace System;
int main()
{
array<double> ^ numbers = gcnew array<double>(5);
numbers[0] = 224.52;
numbers[1] = 60.48;
numbers[2] = 1250.64;
numbers[3] = 8.86;
numbers[4] = 1005.36;
return 0;
}
To enumerate the collection, declare a variable based on the
implementer of the IEnumerable and pass the collection to its
constructor.
Once this is done, you can then use the for each. Here is an example:
Header File: Enumerator.h |
using namespace System; using namespace System::Collections; public ref class CEnumerator : public IEnumerator { private: array<double> ^ numbers; int cur; public: CEnumerator(array<double> ^ list); virtual property Object ^ Current { Object ^ get() { try { return numbers[cur]; } catch(IndexOutOfRangeException ^) { Console::WriteLine(L"The current item must be accessed " L"within the range of available items"); return nullptr; } } } virtual void Reset(); virtual bool MoveNext(); }; |
Source File: Enumerator.cpp |
#include "Enumerator.h" CEnumerator::CEnumerator(array<double> ^ list) { numbers = list; cur = -1; } void CEnumerator::Reset() { cur = -1; } bool CEnumerator::MoveNext() { cur++; if( cur < numbers->Length ) return true; else return false; } |
Header File: Enumerable.h |
#pragma once using namespace System::Collections; public ref class CEnumerable : public IEnumerable { private: array<double> ^ numbers; public: CEnumerable(void); void Identify(array<double> ^ values); virtual IEnumerator ^ GetEnumerator(); }; |
Source File: Enumerable.cpp |
#include "Enumerable.h" #include "Enumerator.h" CEnumerable::CEnumerable(void) { } void CEnumerable::Identify(array<double> ^ values) { numbers = values; for(int i = 0; i < values->Length; i++) numbers[i] = values[i]; } IEnumerator ^ CEnumerable::GetEnumerator() { return gcnew CEnumerator(numbers); } |
Source File: Exercise.cpp |
using namespace System; #include "Enumerable.h" int main() { array<double> ^ numbers = gcnew array<double>(5); numbers[0] = 224.52; numbers[1] = 60.48; numbers[2] = 1250.64; numbers[3] = 8.86; numbers[4] = 1005.36; CEnumerable ^ coll = gcnew CEnumerable; coll->Identify(numbers); for each(double d in coll) Console::WriteLine(L"Item {0}", d); return 0; } |
This would produce:
Item 224.52 Item 60.48 Item 1250.64 Item 8.86 Item 1005.36 Press any key to continue . . .
|
- To create a new file, in the Solution Explorer, right-click BethesdaCarRental1 -> Add -> New Item...
- In the Templates list, click C++ File (.cpp)
- Set the Name to Exercise and click OK
- Implement the file as follows:
#include "CarInventory.h" #include "Car.h" #include "Cars.h" using namespace System; int main() { CCarInventory ^ cars = gcnew CCarInventory; CCar ^ nice; nice = gcnew CCar; nice->TagNumber = L"527 495"; nice->Make = L"Honda"; nice->Model = L"Civic"; nice->CarYear = 2006; nice->Mileage = 8631; nice->Category = L"Compact"; nice->HasK7Player = false; nice->HasCDPlayer = true; nice->HasDVDPlayer = false; nice->Available = true; cars->Add(nice); nice = gcnew CCar; nice->TagNumber = L"M838400"; nice->Make = L"Ford"; nice->Model = L"Expedition"; nice->CarYear = 2004; nice->Mileage = 48631; nice->Category = L"SUV"; nice->HasK7Player = false; nice->HasCDPlayer = true; nice->HasDVDPlayer = true; nice->Available = false; cars->Add(nice); nice = gcnew CCar; nice->TagNumber = L"LRT825"; nice->Make = L"Kia"; nice->Model = L"Rio"; nice->CarYear = 2007; nice->Mileage = 12504; nice->Category = L"Economy"; nice->HasK7Player = false; nice->HasCDPlayer = false; nice->HasDVDPlayer = false; nice->Available = false; cars->Add(nice); nice = gcnew CCar; nice->TagNumber = L"917035"; nice->Make = L"Toyota"; nice->Model = L"Camry"; nice->CarYear = 2006; nice->Mileage = 10664; nice->Category = L"Full Size"; nice->HasK7Player = true; nice->HasCDPlayer = true; nice->HasDVDPlayer = false; nice->Available = true; cars->Add(nice); CCars ^ collection = gcnew CCars; collection->Locate(cars); Console::WriteLine(L"=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+="); Console::WriteLine(L"Total: {0} Cars in company inventory", cars->Count); Console::WriteLine(L"=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+="); Console::WriteLine(L"Inventory Summary"); for each(CCar ^ car in collection) { Console::WriteLine(L"=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-="); Console::WriteLine(L"Car Information"); Console::WriteLine(L"-------------------------------"); Console::WriteLine(L"Tag #: {0}", car->TagNumber); Console::WriteLine(L"Make: {0}", car->Make); Console::WriteLine(L"Model: {0}", car->Model); Console::WriteLine(L"Year: {0}", car->CarYear); Console::WriteLine(L"Mileage: {0}", car->Mileage); Console::WriteLine(L"Category: {0}", car->Category); Console::WriteLine(L"K7 Plaher: {0}", car->HasK7Player); Console::WriteLine(L"CD Player: {0}", car->HasCDPlayer); Console::WriteLine(L"DVD Plaher: {0}", car->HasDVDPlayer); Console::WriteLine(L"Available: {0}", car->Available); } Console::WriteLine(L"=-=-=-=-=-=-=-=-=-=-=-=-=-=-="); return 0; }
- Execute the application to see the result:
=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+= Total: 4 Cars in company inventory =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+= Inventory Summary =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= Car Information ------------------------------- Tag #: 527 495 Make: Honda Model: Civic Year: 2006 Mileage: 8631 Category: Compact K7 Plaher: False CD Player: True DVD Plaher: False Available: True =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= Car Information ------------------------------- Tag #: M838400 Make: Ford Model: Expedition Year: 2004 Mileage: 48631 Category: SUV K7 Plaher: False CD Player: True DVD Plaher: True Available: False =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= Car Information ------------------------------- Tag #: LRT825 Make: Kia Model: Rio Year: 2007 Mileage: 12504 Category: Economy K7 Plaher: False CD Player: False DVD Plaher: False Available: False =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= Car Information ------------------------------- Tag #: 917035 Make: Toyota Model: Camry Year: 2006 Mileage: 10664 Category: Full Size K7 Plaher: True CD Player: True DVD Plaher: False Available: True =-=-=-=-=-=-=-=-=-=-=-=-=-=-= Press any key to continue . . .
- Close the DOS window
No comments:
Post a Comment