C#.NET Custom Iterators 101

One of the most underutilized features of C# is custom iterators.  Although, now with LINQ in .NET 3.5, custom iterators will undoubtedly be used substantially more--in a sense, that is--given that iterators are the basis for the deferred execution in LINQ.

Nonetheless, given that LINQ and .NET 3.5 still have a bit to be adopted everywhere and the fact that are so many C#.NET 2.0 applications out there that still need to be maintained, I felt that a quick intro to custom iterators from this perspective would still be appropriate.  Now, this primer will cover the basics in a quick-start manner so that one can take what’s here and quickly and easily apply to his/her application.  

Before we get started, however, here is a clear and concise definition of what a custom iterator is.  A custom iterator is the code in a C# class that lets us conditionally iterate the internal list of the class through a foreach loop.  That’s it!

Now, some clarification…conditionally means that the iterations can return a subset of the list if we desire to do so.  Furthermore, the iterator code can be a method, getter property, or operator defined in the class—given the syntax and grammar explained below.

So, here's the application scenario, you are writing an application for daily activities at a children’s day care chain.  You come to a point in the code where you need to work with a list of children’s profiles.  The list is already populated, and you don’t want to query the database every time you need a subset of the list because that costs processing time, among other reasons.  So, you opt to use custom iterators.  There are two iterators you need, one to return only those kids in the list that are in attendance (i.e. not absent) and one to return only those kids a specific age range.  The first subset might be to determine the amount of supplies needed for the day; and the other might be to determine which kids are allowed on a field trip.

So, here is an outline of the above requirements:

  • Condition: Have an already-populated list of all kids registered from DB
  • Need subset of kids that are in attendance
  • Need a subset of kids that are in specific age range


Below is code for the class defining kids’ profiles.  Note that all code is operable and tested on Visual Studio 2005.

    public class StudentProfile
    {
        //not using properties for simplicity
        public string FirstName;
        public string LastName;
        public int Age;        
        public bool InAttendance;

        #region Constructors
        public StudentProfile(string firstName, string lastName, int age, bool inAttendance)
        {
            if (age < 0)
            {
                throw new Exception("Age must be 0 or more.");
            }

            FirstName = firstName;
            LastName = lastName;
            Age = age;
            InAttendance = inAttendance;

        }
        #endregion Constructors
    }

 

 

Now, before we delve into the custom iterator code, let’s go over the syntax and grammar of custom iterators:

  • iterator code can be a class method, property getter, or operator.
  • Iterators use “yield return” to return one of the list elements.
  • The return type of the iterator code must be one the following: IEnumerable, IEnumerator, IEnumerable<T>, or IEnumerator<T>

 

The keyword yield in C# is tricky unless you understand what’s going on in the IL code.  I would suggest accepting it to get things off the ground and running.  For those interested, I would recommend allocating enough time and effort to understand and point you to http://msdn.microsoft.com/en-us/library/dscyy5s0.aspx and maybe even http://msdn.microsoft.com/en-us/magazine/cc163970.aspx.

 

Now, below is the code for defining the list of kids with the two custom iterators.

    public class AllStudents_CustIterator
    {
        List<StudentProfile> m_allStudents = new List<StudentProfile>();

        #region Iterators
        /// <summary>
        /// This is an iterator that is implemented as a property getter.
        /// </summary>
        public IEnumerable StudentsInAttendance
        {
            get
            {
                for (int counter = 0; counter < m_allStudents.Count; counter++)
                {
                    //this is our condition/filter for this iterator.
                    if (m_allStudents[counter].InAttendance)
                    {
                        //Note the use of "yield return"
                        yield return m_allStudents[counter];
                    }
                }
            }
        }

        /// <summary>
        /// This is the basic iterator that all lists need to be used in a foreach loop.
        /// </summary>
        /// <returns></returns>
        public IEnumerator GetEnumerator()
        {
            for (int counter = 0; counter < m_allStudents.Count; counter++)
            {
                //Note the use of "yield return"
                yield return m_allStudents[counter];
            }
        }

        /// <summary>
        /// This is iterator that will return only kids
        /// within an age range. This iterator is
        /// implemented as a method.
        /// </summary>
        /// <returns></returns>
        public IEnumerable WithinAges(int agedAtLeast, int agedAtMost)
        {
            if (agedAtLeast > agedAtMost)
            {
                throw new Exception("Age range invalid.");
            }
            if (agedAtLeast < 0 || agedAtMost < 0)
            {
                throw new Exception("Ages must be 0 or greater.");
            }

           
            for (int counter = 0; counter < m_allStudents.Count; counter++)
            {
                if ((m_allStudents[counter].Age >= agedAtLeast) &&
                    (m_allStudents[counter].Age <= agedAtMost))
                {
                    //Note the use of "yield return"
                    yield return m_allStudents[counter];
                }
            }
        }
        #endregion Iterators

        #region List Methods
        /// <summary>
        /// Adds a student profile to list.
        /// </summary>
        /// <param name="studentToAdd"></param>
        public void Add(StudentProfile studentToAdd)
        {
            m_allStudents.Add(studentToAdd);
        }

        /// <summary>
        /// Removes a student profile at given index.
        /// </summary>
        /// <param name="index"></param>
        public void RemoveAt(int index)
        {
            m_allStudents.RemoveAt(index);
        }

        /// <summary>
        /// Clears entire list.
        /// </summary>
        public void Clear()
        {
            m_allStudents.Clear();
        }
        #endregion List Methods
    }

 

Finally, here is a segment of code that calls the iterators:

        private void button1_Click(object sender, EventArgs e)
        {
            AllStudents_CustIterator allStudents = new AllStudents_CustIterator();

            //Note that although requirements specify that list is already populated, we do this to simulate DB call for
            //explanations purposes.
            allStudents.Add(new StudentProfile("John", "Doe", 0, true));
            allStudents.Add(new StudentProfile("Jane", "Smith", 1, false));
            allStudents.Add(new StudentProfile("Mike", "Manning", 2, true));
            allStudents.Add(new StudentProfile("Rose", "Martinez", 2, true));
            allStudents.Add(new StudentProfile("Sandy", "Roosevelt", 3, false));


            //Get only those kids that are here.
            foreach (StudentProfile kid in allStudents.StudentsInAttendance)
            {               
                textBox1.Text += Environment.NewLine + kid.FirstName + " is present!";
                //do something else with kid variable.
            }

            //Get only those kids that are between ages of 2 and 5.
            foreach (StudentProfile kid in allStudents.WithinAges(2, 5))
            {
                textBox1.Text += Environment.NewLine + kid.Age.ToString() + " year old, " + kid.FirstName + " can go on field trip!";
                //do something else with kid variable.
            }

        }

 

...and the output when I run this is:

 

John is present!
Mike is present!
Rose is present!
2 year old, Mike can go on field trip!
2 year old, Rose can go on field trip!
3 year old, Sandy can go on field trip!

That's all folks!  Please feel free to ask questions and make comments! 

 

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Related posts

Comments