Posted by: Cirilo Meggiolaro | 01/31/2009

Tip of the day #109 – Indexed Collections with custom index data types

You have several ways to create a collection of your application’s objects. You may use arrays, generic lists and so on.

This tip shows how to create a generic collection for these objects that you may access using an index. The index does not need to be an integer value as we are used to.

Some indexer facts

  • Indexers may be from types other than integers;
  • Your class may have more than one indexer. Several overloads are allowed;
  • Indexers don’t need to receive just the index as parameter. You may define your own parameters.

How to…

Let’s assume your application has several business objects like the following:

public class Customer
{
    /// Primary key for objects on the database stored as unique identifiers.
    public Guid ID { get; set; }
    public string Name { get; set; }
    public DateTime DOB { get; set; }
    […]
}

The idea is to have a generic collection that may be indexed by the Guid property. So the first task we are going to perform is to declare our generic IndexedCollection class naming the data type as T and declare a list of T objects.

public class IndexedCollection<T>
{
    private List<T> _items = new List<T>();
}

The code snippet to create an index is called indexer. Type indexer on the class scope and press the Tab key twice on the keyboard. The following code will be displayed:

public object this[int index]
{
    get { /* return the specified index here */ }
    set { /* set the specified index to value here */ }
}

Our data type is called T, so replace the object keyword by T, replace the int data type by Guid and name the parameter as id. Our indexer method will be similar to the following:

public T this[Guid id]
{
    get { /* return the specified index here */ }
    set { /* set the specified index to value here */ }
}

But how do we check our list of T objects to find a property called ID with Guid data type? First of all, the T object must understand that this property exists. Constraints may be added to generic classes so this “understanding” is possible. A constraint may be based on an interface implementation, an inheritance from a specific class and so on. To check all allowed constraints on generic classes, click here!

So, go back to the Customer class for a while. The class has the Guid ID property, doesn’t it? Maybe other classes have the same unique key. So, the next step is to create our constraint that will be an interface with the Guid ID property:

public interface IBusinessIndexes
{
    Guid ID { get; set; }
}

Don’t forget to add the interface implementation to the Customer class:

public class Customer : IBusinessIndexes
{
    […]
}

So far so good! Let’s add the constraint to our indexed collection object:

public class IndexedCollection<T> where T : IBusinessIndexes
{
    […]
}

To implement our index function, we are going to call a support method that uses a simple lambda expression to find an item in the T collection.

T myT = _items.FirstOrDefault(item => item.ID == id);

if (myT == null)
    throw new ArgumentOutOfRangeException(“The object with the guid index does not exist on the list.”);

return myT;

Once we have the item instance you may return it or set its value;

I added some more support methods to add or remove T items from the collection. Here is the final code. It’s fully commented:

/// <summary>
/// The interface stores indexes fields
/// that may be used on collections.
/// </summary>

public interface IBusinessIndexes
{
    Guid ID { get; set; }
}

/// <summary>
/// Customer business object class.
/// </summary>
public class Customer : IBusinessIndexes
{
    /// <summary>
    /// Gets or sets the customer’s ID.
    /// (Defined on the business object interface).
    /// </summary>
    public Guid ID { get; set; }

    /// <summary>
    /// Gets or sets the customer’s name.
    /// </summary>

    public string Name { get; set; }

    /// <summary>
    /// Gets or sets the customer’s date of birth.
    /// </summary>

    public DateTime DOB { get; set; }
}

/// <summary>
/// Generic indexed collection.
/// </summary>
/// <typeparam name=”T”>Data type.</typeparam>
public class IndexedCollection<T> where T : IBusinessIndexes
{
    /// <summary>
    /// Stores a list of T type objects.
    /// </summary>
    
private List<T> _items = null;

    /// <summary>
    /// Default constructor.
    /// </summary>
    public IndexedCollection()
    {
        /// Instantiates the list
        /// of T data type objects.
        _items = new List<T>();
    }

    /// <summary>
    /// Indexer based on a Guid.
    /// </summary>
    /// <param name=”id”>Item id.</param>
    /// <returns>An instance of T data type.</returns>
    public T this[Guid id]
    {
        get
        {
            /// Calls the GetItem method to find and
            /// retrieve the item based on the Guid ID.

            return
GetItem(id);
        }
        set
        {
            /// Calls the GetItem method to find an item.
            T myT = GetItem(id);

            /// Sets the value for the T object.
            myT = value;
        }
    }

    /// <summary>
    /// Gets an item from the items collection.
    /// </summary>
    /// <param name=”id”>Guid ID.</param>
    /// <returns>A T data type object.</returns>
    private T GetItem(Guid id)
    {
        /// Using lambda expressions to set the T object.
        T myT = _items.FirstOrDefault(item => item.ID == id);

        if (myT == null)
            throw new ArgumentOutOfRangeException(“The object with the guid index does not exist on the list.”);

        return myT;
    }

    /// <summary>
    /// Add a T data type instance to the collection.
    /// </summary>
    /// <param name=”myT”>A T data type object.</param>
    public void Add(T myT)
    {
        if (!_items.Contains(myT))
            _items.Add(myT);
    }

    /// <summary>
    /// Remove a T data type instance from the collection.
    /// </summary>
    /// <param name=”myT”>A T data type object.</param>
    public void Remove(T myT)
    {
        if (_items.Contains(myT))
            _items.Remove(myT);
    }
}

Executing

The following code creates an instance of the IndexedCollection, creates three customers, adds them to the collection and displays the customer names based on the Guid id.

/// Creates an instance of the generic IndexedColletion class.
/// The Customer class implements the IBusinessIndexes interface so the constraint is valid.
IndexedCollection<Customer> customers = new IndexedCollection<Customer>();

/// Creates first customer.
Customer c1 = new Customer()
{
    ID = Guid.NewGuid(),
    Name = “John Smith”,
    DOB = new DateTime(1970, 6, 22)
};

/// Creates second customer.
Customer c2 = new Customer()
{
    ID = Guid.NewGuid(),
    Name = “George Ng”,
    DOB = new DateTime(1964, 9, 3)
};

/// Creates third customer.
Customer c3 = new Customer()
{
    ID = Guid.NewGuid(),
    Name = “Bill Mayers”,
    DOB = new DateTime(1940, 7, 30)
};

/// Add customers to the collection.
customers.Add(c1);
customers.Add(c2);
customers.Add(c3);

/// Accesses customers by guid
Console.WriteLine(customers[c1.ID].Name);
Console.WriteLine(customers[c2.ID].Name);
Console.WriteLine(customers[c3.ID].Name);

Output

John Smith
George Ng
Bill Mayers

Advertisements

Responses

  1. The index setter ‘does nothing’. You’d need to use FindIndex and then replace that index on the _list, or Add if it doesn’t exist yet. Also you should check that value.Guid == id

  2. Hi Norman,

    You are right! We may add an item from the indexer property if it doesn’t exist yet. It is a better solution.
    The idea of this tip is to give a simple example about how to implement the indexer in a class.

    Thanks for your comment!


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Categories

%d bloggers like this: