Implementing C# Generic Collections Using Collection<T>, ICollection<T> With IEnumerator<T>

Introduction

 
This article will demonstrate how to create your own custom collection class using generic Collection<T>. It will also demonstrate how to implement ICollection<T> and IEnumerator<T> in order to create a generic, type-safe, and expandable collection class. 
 
Let's start with a step by step approach to first learn Collection<T> and then ICollection<T> and IEnumerator<T> . 
 
Prerequisites
 
Learn how to create your own custom collection class with help of System.Collection.Generics and customize your collection to add common type of validation in one place.
 

Using Collection<T> 

 
Let's have class training with a few properties and then make a collection of that class. The generic Collection<T> is basically derived from ICollection<T> where most of the implementations are taken care  of for you and you do not need to worry about addition and removal etc. if you want to customize addition and removal use ICollection<T> directly which is step2 in this article. 
  1. public class Training  
  2.    {  
  3.        public string Name { getset; }  
  4.   
  5.        public int Cost { getset; }  
  6.    }
  1. public class Trainings : Collection<Training>  
  2.     {  
  3.         public Training this[string name]  
  4.         {  
  5.             get { return this.Items.First(s => string.Equals(s.Name, name, StringComparison.OrdinalIgnoreCase)); }  
  6.         }  
  7.   
  8.         public IEnumerable<string> All => this.Items.Select(s => s.Name);  
  9.   
  10.         protected override void InsertItem(int index, Training item)  
  11.         {  
  12.             // validation before adding in common place.  
  13.             if (item.Cost > 0)  
  14.             {  
  15.                 base.InsertItem(index, item);  
  16.             }  
  17.         }  
  18.   
  19.         public void ForEach(Action<string> action)  
  20.         {  
  21.             foreach (var item in Items)  
  22.             {  
  23.                 action($"Traning Name {item.Name} and cost {item.Cost}");  
  24.             }  
  25.         }  
  26.     }  
You may notice that you have your indexer to get by name and validation before inserting a new item. Let's have a look at how to use this collection in console application.
  1. static void Main(string[] args)  
  2.         {  
  3.             var trainings = new Trainings();  
  4.             trainings.Insert(0, new Training { Name = "C#", Cost = 10 });  
  5.             trainings.Add(new Training() { Name = "Java", Cost = 10 });  
  6.   
  7.             trainings.ForEach(Console.WriteLine);  
  8.             Console.ReadKey();  
  9.         }  

Using ICollection<T>

 
The new collection class derived from ICollection<T> is responsible for adding, inserting and removing objects from our collection with our own validation etc, checking the collection for of a given object, and instantiating an IEnumerator<T> object in order to enumerate the collection using a foreach statement. Let's have look at a new collection class which implements from ICollection. ICollection generic collection Interface is derived from IEnumerable, this interface was implemted for a collection based class as mentioned above.
  1. public class TrainingsCollection<T> : ICollection<T> where T : Training  
  2. {  
  3.     public TrainingsCollection()  
  4.     {  
  5.         List = new List<T>();  
  6.     }  
  7.   
  8.     protected IList List { get; }  
  9.   
  10.     public T this[int index] => (T)List[index];  
  11.   
  12.     public int Count => this.List.Count;  
  13.   
  14.     public bool IsReadOnly => throw new NotImplementedException();  
  15.   
  16.     public void Add(T item)  
  17.     {  
  18.         if (!string.IsNullOrEmpty(item.Name))  
  19.         {  
  20.             this.List.Add(item);  
  21.         }  
  22.     }  
  23.   
  24.     public void Clear()  
  25.     {  
  26.         this.List.Clear();  
  27.     }  
  28.   
  29.     public bool Contains(T item)  
  30.     {  
  31.         return this.List.Contains(item);  
  32.     }  
  33.   
  34.     public IEnumerator<T> GetEnumerator()  
  35.     {  
  36.         return new TraningsEnumerator<T>(this);  
  37.     }  
  38.   
  39.     public bool Remove(T item)  
  40.     {  
  41.         if (item != null && Contains(item))  
  42.         {  
  43.             this.List.Remove(item);  
  44.             return true;  
  45.         }  
  46.   
  47.         return false;  
  48.     }  
  49.   
  50.     IEnumerator IEnumerable.GetEnumerator()  
  51.     {  
  52.         return new TraningsEnumerator<T>(this);  
  53.     }  
  54.   
  55.     public void CopyTo(T[] array, int arrayIndex)  
  56.     {  
  57.         this.List.CopyTo(array, arrayIndex);  
  58.     }  
  59.   
  60.     public void ForEach(Action<string> action)  
  61.     {  
  62.         foreach (var item in List.OfType<T>())  
  63.         {  
  64.             action($"Traning Name {item.Name} and cost {item.Cost}");  
  65.         }  
  66.     }  
  67. }  
These two implementations mentioned above for GetEnumerator are there for foreach loop and this will be calling in order to loop through our custom collection. We have defined our custom generic Enumerator object that will perform the actual looping.
 

Using IEnumerator<T> 

  1. public class TraningsEnumerator<T> : IEnumerator<T> where T : Training  
  2.     {  
  3.         private readonly TrainingsCollection<T> collection;  
  4.         public int Counter = -1;  
  5.   
  6.         public TraningsEnumerator(TrainingsCollection<T> collection)  
  7.         {  
  8.             this.collection = collection;  
  9.         }  
  10.   
  11.         public object Current => collection[Counter];  
  12.   
  13.         T IEnumerator<T>.Current => collection[Counter];  
  14.   
  15.         public void Dispose()  
  16.         {  
  17.             Counter = -1;  
  18.         }  
  19.   
  20.         public bool MoveNext()  
  21.         {  
  22.             ++Counter;  
  23.             if (collection.Count >  Counter)  
  24.             {  
  25.                 return true;  
  26.             }  
  27.   
  28.             return false;  
  29.         }  
  30.   
  31.         public void Reset()  
  32.         {  
  33.             Counter = -1;  
  34.         }  
  35.     }  
Let's have a look at how to use this collection in console application.
  1. static void Main(string[] args)  
  2.         {  
  3.             var trainings = new TrainingsCollection<Training>();  
  4.             trainings.Add(new Training { Name = "C#", Cost = 10 });  
  5.             trainings.Add(new Training() { Name = "Java", Cost = 10 });  
  6.   
  7.             // This loop with trigger to IEnumerator<T> GetEnumerator() from our custom TraningsEnumerator  
  8.             foreach (var item in trainings)  
  9.             {  
  10.                 Console.Write($"Traning Name {item.Name} and cost {item.Cost}");  
  11.             }  
  12.   
  13.             Console.ReadKey();  
  14.         }  

Conclusion 

 
Here we learned how to create your own custom generic collection with the help of Collection<T>, ICollection<T> and IEnumerator<T>. This Generic Collection gives you an advantage for your application with type-safe and saves a lot of time, avoiding duplication of code. You could also use of List<T> but there are many ways to implement type-safe generic collections in c#.