Iterating Through a Collection
Iterating Through a Collection
Enumerating a Collection
Introduction to the Collections Namespaces
A collection is a group of values. To create these values, you can use a collection class. you have various options.
To use a collection, you can create a collection class. To assist you in creating and managing a collection of values or objects, the .NET Framework provides a namespace named System.Collections. The main goal of that namespace is to provide all the routine operations performed on a list of items, whether a list of primitive values or a collection of composite objects.
The System.Collections namespace contains classes used to create a collection for a specific object. To allow you to create a collection for any type of object, the .NET Framework provides another namespace named System.Collections.Generics. This is a large namespace with many interfaces and various collection classes. Of course, to use a class from this namespace, if you are writing your code in a class, you either should (must) qualify the name of its class or add the namespace in the top section of the document. This can be done as follows:
using System.Collections.Generic;
public class Fundamentalist
{
}
Introduction to Collections Interfaces
To assist you to create collections, both the System.Collections and the System.Collections.Generics namespaces include many interfaces. One of the most regular ways to check the members of a collection consists of visiting each and taking the appropriate action.
To support the ability to visit each item of a collection, the .NET Framework provides some mechanism through two interfaces named IEnumerator and IEnumerable.
Introduction to the IEnumerator Interface
The IEnumerator<> (generic) interface provides the means of identifying the class that holds a sample of the items that will be enumerated. The IEnumerator<> interface inherits from the IEnumerator (non-generic) interface and the IDisposable (non-generic) interfaces. This means that you should include the System.Collections.Generic namespace in the top section of the document where you intend to use these interfaces. Here is an example:
using System.Collections.Generic;
To use the functionality of the IEnumerator interface, you must create a class that implements it. You can start the class as follows:
using System.Collections.Generic;
public class Enumerator<T> : IEnumerator<T>
{
}
If your collection is an array-based list, you can start by declaring the base array in the class: Here is an example:
using System.Collections.Generic;
public class Enumerator<T> : IEnumerator<T>
{
private T[] objects;
}
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 System.Collections.Generic;
public class Enumerator<T> : IEnumerator<T>
{
private T[] objects;
public Enumerator(T[] 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:
using System.Collections.Generic;
public class Enumerator<T> : IEnumerator<T>
{
private T[] objects;
public Enumerator(T[] list)
{
this.objects = list;
}
}
The Current Item of an Enumeration
When creating a list, you should have a type of tag, as a field, that allows you to monitor the item that is being currently accessed or used in the list. 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 member.
To implement the Current property, you can define its get accessor to return the item at the current position. This can be done as follows:
using System.Collections.Generic; public class Enumerator<T> : IEnumerator<T> { private T[] objects; private int cur; . . . public T Current { get { return this.objects[cur]; } } }
Besides its own version, the IEnumerator<> (generic) interface inherits another version of the Current property from its parent and you must implement it too. The Current property of the IEnumerator interface is of type object. You can write its code like that of the previous version. Because both properties use the exact same name, to distinguish them, you should (must) qualify the version of the IEnumerator interface. Here is an example:
using System.Collections.Generic;
public class Enumerator<T> : IEnumerator<T>
{
. . .
object System.Collections.IEnumerator.Current
{
get
{
return this.objects[cur];
}
}
}
Resetting the Tag of the Current Item
Although you should be able to identify the current item at any time, when your 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:
using System.Collections.Generic;
public class Enumerator<T> : IEnumerator<T>
{
private int cur;
private T[] objects;
public Enumerator(T[] list)
{
objects = list;
cur = -1;
}
. . .
}
While the collection is being used, at one time, 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 that the IEnumerator<> interface inherits. 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 System.Collections.Generic;
public class Enumerator<T> : IEnumerator<T>
{
. . .
public void 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.
Moving to the Next Item in the Enumerator
We know that, when using the items of a collection, one way you could locate one item from another is 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 inherits the MoveNext() method from its parent. The syntax in the IEnumerator<> interface 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 System.Collections.Generic;
public class Enumerator<T> : IEnumerator<T>
{
. . .
public bool MoveNext()
{
cur++;
if (cur < objects.Length)
return true;
else
return false;
}
}
Disposing of the Enumerator
In some cases, the object of a generic class consumes resources. These resources should be freed when the object is not used anymore. To assist you with cleaning memory, the IEnumerator<> interface implements the IDisposable interface. Remember that this interface has only one method, named Dispose. If you are creating a generic class that is not concerned with resources and would let the compiler take care of that, you can simply provide a body for this method. Here is an example
using System.Collections.Generic;
public class Enumerator<T> : IEnumerator<T>
{
private int cur;
private T[] objects;
public Enumerator(T[] list)
{
cur = -1;
objects = list;
}
public T Current
{
get
{
return objects[cur];
}
}
object System.Collections.IEnumerator.Current
{
get
{
return objects[cur];
}
}
public void Reset()
{
cur = -1;
}
public bool MoveNext()
{
cur++;
if (cur < objects.Length)
return true;
else
return false;
}
public void Dispose()
{
return;
}
}
On the other hand, if you anticipate that the resources used by your class will need to be deallocated, you must implement this method properly.
Introduction
The IEnumerator<> interface is used to set up a collection for enumeration. The IEnumerator<> class doesn't provide the functionality necessary to enumerate a collection. The next step is to implement another interface called IEnumerable.
While the IEnumerator<> (generic) interface is used to identify the class that holds each value that will be visited, the IEnumerable<> (generic) interface inherits from the IEnumerator (non-generic) interface that is used to communicate with the collection whose items will be enumerated. For this reason, when implementing the IEnumerable<> 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 interface is a collection. Here is an example:
using System.Collections.Generic;
public class Collection<T> : IEnumerable<T>
{
. . . No Change
}
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, we saw that you can create the field as follows:
public class Collection<T> : IEnumerable<T>
{
private T[] items;
}
Of course, you should initialize the field. Here is an example:
using System.Collections.Generic;
public class Collection<T> : IEnumerable<T>
{
private T[] items;
public Collection()
{
items = new T[10];
}
}
For a collection class, you shoud provide the routine operations such as getting the number of items in the collection. Here is an example:
using System.Collections.Generic; public class Collection<T> : IEnumerable<T> { private T[] items; private int size; public Collection() { size = 0; items = new T[10]; } public int Count { get { return size; } } }
Of course, you should also provide the ability to add items to the colloection. Here is an example:
using System.Collections.Generic;
public class Collection<T> : IEnumerable<T>
{
private T[] items;
private int size;
public Collection()
{
size = 0;
items = new T[10];
}
public int Count => size;
public void Add(T item)
{
items[size] = item;
size++;
}
}
Most importantly, you should (must) add an indexed property to the class, which we already learned how to do. Here is an example:
using System.Collections.Generic;
public class Collection<T> : IEnumerable<T>
{
private T[] items;
private int size;
public Collection()
{
size = 0;
items = new T[10];
}
public int Count
{
get
{
return size;
}
}
public void Add(T item)
{
items[size] = item;
size++;
}
public T this[int index]
{
get
{
return items[index];
}
set
{
items[index] = value;
size++;
}
}
}
For Each Member of an Enumeration
Introduction
To support the ability to enumerate the members of a collection, the IEnumerable<> interface is equipped with a method named GetEnumerator that you must implement. The IEnumerable<>.GetEnumerator() method returns an IEnumerator<> object. Its syntax is:
IEnumerator<T> 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:
public class Collection<T> : IEnumerable<T>
{
private T[] items;
. . . No Change
public IEnumerator<T> GetEnumerator()
{
return new Enumerator<T>(items);
}
}
Besides this version, the IEnumerable<> interface inherits another version of the GetEnumerator() method from IEnumerable. Its syntax is:
IEnumerator GetEnumerator();
The IEnumerator.GetEnumerator() method returns an IEnumerator (non-generic) value. Since these two methods have the same name, to distinguish them, you must qualify the version of the IEnumerator interface. Here is an example:
using System.Collections.Generic;
public class Collection<T> : IEnumerable<T>
{
private T[] items;
private int size;
public Collection()
{
size = 0;
items = new T[10];
}
public int Count
{
get
{
return size;
}
}
public void Add(T item)
{
items[size] = item;
size++;
}
public T this[int index]
{
get
{
return items[index];
}
set
{
items[index] = value;
size++;
}
}
public IEnumerator<T> GetEnumerator()
{
return new Enumerator<T>(items);
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return new Enumerator<T>(items);
}
}
To make it simple to enumerate the elements of a collection, the C# language provides an operator named foreach. In order to use it, the collection on which you want to apply it must implement the IEnumerator<> and the IEnumerable<> interfaces as we have introduced in the previous sections. To start, you must prepare the collection and its items for processing. To enumerate the collection, declare a variable based on the implementer of the IEnumerable<> interface and pass the collection to its constructor. Once this is done, you can use the foreach loop. The formula to use foreach is:
foreach(type in collection-variable) { statement(s) }
The loop starts with its keyword, followed by parentheses, and a delimited body. The type can be the class that represents the objects of the collection. Alternatively, you can use the var (or the dynamic) keyword. The collection-variable is the variable you used to create the collection. In the body of the loop, create the statement(s) you want to process. Here is an example:
using System.Collections; using static System.Console; using System.Collections.Generic; public class Enumerator<T> : IEnumerator<T> { private int cur; private T[] objects; public Enumerator(T[] list) { cur = -1; objects = list; } public T Current { get { return objects[cur]; } } object IEnumerator.Current { get { return objects[cur]; } } public bool MoveNext() { cur++; if (cur < objects.Length) return true; else return false; } public void Reset() { cur = -1; } protected virtual void Dispose(bool disposing) { } public void Dispose() { } } public class Collection<T> : IEnumerable<T> { private T[] items; private int size; public Collection() { size = 0; items = new T[5]; } public int Count { get { return size; } } public void Add(T item) { items[size] = item; size++; } public T this[int index] { get { return items[index]; } set { items[index] = value; size++; } } public IEnumerator<T> GetEnumerator() { return new Enumerator<T>(items); } IEnumerator IEnumerable.GetEnumerator() { return new Enumerator<T>(items); } } public class Exercise { public static int Main(string[] args) { Title = "Countries Statistics"; WriteLine("Countries Statistics"); Collection<string> territories = new Collection<string>(); territories.Add("Guam "); territories.Add("Puerto Rico"); territories.Add("United States Virgin Islands"); territories.Add("Northern Mariana Islands"); territories.Add("American Samoa"); WriteLine("====================="); WriteLine("Territories"); WriteLine("-----------------------------------------"); foreach (string strTerritory in territories) WriteLine("Territory: {0}", strTerritory); WriteLine("========================================="); return 10; } }
This would produce:
Countries Statistics ===================== Territories ----------------------------------------- Territory: Guam Territory: Puerto Rico Territory: United States Virgin Islands Territory: Northern Mariana Islands Territory: American Samoa ========================================= Press any key to continue . . .
In the above example, we created a collection of strings. In the same way, to create a collection of objects, pass the name of the class to the <> operator of the collection class.
The IEnumerable<> interface makes it possible to enumerate the members of a collection that implements this interface. In the previous sections, we went through some details of this exercise. Because this can be overwhelming sometimes, the C# language provides the yield keyword you can use. There are various ways you can use yield.
One way you can use yield consists of creating a method that returns an IEnumerator<> value. When implementing the method, use a while loop to get to each member of the collection. In the loop, use a yield return expression to return the value that is currently visited. Also make sure you can navigate from the current to the next item, and stop when you get to the last. After creating the class, you can use it to create a collection of objects.
Yielding An Enumerable
Another technique to use yield consists of creating a method that returns an IEnumerable<> object. In the body of the method, you can use a while loop to access each member of the collection and return it. Once again, you combine yield and return to indicate that the method is an iterator that returns a member of the collection. At the end of each loop, find a way to get to the next member unless you have gotten to the end of the collection. As done previously, you can use a local variable to take care of that (in the following example, that local variable is named counter).
After creating the method (the code of the collection class is provided in the next Practical Learning section), when using the collection, make sure you call the method.
You can create the method as static. In this case, you can pass the collection class as argument. When calling the method, use the name of the collection class to call the method and pass the variable as argument.
|
|||
Previous | Copyright © 2001-2023, FunctionX | Friday 22 October 2021 | Next |
|