Managed Extensions: Combining IEnumerable and IEnumerator

WEBINAR: On-demand webcast

How to Boost Database Development Productivity on Linux, Docker, and Kubernetes with Microsoft SQL Server 2017 REGISTER >

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.

Previous installments of the .NET Tips & Techniques series covered using types defined in the .NET Collection namespace to make classes both enumerable and sortable from Visual C++ and Managed Extensions applications. Each article employed the technique of creating two additional classes to the class being enumerated: a collection class (IEnumerable-derived) and an enumerator class (IEnumerator-derived). This article explains why the .NET BCL (Base Class Library) team chose to define two interfaces that are used together to achieve enumerability and how—in specific scenarios—you can define a single class that implements both interfaces.

Why Two Interfaces?

Why doesn't the IEnumerable interface define the Current, MoveNext, and Reset members, thereby eliminating the need for the IEnumerable::GetEnumerator method and the IEnumerator interface? That way, the collection class could simply provide public accessor methods to the data. Although this technique sounds reasonable at first blush, it has drawbacks.

One such drawback is evident if a single collection object can have multiple clients and its data can be modified (especially if records can be added or deleted) while the data is being enumerated. You could, of course, circumvent this particular problem by creating a "data object" internal to the collection object for each client. Then the problem would be associating each client with its respective data object—typically done through an attach/detach method pair. The client then would need to identify itself using some sort of ID (probably returned from an attach method) to retrieve its data.

As you can see, this solution heads down a slippery slope of diminishing returns, generating more work than it alleviates. This is not even mentioning the fact that you would forfeit the generic solution afforded you via the IEnumerable/IEnumerator interface pair. With these interfaces, the snapshot of the collection data that existed when the client requested the enumerator object is copied to the enumerator object and becomes available via the standard methods: Current, MoveNext, and Reset.

Combining the Two Interfaces

Having two distinct interfaces (IEnumerable and IEnumerator) eases the burden of keeping track of enumerators that represent snapshots of dynamic data. The logical question, then, is "what if the data is static?" In other words, why define an enumerator class that has its own copy of the original collection data if that data never changes? This is one scenario where combining the two interfaces into a single collection class would be advantageous. (You could also keep the collection and enumerator class definitions separate and simply not copy the data—instead having the enumerator object's Current, MoveNext, and Reset members directly access the collections data. However, why even define a separate enumerator class in situations where it has no benefit?)

Therefore, in the following class definitions, I've taken the Article class used in the past few articles and modified it to include a single class (ArticleCollection) that implements both the IEnumerable and IEnumerator interfaces:

#pragma once

#pragma push_macro("new")
#undef new

__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; }
};

__gc class ArticleCollection : public IEnumerable,
                               public IEnumerator
{
protected:
   ArrayList* articles;
public:
   ArticleCollection()
   {
      articles = new ArrayList();

      // create some sample data
      articles->Add(new Article(S"Article #1", 
                                S"Tom", 
                                S"Managed Extensions"));
      articles->Add(new Article(S"Article #2", 
                                S"Dick", 
                                S"Visual C++/MFC"));
      articles->Add(new Article(S"Article #3", 
                                S"Harry", 
                                S"C#"));
   }

public:
   IEnumerator* GetEnumerator()
   {
      Reset();
      return this;
   }

protected:
   int position;

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

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

      return articles->Item[position];
   }

   void Reset() { position = -1; }
};
#pragma pop_macro("new")

Here's a list of the main changes to the technique for enumerating your classes covered in the article, "Managed Extensions: Adding Enumeration to Your Classes:"

  • Aside from the class you want enumerable, the only class you need to add is a collection class that implements (derives from) both IEnumerable and IEnumerator. In this example, this leaves you with the Article and ArticleCollection classes.
  • Because the collection class implements both interfaces, the GetEnumerator method now can simply return the current object instance instead of instantiating a separate enumerator object, which would involve the unnecessary copying of the collection object's data.
  • The Reset, MoveNext, and get_Current methods are now in the collection class.


About the Author

Tom Archer - MSFT

I am a Program Manager and Content Strategist for the Microsoft MSDN Online team managing the Windows Vista and Visual C++ developer centers. Before being employed at Microsoft, I was awarded MVP status for the Visual C++ product. A 20+ year veteran of programming with various languages - C++, C, Assembler, RPG III/400, PL/I, etc. - I've also written many technical books (Inside C#, Extending MFC Applications with the .NET Framework, Visual C++.NET Bible, etc.) and 100+ online articles.

Comments

  • Breaks independence of enumerators

    Posted by shammah on 10/24/2006 02:53pm

    Doesn't this break the conceptual independence of enumerators. e.g., if I have two enumerators from ArticleCollection::GetEnumerator, then calling Reset() or MoveNext() on one would actually change the other!
    
    ArticleCollection ac;
    foreach(Article a in ac)
    {
      foreach(Article b in ac)
      {
        // Do something with the pair (a, b)
      }
    }

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Today's trend towards digital transformation has produced a shift from monolithic, purpose-built, network packet brokers (NPB) to software-driven, off-the-shelf hardware platforms based on merchant silicon. Software-driven packet flow visibility solutions are more flexible, scalable, and cost-effective to deploy, and, ultimately, also enable software innovation. This white paper describes how the latest generation of multicore processor x86 server platforms can further reduce the cost and increase the …

  • Chuze Fitness is a fast-growing fitness chain with over 21 locations spanning California, Arizona and Colorado. Chief information and marketing officer, Kris Peterson, explains why access to fast and reliable Wi-Fi is a "must have" service at their gyms and why they switched to Ruckus Cloud Wi-Fi. Chuze Fitness needed to provide a good user experience to the hundreds of guests streaming music, podcasts and videos as they worked out. They also needed to adequately cover their sprawling 20-40,000 square foot …

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date