Click here to Skip to main content
6,594,432 members and growing! (16,556 online)
Email Password   helpLost your password?
Languages » C# » General     Intermediate License: The Code Project Open License (CPOL)

Implementing C# Generic Collections using ICollection<T>

By Jake Weakley

An article explaining one way to implement a generic collection in C# using ICollection<T> with an example Business Logic Layer
VC7, VC8.0, C# 2.0, C# 3.0.NET 2.0, WinXP, Win2003, Vista, .NET 3.0, ASP.NET, WebForms, VS2005, VS2008, Dev
Posted:8 Nov 2007
Views:45,346
Bookmarked:60 times
Unedited contribution
Announcements
Loading...
 
Search    
Advanced Search
Add to IE Search
printPrint   add Share
      Discuss Discuss   Broken Article?Report  
17 votes for this article.
Popularity: 4.83 Rating: 3.93 out of 5
1 vote, 5.9%
1
3 votes, 17.6%
2
1 vote, 5.9%
3
5 votes, 29.4%
4
7 votes, 41.2%
5

Screenshot - GenericsExample.jpg

Introduction

In the process of building three-tier web applications I came across the need for a generic collection class which would be able to hold a type-safe collection of any of my business objects from my Business Logic Layer. I had already written a non-generic collection class (one for each of my business objects, sadly) which inherited from CollectionBase. As the need for more and more business objects arose I immediately stopped what I was doing and started looking for a more dynamic solution.

As I could recall, C++ had something called template classes that would accept a dynamic variable type. It seemed to me that a generic collection class which would be enumerable (i.e. able to be traversed using a foreach statement) wouldn't be very difficult to implement in C#. I thought the best way to go about this would be to write a generic class that implemented the generic interface ICollection<T>. I set out looking for examples on the Internet and found that decent examples of this implementation were few and far between and some sites actually stated that it wasn't worth looking at because no one implements ICollection<T>. Surely, I thought, there must be someone who implements ICollection<T>.

After a bit of searching around I found a few examples showing incomplete and/or non-functioning code on how to implement this interface. I took what I could learn from all of the information I found and came up with what I believe to be a decent, functioning implementation of ICollection<T> as well as IEnumerator<T> to enable enumeration through a foreach statement. This article attempts to present a solid example of how to implement ICollection<T> and IEnumerator<T> in order to create a generic, type-safe, and expandable collection class.

What are Generics?

Generics were first introduced into the C# language in .NET 2.0. They provide a type-safe method of accessing data used in collections and/or function calls. Using Generics significantly decreases the amount of run-time errors due to casting or boxing/unboxing because the types used in a generic operation are evaluated at compile time. If you are a C++ fan you will realize that generics operate in a very similar way to Template Classes in C++, but with a few differences.

What is the Difference Between C++ Templates and C# Generics?

One of the main differences between C++ Template Classes and C# Generics is that in C# you cannot use any arithmetic operators (except custom operators) in a generic class.

C# does not allow type parameters to have default types, nor does C# allow you to use the type parameter (T) as the base class for the Generic Type.

These are only a few of the differences at a glance, but it is important to take note of them if you are coming from a familiar C++ frame of reference. Even though C++ is more flexible in this area, the C# model provides a decreased amount of runtime errors by limiting what can be done based on Generic Constraints.

A Closer Look at ICollection<T>

Before we get started lets take a look at how ICollection<T> is defined in the .NET Framework.

   public interface ICollection<T> : IEnumerable<T>, IEnumerable
   {
      void Add(T item);

      bool Remove(T item);
      void Clear();
      bool Contains(T item);
      void CopyTo(T[] array, int arrayIndex);
      int Count { get; }
      bool IsReadOnly { get; }
   }

As you can see, in order to extend ICollection<T> in our generic collection we must implement 2 properties and 5 methods. We must also implement 2 methods which are required by IEnumerator<T> and IEnumerator; the interfaces which ICollection<T> extends to allow foreach enumeration. More on this later. For now, lets define the objects we are going to be collecting.

Example Business Logic Layer

Before we actually write our collection class we are going to write some foundation code with which it can be used. The example source code attached to this article contains 2 classes which make up a simple Business Logic Layer that we will use with our generic collection class.

BusinessObjectBase Class

This is the base class from which all of our other business objects will be derived. The class contains only 1 property called UniqueId (Guid), and a default constructor which initializes that property. Even though the base class we are using here is trivial, much functionality could be added to the base class for a Business Object. As for right now, we are only interested in the way it is derived by other classes because we are going to use this to limit the types that can be used with our generic collection.

//Abstract base class for all business object in the Business Logic Layer

public abstract class BusinessObjectBase
{
    protected Guid? _UniqueId; //local member variable which stores the object's UniqueId

   
    //Default constructor

    public BusinessObjectBase()
    {
        //create a new unique id for this business object

        _UniqueId = Guid.NewGuid();
    }

    //UniqueId property for every business object

    public Guid? UniqueId
    {
        get
        {
            return _UniqueId;
        }
        set
        {
            _UniqueId = value;
        }
    }
}

This is nothing too complicated. The base class is abstract to ensure that it must be inherited in order to be instantiated, and it only contains a single property to uniquely identify each business object which it derives.

Person Class

This class will represent a person which we will be storing in our collection. As you can see below, the Person class inherits from the abstract class BusinessObjectBase. This will be important later on when we create our generic collection class.

public class Person : BusinessObjectBase
{
    private string _FirstName = "";
    private string _LastName = "";

    //Paramaterized constructor for immediate instantiation

    public Person(string first, string last)
    {
        _FirstName = first;
        _LastName = last;
    }

    //Default constructor

    public Person()
    {
        //nothing

    }
 
    //Person' First Name 

    public string FirstName 
    {
        get
        {
            return _FirstName;
        } 
        set
        {
            _FirstName = value;
        }
    }

    //Person's Last Name

    public string LastName
    {
        get
        {
            return _LastName;
        }
        set
        {
            _LastName = value;
        }
    }
}

This class is equally trivial and contains only a FirstName and LastName property as well as a constructor to initialize those properties immediately and a default parameterless constructor. Now that we've seen the objects we will be collecting, lets move on to how we will be collecting them.

BusinessObjectCollection<T> Class

This is going to be where most of the magic happens. The methods we implement here from ICollection<T> are going to be responsible for adding and removing objects from our collection, checking the collection for instances of a given object, and instantiating an IEnumerator<T> object in order to enumerate the collection using a foreach statement (we'll look at that later). Below is the definition of BusinessObjectCollection<T>.

using System;
using System.Collections.Generic; 
using System.Collections; //needed for non-generic explicit interface implementation requirements from ICollection

using System.Text;

namespace GenericsExample.Business
{
    public class BusinessObjectCollection<T> : ICollection<T> where T : BusinessObjectBase
    {
        protected ArrayList _innerArray; //inner ArrayList object

        protected bool _IsReadOnly; //flag for setting collection to read-only mode (not used in this example)


        // Default constructor

        public BusinessObjectCollection()
        {
            _innerArray = new ArrayList();
        }

        // Default accessor for the collection 

        public virtual T this[int index]
        {
            get
            {
                return (T)_innerArray[index];
            }
            set
            {
                _innerArray[index] = value;
            }
        }

        // Number of elements in the collection

        public virtual int Count
        {
            get
            {
                return _innerArray.Count;
            }
        }

        // Flag sets whether or not this collection is read-only

        public virtual bool IsReadOnly
        {
            get
            {
                return _IsReadOnly;
            }
        }

        // Add a business object to the collection

        public virtual void Add(T BusinessObject)
        {
            _innerArray.Add(BusinessObject);
        }

        // Remove first instance of a business object from the collection 

        public virtual bool Remove(T BusinessObject) 
        {
            bool result = false;

            //loop through the inner array's indices

            for (int i = 0; i < _innerArray.Count; i++)
            {
                //store current index being checked

                T obj = (T)_innerArray[i];

                //compare the BusinessObjectBase UniqueId property

                if (obj.UniqueId == BusinessObject.UniqueId)
                {
                    //remove item from inner ArrayList at index i

                    _innerArray.RemoveAt(i);
                    result = true;
                    break;
                }
            }

            return result;
        }

        // Returns true/false based on whether or not it finds the requested object in the collection.

        public bool Contains(T BusinessObject)
        {
            //loop through the inner ArrayList

            foreach (T obj in _innerArray)
            {
                //compare the BusinessObjectBase UniqueId property

                if (obj.UniqueId == BusinessObject.UniqueId)
                {
                    //if it matches return true

                    return true;
                }
            }
            //no match

            return false;
        }
 
        // Copy objects from this collection into another array

        public virtual void CopyTo(T[] BusinessObjectArray, int index)
        {
            throw new Exception("This Method is not valid for this implementation.");
        }

        // Clear the collection of all it's elements

        public virtual void Clear()
        {
            _innerArray.Clear();
        }

        // Returns custom generic enumerator for this BusinessObjectCollection

        public virtual IEnumerator<T> GetEnumerator()
        {
            //return a custom enumerator object instantiated to use this BusinessObjectCollection 

            return new BusinessObjectEnumerator<T>(this);
        }

       // Explicit non-generic interface implementation for IEnumerable extended and required by ICollection (implemented by ICollection<T>)

        IEnumerator IEnumerable.GetEnumerator()
        {
            return new BusinessObjectEnumerator<T>(this);
        }
    }
}

You'll notice the first line of the class declaration:

public class BusinessObjectCollection<T> : ICollection<T> where T : BusinessObjectBase

The syntax above simply tells the compiler to only allow types which are derived from BusinessObjectBase to be allowed into the collection. This is where a generic collection really comes in handy. When we use Generic Type Constraints as above we are saving ourselves the step of worrying about type-safety errors at runtime. If we try to create a collection of object which do not derive from BusinessObjectBase we will get a compiler error. This may sound limiting, but this is exactly what we want in this case because we may want to add special processing to our collection class that will only work when used on a class that inherits BusinessObjectBase.

You can add other Generic Type Constraints as well, such as the new() constraint, which will tell the compiler that the Generic Type which the collection will store must have one public paramaterless constructor. This is normally used to gain the ability to instantiate an instance of the Generic Type inside the Generic Class. For more information on Generic Type Constraints visit the MSDN web site here.

You'll notice that I have used an ArrayList object as the inner data structure which powers the custom generic collection. Since this is a non-generic type it could cause some boxing/unboxing if you tried to create a generic collection full of value types (such as integers). But since we are using a constraint to only allow types which are derived from BusinessObjectBase we don't have to worry about that issue. There will still be some casting down to the Object base class done by the ArrayList object but there is a minimal performance degredation, if any at all. You could also use another generic list type as the backing element such as List<T> or even a strongly typed array. The performance of using List<T> and ArrayList are pretty much the same, even with a huge (I tested with 20,000 elements) amount of data. The redimensioning operations needed to use a strongly typed array, however, are extremely costly and took up about %70-80 of total operation time when adding the same number of elements to the collection.

The rest of this class is just basic implementation of the ICollection<T> interface with a little bit of custom logic for the Remove() and Contains() methods which look at the UniqueId field of the BusinessObjectBase class in order to determine an object's existence within the collection.

There is, however, a bit of code worth looking at which ties in with our next class:

 // Returns custom generic enumerator for this BusinessObjectCollection

public virtual IEnumerator<T> GetEnumerator()
{
   //return a custom enumerator object instantiated to use this BusinessObjectCollection 

   return new BusinessObjectEnumerator<T>(this);
}

// Explicit non-generic interface implementation for IEnumerable extended and required by ICollection (implemented by ICollection<T>)

IEnumerator IEnumerable.GetEnumerator()
{
   return new BusinessObjectEnumerator<T>(this);
}

These two implementations are what the foreach loop will be calling in order to loop through our custom collection. We have to define a custom generic Enumerator object that will perform the actual looping. If you've never dealt with generic interface implementation before you may be wondering why there are 2 implementations of GetEnumerator() here. The second method is an Explicit non-generic interface implementation for IEnumerable. Why do we have to do this? The answer is simple, because IEnumerator<T> implements the IEnumerable interface, which is non-generic. And since we implementing ICollection<T> we must account for it's non-generic roots.

Now that we now how we will be collecting the Business Objects, lets move on to how we will be retrieving them using a custom generic Enumerator.

BusinessObjectEnumerator<T> Class

Below if the code definition for our custom generic Enumerator which will be used to enumerate through instances of the BusinessObjectCollection<T> class. The code is very simple to understand with a few main points of interest on which I will touch below.

 public class BusinessObjectEnumerator<T> : IEnumerator<T> where T : BusinessObjectBase 
{
        protected BusinessObjectCollection<T> _collection; //enumerated collection

        protected int index; //current index

        protected T _current; //current enumerated object in the collection


        // Default constructor

        public BusinessObjectEnumerator()
        {
            //nothing

        }

        // Paramaterized constructor which takes the collection which this enumerator will enumerate

        public BusinessObjectEnumerator(BusinessObjectCollection<T> collection)
        {
            _collection = collection;
            index = -1;
            _current = default(T);
        }

        // Current Enumerated object in the inner collection

        public virtual T Current
        {
            get
            {
                return _current;
            }
        }

        // Explicit non-generic interface implementation for IEnumerator (extended and required by IEnumerator<T>)

        object IEnumerator.Current
        {
            get
            {
                return _current;
            }
        }

        // Dispose method

        public virtual void Dispose()
        {
            _collection = null;
            _current = default(T);
            index = -1;
        }

        // Move to next element in the inner collection

        public virtual bool MoveNext()
        {
            //make sure we are within the bounds of the collection

            if (++index >= _collection.Count)
            {
                //if not return false

                return false;
            }
            else
            {
                 //if we are, then set the current element to the next object in the collection

                _current = _collection[index];
            }
            //return true

            return true;
        }

        // Reset the enumerator

        public virtual void Reset()
        {
            _current = default(T); //reset current object

            index = -1;
        }
    }

First you should take note of the beginning line of this class declation:

public class BusinessObjectEnumerator<T> : IEnumerator<T> where T : BusinessObjectBase 

As you can see here we have implemented the same generic constraints in order to ensure the type of objects we will be enumerating through are derived from BusinessObjectBase.

We also define 3 member variables which we will use to control the flow of the enumeration. They are:

protected BusinessObjectCollection<T> _collection; //enumerated collection

protected int index; //current index

protected T _current; //current enumerated object in the collection

The first is the internal instance of the BusinessObjectCollection which is being traversed. This variable gets set through the parameterized constructor inside the BusinessObjectEnumerator<T> class. The BusinessObjectEnumerator itself gets instantiated once a foreach loop calls the GetEnumerator() method of the BusinessObjectCollection class. Each time an item is found, the foreach loop moves to the next item in the collection using the MoveNext() method found above.

The other 2 member variables are simply a reference to the current object of type T being read and an index to let us know where we are at in the collection. The rest of the IEnumerator<T> implementation is pretty simple to understand so I won't waste your time or strain your eyes by explaining it all here.

Now that you've defined all your classes and made them work with each other the only thing left to do is write a little "driver" or example program to demonstrate the Custom Generic Collection's functionality. I included a sample console application which demonstrates the usage of the generic classes explained here with the source code for the classes themselves. I also added in another little console application I wrote in order to test the amount of time it took to complete different collection based operations using different generic and non-generic types with boxing/unboxing and casting.

Conclusion

Custom Generic Collections can be a huge advantage for your application and save you a lot of time and trouble. By creating a type-safe environment in which your objects will be collected you initially rid yourself of having to worry about what actually gets stored in the collection at run-time. You also gain the ability to reuse and extend your code any way you wish by not hard-coding data types. You could easily remove the constraints in the classes above to create a solution that fits your exact needs and could be extended to fit your needs in the future as well.

You may be thinking, "well all this is great, but why not just use List<T> and be done with it?". Well, you could! There are many different ways to implement type-safe and polymorphic data structures in C#. This is but one example. I hope this article has been some help to someone out there and I look forward to getting feedback from the community.

References

Constraints on Type Parameters (C# Programming Guide)
Differences Between C++ Templates and C# Generics (C# Programming Guide)
What are Generics in C#?
C# Generics: Collection Interfaces
Professional C# 2005

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Jake Weakley


Member
I have a background in IT and Computer Science/Programming since about 1990. I have an A.A.S degree in CS and I am still working on my B.S. as well as some certifications.

Previously, I worked in the government sector for about 3 years doing IT Management/Programming.

Currently I am a Web Developer for a private company in Virginia.
Occupation: Web Developer
Location: United States United States

Other popular C# articles:

Article Top
You must Sign In to use this message board.
FAQ FAQ 
 
Noise Tolerance  Layout  Per page   
 Msgs 1 to 13 of 13 (Total in Forum: 13) (Refresh)FirstPrevNext
QuestionImplementing C# Generic Collections using ICollection :: Error while building the same code. PinmemberSunilKurian8:35 3 Jul '09  
GeneralGreat Post PinmemberDale Edmondson9:07 7 Jun '08  
GeneralRe: Great Post PinmemberJake Weakley10:35 3 Jul '08  
Generalusing names instead of index PinmemberNigam SAMir0:59 12 Dec '07  
GeneralRe: using names instead of index PinmemberJake Weakley11:40 17 Dec '07  
GeneralWhy nobody implements ICollection PinmemberAlex Furmanski2:36 20 Nov '07  
GeneralRe: Why nobody implements ICollection PinmemberJake Weakley3:54 20 Nov '07  
GeneralSuggestion PinmemberRichard Deeming10:07 13 Nov '07  
GeneralRe: Suggestion PinmemberJake Weakley10:37 14 Nov '07  
GeneralA remark on C++ templates PinmemberNemanja Trifunovic16:20 9 Nov '07  
GeneralRe: A remark on C++ templates PinmemberJake Weakley22:10 9 Nov '07  
GeneralGeneric List<T>.GetEnumerator() Pinmember_SAM_20:33 8 Nov '07  
GeneralRe: Generic List<T>.GetEnumerator() PinmemberJake Weakley3:48 9 Nov '07  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 8 Nov 2007
Editor:
Copyright 2007 by Jake Weakley
Everything else Copyright © CodeProject, 1999-2009
Web17 | Advertise on the Code Project