Page 1 of 1

GetEnumerator Basics

#1 Robin19  Icon User is online

  • D.I.C Addict
  • member icon

Reputation: 256
  • View blog
  • Posts: 529
  • Joined: 07-July 10

Posted 22 September 2010 - 09:14 AM

Introduction
This tutorial is designed to assist you in making special collections wrappers. There are times you like using List, Array, or other collections. You want to do special operations on these collections, so you make a custom class. You don't want to expose the list to the public, forcing users to use the functions you provided. However, you can no longer enumerate through the list. You can write a foreach statement against your class, but you get an error about using IEnumerable. This tutorial will show you how to get around this problem.


Basic Set Up
It is almost lunch time as I write this, and I'm hungry. So I created a simple Food class. It contains just two fields and a simple constructor.
    public class Food
    {
        #region Fields
        public string Name;
        public int Calories;
        #endregion

        #region Constructors
        public Food(string Name, int Calories)
        {
            this.Name = Name;
            this.Calories = Calories;
        }
        #endregion
    }


I then created a container class called Meal. It holds a List of Food, along with a name. Again, just a simple constructor and the two fields.
    public class Meal
    {
        #region Fields
        public string Name;
        private List<Food> FoodList;
        #endregion

        #region Constructors
        public Meal(string Name, List<Food> FoodList)
        {
            this.Name = Name;
            this.FoodList = FoodList;
        }
        #endregion
    }


In our Main, let's create a List<Food> and a Meal and test it.
        static void Main(string[] args)
        {
            List<Food> FoodList = new List<Food>()
            {
                new Food("Salad", 90),
                new Food("Steak", 202),
                new Food("Potato", 157),
                new Food("Cake", 235)
            };

            Meal dinner = new Meal("Dinner", FoodList);

            Console.WriteLine("I was sitting, having {0} ...", dinner.Name);
            int calories = 0;
            foreach (Food item in dinner)
            {
                Console.WriteLine("\t{0} with {1} calories", item.Name, item.Calories);
                calories += item.Calories;
            }

            Console.WriteLine("I consumed {0} calories.  It's time to go work it off", calories);

            Console.ReadLine();
        }


The code refuses to run. foreach contains an error "foreach statement cannot operate on variables of type '*.Meal' because '*.Meal' does not contain a public definition for 'GetEnumerator'". This can be solved.


IEnumerator
Go back to the Meal class. The Meal class needs to inhierit from IEnumerable. You will need the using System.Collections; declaration.

It needs a public definition for GetEnumerator. This is where it gets tricky. We will create two definitions for GetEnumerator. The first one will not have an access modifier on it.
IEnumerator IEnumerable.GetEnumerator()

So it needs to return an IEnumerator. What do we return? The solution is to create your own IEnumerator. We can call this class MealEnumerator, so everyone knows what it's used for. MealEnumerator also needs to declare using System.Collections;. MealEnumerator inherits from IEnumerator.

First, give it two fields. One is a private readonly of our food list. The other is a private int of position. Set the position to -1.
        #region Fields
        private readonly List<Food> _food;
        private int position = -1;
        #endregion


We want a basic constructor that takes a list and saves it to the readonly field.
        #region Constructor
        public MealEnumerator(List<Food> FoodList)
        {
            _food = FoodList;
        }
        #endregion


Now we need two properties, both will be readonly. The first one will return the type of object to return in our enumerations, in this case Food. We also need to make sure to have a try/catch block in case it enumerates past the length of our collection.
        public Food Current
        {
            get
            {
                try
                {
                    return _food[position];
                }
                catch (IndexOutOfRangeException)
                {
                    throw new InvalidOperationException();
                }
            }
        }


The other property is from IEnumerator. It returns an object. This is the hook to the class. IEnumerator.Current is called, and we just need to point it towards the property we just made.
        object IEnumerator.Current
        {
            get
            {
                return Current;
            }
        }


IEnumerator also has two methods we need to implement. The first is MoveNext that returns a boolean value. What we want is for it increment the position and return whether there are more items in the collection. We can compare the position to the collection length for this.
        public bool MoveNext()
        {
            position++;
            return (position < _food.Count);
        }


The other method we need is Reset. This one simply starts the iteration over.
        public void Reset()
        {
            position = -1;
        }


You may notice that we set the default position to -1. We also set it to -1 with the method Reset. This is because MoveNext is called before Current is read, even at the beginning of the iteration. If we set these to 0, we will never see the first object of the collection.


Back to Meal
Before we finish our first method, we need our second method. Just like we did in IEnumerator.Current, the IEnumerable.GetEnumerator is a hook we can use to point to a custom GetEnumerator. This will be a public method that simply creates a MealEnumerator and returns it.
        public MealEnumerator GetEnumerator()
        {
            return new MealEnumerator(FoodList);
        }


Our first method will call this method, casting the result as an IEnumerator.
        IEnumerator IEnumerable.GetEnumerator()
        {
            return (IEnumerator)GetEnumerator();
        }


We can now compile this code without being yelled at by the IDE. You can see that Meal is much easier to use. You can call foreach (Food item in dinner) to iterate through every food item in your class, without exposing the collection as public. I have included a .zip copy of the files for your use.

Attached File(s)



Is This A Good Question/Topic? 1
  • +

Replies To: GetEnumerator Basics

#2 Guest_Rinacom*


Reputation:

Posted 27 September 2010 - 06:04 AM

we can have a big shortcat, by only using the yield keyword, don't remember exactly how...
but thank you anyway
Was This Post Helpful? 0

Page 1 of 1