Managed Extensions: Adding Enumeration to Your Classes

Welcome to this week’s installment of .NET Tips & Techniques! Each week, award-winning Architect and Lead Programmer Tom Archer demonstrates how to perform a practical .NET programming task using either C# or Managed C++ Extensions.

In addition to providing thousands of classes and types that enable us to create richer and more robust class hierarchies, the .NET BCL (Base Class Library) also defines many interfaces that enable the class writer to incorporate powerful features simply by implementing a given interface and its members. This concept, called interface-based programming, creates a contract between the implementing class and its clients, such that the client can be assured of what to expect from the class. The BCL even provides special operators and methods, such that client code can verify that a given interface is implemented before attempting to use it.

As an example, say that you define a class that implements an interface called ISomeInterface. Using Visual C++ and Managed Extensions, the client could then use the __typeof operator and Type::GetInterface method to verify that the ISomeInterface interface is implemented:


Type* t = __typeof(myVar);
if (t->GetInterface(S”ISomeInterface”))
{

If the client is a C# application, this same verification would look like the following—where the C# is operator is used:


if (myVar is IEnumerable)
{

Having said that, one set of interfaces that you’ll find quite useful is the IEnumerable and IEnumerator interfaces. These interfaces enable you to quickly and easily define your classes, such that client code can enumerate them in a type-safe and object-specific manner. This article presents step-by-step instructions for performing this task.

Implementing the IEnumerable and IEnumerator Interfaces

As a demo project, say you have a class called Article that contains some basic fields, such as title, author, and category:


__gc class Article
{
public:
Article(String* title, String* author, String* category)
{
this->title = title;
this->author = author;
this->category = category;
}

protected:
String* title;
String* author;
String* category;

public:
__property String* get_Title() { return this->title; }
__property String* get_Author() { return this->author; }
__property String* get_Category() { return this->category; }
};

In order to provide enumeration for a collection of Article objects, you could perform the following steps:

Please note that in order to make the following steps more clear in terms of where to insert code, I’ve duplicated code in some cases. For that reason, any step that includes previously shown code will display the new code to be inserted in bold.


  1. Define a class that will act as the collection class for the main class that you wish to be enumerable (Article, in this example). Derive it from the IEnumerable interface, which lets clients know that the class can be enumerated:

  2. __gc class Article
    {

    };

    __gc class ArticleCollection : public IEnumerable
    {
    };

  3. Add a member variable that will contain the collection of objects that can be enumerated. For example, the ArticleCollection variable holds a collection of Article objects, so you need to decide in which type of member to hold those objects. This example uses an ArrayList type:

  4. __gc class ArticleCollection : public IEnumerable
    {
    protected:
    ArrayList* articles;

  5. The IEnumerable interface defines the GetEnumerator method, returns the enumerator object that the client code uses to enumerate the collection. Therefore, implement a GetEnumerator method. In the following GetEnumerator method implementation, the code instantiates a new enumerator object and returns that object casted to the base IEnumerator type:

  6. __gc class ArticleCollection : public IEnumerable
    {

    public:
    IEnumerator* GetEnumerator()
    {
    return dynamic_cast<IEnumerator*>(
    new ArticleEnumerator(this));
    }

    public:
    __gc class ArticleEnumerator : public IEnumerator
    {
    };


  7. Now, you need to define the enumerator class’s constructor. As you can see from the previous step, the enumerator class is instantiated by passing the current collection instance object to its constructor. This is done because one collection object might be enumerated by multiple clients. Therefore, not only might the data be different for each client (depending on when that client requests the enumerator object), but each client will be positioned within the collection (via the enumerator object) at different positions.


    Therefore, the enumerator class contains an internal parent collection object as well as a position member to keep track of where the client is when enumerating the collection. Here’s how you would define and initialize these:


    __gc class ArticleEnumerator : public IEnumerator
    {
    protected:
    ArticleCollection* collection;
    int position;

    public:
    ArticleEnumerator(ArticleCollection* collection)
    {
    this->collection = collection;
    position = -1;
    }


  8. At this point, you have the basic framework for an enumerable collection. All that is left is implementing the IEnumerator methods: MoveNext, Current, and Reset.

    • The MoveNext method simply increments the current position value. As it is standard practice to initialize the current position to -1, the client should always call this method first (before attempting to use the Current property).

    • Use the Current property (get_Current C++ function) to retrieve the element associated with the current position value. This property must first ensure that the current position is valid. If it is not—such as if it is less than 0 (indicating the first element) or greater than the number of elements—it is customary to throw an exception of type InvalidOperationException. The exact mechanics of how to return the current object are going to be specific to how the objects are stored within the collection. In the next step, you’ll see an example of how to return an element from an ArrayList type.

    • Use the Reset method to simply reset the current position to its initialized value of -1:

    • __gc class ArticleEnumerator : public IEnumerator
      {

      public:
      bool MoveNext()
      {
      position++;
      return (position < collection->articles->Count);
      }

      __property Object* get_Current()
      {
      if (0 > position
      || position >= collection->articles->Count)
      throw new InvalidOperationException();

      return collection->articles->Item[position];
      }

      void Reset()
      {
      position = -1;
      }


The Client Side

Now that the Article class is enumerable, look at how client code enumerates a collection of Article objects. Assuming you instantiated and filled an object of type ArticleCollection, you could code a C++ client as follows:


Type* t = __typeof(ArticleCollection);
if (t->GetInterface(S”IEnumerable”))
{
// m_articles is a class-level object of type ArticleCollection
IEnumerator* en = m_articles->GetEnumerator();
while (en->MoveNext())
{
Article* article = dynamic_cast<ARTICLE*>(en->Current);
// Access the article object members as needed
}
}

In C#, the client would look like the following:


if (m_articles is IEnumerable)
{
foreach(Article article in m_articles)
{

}
}

As you can see, C# supports a couple of keywords that reduce the typing a bit, but they both work basically the same.

C# Clients and the foreach Operator

While this article specifically covers how to provide enumeration for your classes using Managed Extensions, you should always take care to accommodate the idiosyncrasies of other .NET languages&#mdash;especially the more popular languages. To that extent, C# programmers expect to be able to use the foreach operator in enumerating collections. This operator can be used only on expressions that are collection types. As defined in the .NET documentation, a collection type is one that either implements the IEnumerable interface (as you’ve done here) or implements the collection pattern, which is defined as follows:


  • The expression variable must contain a public instance method named GetEnumerator that takes no parameters and returns a struct, class, or interface.

  • The type returned from the GetEnumerator method must contain a public instance method named MoveNext that takes no parameters and returns a bool.

  • The type returned from the GetEnumerator method must contain a public instance method named Current that takes no parameters and returns a reference type.

Looking Ahead

In the next few articles, I’ll continue this theme on enumerable collections by covering topics such as adding sorting capabilities to your collections, versioning your enumerators, and fine-tuning your enumerators for better performance.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read