Table of Contents
Being a .NET developer, we all are familiar with collections and generics. We know ArrayLists, Arrays, List, and Dictionary etc. as collection classes through which we can iterate. This article will explain how one can create their own custom collection class that can be iterated through. The article follows a step by step process to create a custom collection class to know what actually it takes to create a collection class. The purpose of the article is to learn the concept of collection classes through step by step practical implementations.
Prerequisites
The prerequisite to learn how to create a custom class is not more than to know how to code in C#. The article uses a console application named CollectionClassConcept
that contains an empty class named CollectionClass
. We’ll use this initial setup to learn about collection classes.
Step 1
Considering that we already have a collection class (even if it is empty for now) try to access the CollectionClass
in the Main method of Program.cs; create an instance of the class and iterate through it.
CollectionClass
namespace CollectionClassConcept
{
public class CollectionClass
{
}
}
Program.cs
using System;
namespace CollectionClassConcept
{
class Program
{
static void Main(string[] args)
{
CollectionClass collectionClass=new CollectionClass();
foreach (string collection in collectionClass)
{
Console.WriteLine(collection);
}
}
}
}
When the solution is compiled, it ends up having a compile time error as shown below.
Error: foreach statement cannot operate on variables of type 'CollectionClassConcept.CollectionClass' because 'CollectionClassConcept.CollectionClass' does not contain a public definition for 'GetEnumerator'
Step 2
As the error suggests that a class needs to have GetEnumerator
method for it to be iterative. So add a new GetEnumerator()
method into the CollectionClass
class.
CollectionClass
namespace CollectionClassConcept
{
public class CollectionClass
{
public int GetEnumerator()
{
}
}
}
Program.cs
using System;
namespace CollectionClassConcept
{
class Program
{
static void Main(string[] args)
{
CollectionClass collectionClass=new CollectionClass();
foreach (string collection in collectionClass)
{
Console.WriteLine(collection);
}
}
}
}
When the solution is compiled, it ends up having a compile time error as shown below.
Error:
- CS0161 - '
CollectionClass.GetEnumerator()
': not all code paths return a value
- CS0117 - 'int' does not contain a definition for 'Current'
- CS0202 - foreach requires that the return type 'int' of '
CollectionClass.GetEnumerator()
' must have a suitable public MoveNext
method and public Current
property
Now in the code above, foreach here makes an attempt to execute the GetEnumertaor
method, but fails because it expects a MoveNext
method and a property named Current
, and it also expects the method to return any other value that integer.
Step 3
At this point one can create a new class named CollectionEnumerator
implementing the interface named IEnumerator
and return the instance of that class from GetEnumerator
method of CollectionClass
.
CollectionEnumerator
using System.Collections;
namespace CollectionClassConcept
{
public class CollectionEnumerator : IEnumerator
{
}
}
CollectionClass
using System.Collections;
namespace CollectionClassConcept
{
public class CollectionClass
{
public IEnumerator GetEnumerator()
{
return new CollectionEnumerator();
}
}
}
Program.cs
using System;
namespace CollectionClassConcept
{
class Program
{
static void Main(string[] args)
{
CollectionClass collectionClass=new CollectionClass();
foreach (string collection in collectionClass)
{
Console.WriteLine(collection);
}
}
}
}
Error:
- 'CollectionEnumerator' does not implement interface member 'IEnumerator.Current'
- 'CollectionEnumerator' does not implement interface member 'IEnumerator.MoveNext()'
- 'CollectionEnumerator' does not implement interface member 'IEnumerator.Reset()'
So it is very clear that the IEnumerator
interface (that the class CollectionEnumerator
implements) has three methods that are needed to be implemented in the child class.
Step 4
Let’s try to implement those methods in the CollectionEnumerator
class and see the results.
CollectionEnumerator
using System.Collections;
namespace CollectionClassConcept
{
public class CollectionEnumerator : IEnumerator
{
public bool MoveNext()
{
System.Console.WriteLine("Inside MoveNext Method");
return true;
}
public void Reset()
{
System.Console.WriteLine("Inside Reset Method");
}
public object Current
{
get
{
System.Console.WriteLine("Inside Current Property");
return "Current Property";
}
}
}
}
CollectionClass
using System.Collections;
namespace CollectionClassConcept
{
public class CollectionClass
{
public IEnumerator GetEnumerator()
{
return new CollectionEnumerator();
}
}
}
Program.cs
using System;
namespace CollectionClassConcept
{
class Program
{
static void Main(string[] args)
{
CollectionClass collectionClass=new CollectionClass();
foreach (string collection in collectionClass)
{
Console.WriteLine(collection);
}
}
}
}
When we run the application, we see no compile or run time error, but the console windows shows endless lines repeating them in a loop as shown in the following image.
What causes this endless output? Let’s try to find out. From the CollectionClass
class, GetEnumerator
method is called from the foreach loop. Here it is expecting this GetEnumerator
method to return something that is IEnumerator
so that iteration or enumeration can happen. Thereafter it invokes the MoveNext
method from the returned instance of CollectionEnumerator
class, so in case MoveNext
is returning a true value, that means implicitly that there is some data that could be read, and in turn it calls the Current
property to get that data. When Current
property is called, it writes "Inside Current Property" and then gets accessor of the property and always returns "Current Property" text because we mentioned that in the code. Now again MoveNext
is called and as per our defined case, again true is returned, so it happens to be an endless loop now. In case MoveNext
returns false stating that no more data is left, the loop will stop there. Let’s try to do this in next step.
Step 5
CollectionEnumerator
using System;
using System.Collections;
using System.Collections.Generic;
namespace CollectionClassConcept
{
public class CollectionEnumerator : IEnumerator
{
public List<string> StringList=new List<string>(4) { "Value One", "Value Two", "Value Three", "Value Four", "Value Five" };
public int Counter = -1;
public bool MoveNext()
{
Counter++;
Console.WriteLine("Inside MoveNext Method : " + Counter);
return Counter != 5;
}
public void Reset()
{
Console.WriteLine("Inside Reset Method");
}
public object Current
{
get
{
Console.WriteLine("Inside Current Property : " + StringList[Counter]);
return StringList[Counter];
}
}
}
}
CollectionClass
using System.Collections;
namespace CollectionClassConcept
{
public class CollectionClass
{
public IEnumerator GetEnumerator()
{
return new CollectionEnumerator();
}
}
}
Program.cs
using System;
namespace CollectionClassConcept
{
class Program
{
static void Main(string[] args)
{
CollectionClass collectionClass=new CollectionClass();
foreach (string collection in collectionClass)
{
Console.WriteLine(collection);
}
}
}
}
Output
In the CollectionEnumerator
class a generic List of strings is created. One can also create an array or ArrayList. In this case, we are using List having five members with values: "Value One", "Value Two", "Value Three", "Value Four", and "Value Five". There is a counter variable initialized to -1. Every time the MoveNext method is called, the counter value is incremented by 1. Now we specifically specified the length of the list as five and in the MoveNext
method we specify to return false if the counter exceeds five. Therefore, the counter will have a track how many times the MoveNext
method is called. Whenever MoveNext
method returns true, the Current
property gets called that returns the string member from the defined list i.e. StringList
indexed at the current value of the counter. In the similar way one can iterate through the list based on the length of the list or array.
Step 6
Let’s now try making the collection class independent of fixed length. For example, we used five in the previous example. So we’ll do the implementation to dynamically take the input and iterate through it via calculating its length and accordingly process and show the output.
CollectionEnumerator
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace CollectionClassConcept
{
public class CollectionEnumerator : IEnumerator
{
public List<string> StringList;
public int Counter = -1;
public CollectionEnumerator(string parameter)
{
StringList = parameter.Split(' ').ToList();
}
public bool MoveNext()
{
Counter++;
Console.WriteLine("Inside MoveNext Method : " + Counter);
return Counter != StringList.Count;
}
public void Reset()
{
Console.WriteLine("Inside Reset Method");
}
public object Current
{
get
{
Console.WriteLine("Inside Current Property : " + StringList[Counter]);
return StringList[Counter];
}
}
}
}
CollectionClass
using System.Collections;
using System.Collections.Generic;
namespace CollectionClassConcept
{
public class CollectionClass
{
private string _parameter;
public CollectionClass(string parameter)
{
_parameter = parameter;
}
public IEnumerator GetEnumerator()
{
return new CollectionEnumerator(_parameter);
}
}
}
Program.cs
using System;
using System.Security.AccessControl;
namespace CollectionClassConcept
{
class Program
{
static void Main(string[] args)
{
CollectionClass collectionClass=new CollectionClass("We know what is a collection class.");
foreach (string collection in collectionClass)
{
Console.WriteLine(collection);
}
Console.ReadLine();
}
}
}
The explanation of the above written code is very straight forward and self-explanatory. In the main method of Program
class, when we try to create an instance of CollectionClass
we pass string as a parameter, keeping in mind that the class has a parameterized constructor that takes one string argument. So CollectionClass
constructor being called first holds the parameter in variable _parameter. Now, the foreach statement invokes GetEnumerator
which in turn creates an instance of CollectionEnumerator
class and pass _parameter
as a parameter to the constructor of CollectionEnumerator
keeping in mind that CollectionEnumerator
class now also has a parameterized constructor taking one string argument. As per the implementation of CollectionEnumerator
class, as soon as its constructor is called, the parameter is split by a space character via Split method of strings. One can also supply more split character options by providing an array of splitters to the split method here, for now we’ll use space as a splitter. We convert the array that we got after split to a List of string and initialize that to StringList list defined in the class. Now to make the enumeration work independently of the fixed length, we’ll not use a constant number for the length but the length of the list that we have got in the constructor. So in the MoveNext
method it returns false only if the counter does not match the count of the list. So at the end we have a collection class that is custom as per our implementation and iterates through the string on the words inside. One can also customize it further to match the requirement.
Conclusion
This article covered the topic of Collection Class/ Collection Object in detail. Creating a collection class may sound a bit tough but it’s not that tough, and it helps us to have full control over the kind of enumeration we want to use. Happy Coding : -)
Source Code on Github
Source Code