Full Version: Create A Strongly Typed Collection In C#
Dream.In.Code > Programming Tutorials > C# Tutorials
PsychoCoder
Welcome to my tutorial on Creating a Strongly Typed Collection C#. In this tutorial we will be looking at creating your own type safe collection. Lets say you want to create a collection of IP addresses, one that accepts only the type of IPAddress, to do this you need to create a class that inherits from CollectionBase Class.

This class gives you all the functionality you need for your strongly typed collection. Before we get to that collection, we need to define an IPAddress collection that you strongly types collection will accept. In this collection we will implement the IComparable Interface. Here is the official definition of IComparable:

"Defines a generalized comparison method that a value type or class implements to create a type-specific comparison method."

The IComparable Interface has but a single method, CompareTo. CompareTo gives us the ability, before adding anything to our strongly typed collection, to check the object being added and ensure its of the right type, if it isn't the compiler will generate a compile time error, rather than a run-time error. Catching mistakes like that at compile-time means our application isn't going to die if someone tries to add an object to our collection that it isn't mean to hold.

So now we will create our IPAddress Type for our collection. The first thing we need to add to our class, as always, is a reference to the Namespace's we will be using. We will need a reference to the System Namespace and the System.Collections Namespace. The System Namespace gives us access to the IComparable Interface, while the System.Collections Namespace gives us access to the CollectionBase Class. So now our Namespace's:

CODE

using System;
using System.Collections;


To make referencing our strongly typed collection easier, I suggest adding a Namespace to your class, as I have done here

CODE

namespace PC
{

}


I chose PC simply because I am PsychoCoder, you can have whatever Namespace you wish to have. Since we implement an Interface in this class our signature will look somewhat different. The signature for our IPAddress class will look like this:

CODE

public class IPAddress : IComparable
{

}


Next we need our global variables, which will be tied to ths public properties of the class. Since this is a simple class, used for the IPAddress type, we only have 2 variables, one for the ip itself and one for the host name:

CODE

private string _address;
private string _host;


Next we will declare our public properties, these will be accessible from outside our class because we declare them as public. Properties are natural extensions of data fields and they allow us to control access to the classes members. A read/write property, which we use in our class, consists of 2 parts:
  • get: Used to return the value of this data field once it has been written to
  • set: Used to write a value to this data field

A read only property only has 1 part, the get part, since it cannot be written to from outside the class its contained in. Our properties are read/write, one for the ip address itself, the other for the host name:

CODE

#region Properties
/// <summary>
/// Property to hold the IP address
/// </summary>
public string Address
{
    //return the value of the property
    get { return _address; }
    //set the value of the property
    set { _address = value; }
}

/// <summary>
/// property to hold the host name
/// </summary>
public string Host
{
    //return the value of the property
    get { return_host; }
    //set the value of the property
    set { _host = value; }
}
#endregion


NOTE: I always place areas of my classes in #region ... #endregion sections for ease of finding items when searching through my classes.

Next, if we want to be to access our class (initialize) from outside the class itself, we need Constructors. Constructors are the code that is executed once the object is initialized. In this class we have 2 constructors, the first one is a base constructor that sets our 2 variables, thus our properties, to an empty string. The second constructor utilizes overloading, which means we can have a different set parameters in each constructor. Here are our constructors:

CODE

#region Constructors
public IPAddress()
{
    _address = string.Empty;
    _host = string.Empty;
}

public IPAddress(string address,string host)
{
    _address = address;
    _host = host;
}
#endregion


When we implement from IComparable you have to have a CompareTo method, which happens to be the only method of the IComparable Interface. The CompareTo Method is what allows us to compare each object before it is added to our strongly typed collection. It allows us to ensure the type being added is of the PAddress type. When using CompareTo 3 possible values are returned
  • > 0
  • 0
  • < 0

When using CompareTo we always want a return value of 0 (zero), which means we are adding the proper type to our collection. Here is our CompareTo method:

CODE

#region IComparable Members
public int CompareTo(object obj)
{
    //check to see if the type being passed is
    //of the IPAddress type
    if (!(obj is IPAddress))
    {
        //since its not we need to throw
        //an ArgumentException
        throw new ArgumentException("Object provided is of the wrong type");
    }
    //convert the object being passed to the type IPAddress
    IPAddress addr = (IPAddress)obj;
    //execute CompareTo and set its return
    //value to our int variable
    int cmpl = this.Address.CompareTo(addr.Address);
    //check the value of the CompareTo method
    if (!(cmpl == 0))
    {
        //not equal to zero, meaning it isnt the
        //right object
        return cmpl;
    }
    return this.Address.CompareTo(addr.Address);
}


Thats the end of our "type" collection. Now that we have finished our type, we need to create our strongly typed collection. In this collection we inherit from the CollectionBase Class, which gives us more functionality than your average collection.

CollectionBase implements the IList Interface, which gives us access to methods such as
  • Add: Add an item to the current collection
  • Contains: Used to check if the collection contains the current element before adding it
  • Remove: Remove a specified item from a strongly typed collection
  • Insert: Insert an item into the collection at the specified index

CollectionBase gives us access to methods such as:
  • CopyTo: Copy an entire collection to a one-dimensional array at the specified index
  • RemoveAt: Remove an item from the collection at the specified index
.

First, since we inherit from CollectionBase we need to specify that in our signature:

CODE

public class AddressList : CollectionBase
{

}


For this collection class we need a Constructor as well, but the constructor for this class will be empty, it's just there to allow us to initialize our collection:

CODE

#region Constructors
public AddressList()
{

}
#endregion


Next, instead of properties in this class, like the class that defines our type, we use an Indexer. As the definition states, Indexers allow us to index a class in the same way an Array is indexed. The only difference between a property and an indexer is an indexer accepts parameters. Our indexer looks like this:

CODE

#region Indexer
/// <summary>
/// Indexer for our collection
/// </summary>
/// <param name="idx">Index of the collection</param>
/// <returns>IPAddress at that index</returns>
public IPAddress this[int idx]
{
    get
    {
        //returns the IPAddress at the specified index
        return (IPAddress)this.InnerList[idx];
    }

}


Now we get to the CollectionBase/IList Methods. In order to add things to our strongly typed collection we need an Add Method. This method is a single line of code, and it simply adds the item to the collection in the next available index:

CODE

#region Add
/// <summary>
/// Method for adding an IPAddress to the list
/// </summary>
/// <param name="addr">IPAddress to add</param>
public void Add(IPAddress addr)
{
    //add this item to the list
    this.InnerList.Add(addr);
}
#endregion


Since this is a strongly typed collection, we don't want to allow duplicate entries. Since we're inheriting from CollectionBase this offers us the functionality to search our collection, and having duplicate entries can cause some unwanted results, so we add a Contains method which checks the items of our collection to see if what we're wanting to add already exists in the collection. In this method, we enumerate through each item in the collection, checking if the address/host name combination being added exists anywhere in our collection, if it does we return true, otherwise we return false:

CODE

#region Contains
/// <summary>
/// Method to check and see if the
/// items is in the list
/// </summary>
/// <param name="addr">Item to check</param>
/// <returns>true/false</returns>
public bool Contains(IPAddress addr)
{
    //loop through all the items in the list
    foreach (IPAddress item in this.InnerList)
    {
        //check to see if its in the list
        //returning 0 means its in the list
        if (item.CompareTo(addr) == 0)
        {
            //return true
            return true;
        }
    }
    //not in the list return false
    return false;
}
#endregion


Now lets say you want to remove an item from your strongly typed collection. inheriting from CollectionBase, which implementes the IList Interface, gives us access to the IList's Remove Method, so we take advantage of that:

CODE

#region Remove
/// <summary>
/// Method for removing a specified IPAddress fromthe list
/// </summary>
/// <param name="ip">IPAddress to remove</param>
public void Remove(IPAddress ip)
{
    //check to see if the list contains this
    //item, if it doesnt exit the procedure
    if (!this.Contains(ip)) return;
    //declare a counter variable
    int i;
    //loop through all the items in the list
    for (i = 0; i <= this.InnerList.Count; i++)
    {
        //create an instance of the IPAddress
        IPAddress item = (IPAddress)this.InnerList[i];
        //now check to see if its in the list
        if ((item.CompareTo(ip) == 0))
        {
            //if its in the list remove it
            this.RemoveAt(i);
            return;
        }
    }
}
#endregion


One method that's accessible when inheriting from CollectionBase, and is almost always overlooked is the OnValidate Method. This method is very important when creating a strongly typed collection. CollectionBase, as stated before, implements the IList Interface, which means that a user can cast your object to an IList object then call any of the methods in the IList Interface. This gives the user the opportunity to insert incorrect types into your collection. This method should throw an ArgumentException if the type being passed isn't of the type we're expecting:

CODE

#region OnValidate
protected override void OnValidate(object value)
{
    //first validate the object
    base.OnValidate(value);
    //if the object isnt of the correct type
    //throw an ArgumentException
    if (!(value is IPAddress))
    {
        throw new ArgumentException("Collection only supports items of type IPAddress");
    }
}
#endregion


There is of course much more than can be accomplished when inheriting from IComparable and CollectionBase, but I touched on the most commonly used methods, the ones that you will use continually in your strongly typed collections. Inheriting from base classes and implementing base interfaces available to you in the .Net Framework will make you a much more powerful programmer in the long run, and give you a far better understanding of the power this framework contains, and of Object Oriented Programming.

I am including for download, the class that I created for writing this tutorial. Remember, since the code is under the GNU - General Public License you may modify, use and distribute as you see fit, but the license header must stay intact. That is the end of this tutorial on Creating a Strongly Typed Collection in C#. I hope you found this tutorial useful and informative. Thanks for reading.

Happy Coding smile.gif

Click to view attachment
baavgai
Nice tutorial, typed Collections are a cornerstone of good OO design. In fact, everyone had to rewrite collection classes so often that a new solution was proposed and has been out for some time, Generics.

Your entire implementation of AddressList can implemented with just this:

CODE

public class AddressList : System.Collections.Generic.List<IPAddress> {
    public AddressList() {}
}


However, I don't think IComparable means what you think it means. It's basically for sorting, where here you're using it for identity / equality. You seem to really want to override the base method shared by all classes, Equals.

I don't like throwing Exceptions for invalid objects, for IComparable I'd just give a -1 to bad requests. For Equals, you send a false if you get something you don't like. Here's an alternative IPAddress class for the Generic list above.

CODE

public class IPAddress : IComparable {
    private string address;
    private string host;

    public IPAddress() { }

    public IPAddress(string address, string host) {
        this.address = address;
        this.host = host;
    }

    public string Address {
        get { return this.address; }
        set { this.address = value; }
    }

    public string Host {
        get { return this.host; }
        set { this.host = value; }
    }

    public int CompareTo(object obj) {
        if (obj==null) { return -1; }
        if (!(obj is IPAddress)) { return -1; }
        return this.address.CompareTo(((IPAddress)obj).address);
    }

    public override bool Equals(object obj) {
        if (!(obj is IPAddress)) { return false; }
        return this.address.Equals(((IPAddress)obj).address);
    }

    // This needs to implemented for Equals to work.
    public override int GetHashCode() {
        return this.address.GetHashCode();
    }

}


I'd love to see you do a Generics tutorial. After doing it the old object boxing way, I'm sure you'll really enjoy the .NET 2.0 version.
PsychoCoder
Once I am done with my series on Multi-Threading I plan on writing one on Generics. I wrote one for VB.Net now its time for C#.

By the way, I program exclusively in 2.0, and have since it came out.

And as far as your comment about me not knowing what IComparable is used for, you couldn't be further from the truth, its actually an excellent way to ensure type safety. If you do some extensive research on strongly typed collections, which I did before writing my first one, most use IComparable for just that purpose.

Thanks for the comments though smile.gif
baavgai
QUOTE(PsychoCoder @ 17 Oct, 2007 - 04:01 PM) *

most use IComparable for just that purpose. for C#.


I'm afraid I find this to be a highly questionable claim.

Consider how Microsoft does this. Notice that the typed methods tend to be overridden with simple code, like this:

CODE

public bool Contains( Int16 value )  {
    return( List.Contains( value ) );
}


A Typed Collection should really only be spending extra code on OnValidate and some typed methods. Your code has to work extra hard because the objects in the collection are functionally invalid for use in a collection.

Consider this code:

CODE
IPAddress [] testList = new IPAddress[] {  

    new IPAddress("192.168.1.2","larry"),

    new IPAddress("192.168.1.3","curly"),

    new IPAddress("192.168.1.4","moe")

};


IPAddress testItem = new IPAddress("192.168.1.3","shemp");



ArrayList aList = new ArrayList(testList);

Console.WriteLine(aList.Contains(testItem));


AddressList taList = new AddressList();

foreach(IPAddress item in testList) {taList.Add(item); }



Console.WriteLine(taList.Contains(testItem));


Results:
CODE
False
True


If Equals was overridden, the first case would be true as well and all that extra code would be unnecessary.

I wouldn't normally go to such exhaustive effort to point this out. After all, it's your code and if it works for you, great. However, if you're offering it up as a tutorial, for others to learn from and perhaps emulate, I'd think you'd be more open to an obvious criticism. If the methodology has a reasoned purpose that I, the student, do not see, I'd hope for a greater explanation than citing some bogus "extensive research."

Again, I am sorry to be so critical. I'll say no more on this.
This is a "lo-fi" version of our main content. To view the full version with more information, formatting and images, please click here.
Invision Power Board © 2001-2008 Invision Power Services, Inc.