Extend a Generic List to Provide Dictionary-Like Lookups

 

Welcome to a quick and dirty example in which I’ll make use of interfaces, inheritance and generics. I was working with some code that did a lot of iterating over collections to find objects with a particular ID. This is an abstracted example of what I did to simplify and centralize that code in a reusable manner.

If you are using Visual Studio 2008, you could achieve the same kind of reuse with an extension method on your generic list. The project I was working on was in .NET 2.0 and Visual Studio 2005.

This method returns a student’s course based on its ID. The student object here is a private class member. The CurrentCourses property is a List<Course> collection.

private Course FindCourseOld(int id)
{
    foreach (var course in _student.CurrentCourses)
    {
        if (course.ID == id)
        {
            return course;
        }
    }

    return null;
}
 

It’s pretty simple, but suppose you had a dozen or more types with an ID property that were all queries like this? It smells like an opportunity to do some refactoring. We need to keep all the functionality of the List<T> generic collection, including sorting, index operations, and enumerating, but we want to provide a dictionary-type of lookup on the ID property. Let’s inherit from List<T> and add a method to perform our lookup.

Before an object can be stored in this new collection, our child of List<T>, that we are going to create, we need to ensure it has an ID property on which we can operate. To that end, I present to you the IHasIdentifier interface. I know it’s not grammatically correct, but I think it gets the point across.

public interface IHasIdentifier
{
    int ID { get; set; }
}

 

The new interface has a single property named ‘ID’, which many of the types you might consider using in our collection may already have. Next up is the IdentityList<T> itself. The implementation I have here is very simple. There is just a single method, GetItemByID, added to the base class, List<T>.

public class IdentityList<T> : List<T> where T : IHasIdentifier
{
    public T GetItemByID(int id)
    {
        foreach (var obj in this)
        {
            if (obj.ID == id)
            {
                return obj;
            }
        }

        return default(T);
    }
}

 

We’re inheriting from List<T> and specifying that T must implement IHasIdentifier. Now, in the GetItemByID method, we can do our iteration to find the object with the given ID and return it. Finally, let’s take a look at the FindCourseNew method which uses our IdentityList<T> collection.

private Course FindCourseNew(int id)
{
    return _student.CurrentCourses.GetItemByID(id);
}

 

The code here is much cleaner, and there could be more opportunity for code reuse with the IdentityList<T>. Many of the dictionary type of methods could be adapted and used within this collection. You can download the sample code here. As always, any and all feedback is welcome!

 

 

Roland Weigelt’s GhostDoc for Visual Studio

 

I have been using Roland WeigeIt’s GhostDoc 2.1.3 for both Visual Studio 2005 and 2008 for about six months, and I am hooked. For those of you who have not heard of GhostDoc, here is the summary from Roland’s site:

GhostDoc is a free add-in for Visual Studio that automatically generates XML documentation comments for C#. Either by using existing documentation inherited from base classes or implemented interfaces, or by deducing comments from name and type of e.g. methods, properties or parameters.

It saves me so much time writing XML documentation in my code that there is no longer any excuse for any docs being missing. Documenting a piece of code is as simple as right-clicking on it and selcting ‘Document This’ (there is also a keyboard shortcut – CTRL-D is the default). The built-in rules for creating the comments are probably sufficient for most developers, but more advanced users can edit existing rules or add their own.

I have attached a small sample project containing some domain classes representing part of a pharmacy system. Here are a few examples of the XML documentation that is generated using GhostDoc’s default configuration. Some of the code is a bit contrived so that I could demonstrate different types of comments.

Documenting a Constructor
/// <summary>
/// Initializes a new instance of the <see cref="Drug"/> class.
/// </summary>
/// <param name="id">The id.</param>
public Drug(int id)
{
    LoadDrug(id);
}

Documenting a Dispose Method
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
    // TODO: clean up resources
}

 

Documenting Properties
/// <summary>
/// Gets or sets the therapeutic categories.
/// </summary>
/// <value>The therapeutic categories.</value>
public List<DrugCategory> TherapeuticCategories { get; set; }

/// <summary>
/// Gets or sets the NDC.
/// </summary>
/// <value>The NDC.</value>
public string NDC { get; set; }

/// <summary>
/// Gets or sets a value indicating whether this instance is generic.
/// </summary>
/// <value>
///     <c>true</c> if this instance is generic; otherwise, <c>false</c>.
/// </value>
public bool IsGeneric { get; set; }

 

Documenting a Method
/// <summary>
/// Loads the drug.
/// </summary>
/// <param name="id">The id.</param>
private void LoadDrug(int id)
{
    // do nothing... just an example
}

 

Documenting an Inherited/Implemented Method
/// <summary>
/// Creates a new object that is a copy of the current instance.
/// </summary>
/// <returns>
/// A new object that is a copy of this instance.
/// </returns>
public object Clone()
{
    return MemberwiseClone();
}

 

Documenting an Overridden Method
/// <summary>
/// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
/// </summary>
/// <returns>
/// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
/// </returns>
public override string ToString()
{
    return String.Format("{0} {1}", Name, DoseRoute);
}

 

Documenting an Event

/// <summary>
/// Occurs when [allergy detected].
/// </summary>
public event EventHandler AllergyDetected;

 

 

As you can see, the default configuration of GhostDoc is pretty intelligent. Here are some shots of the rules configuration options provided by default:

 

GhostDoc Rules Config

 

GhostDoc Rules Config 2

(I added the shading in the second shot to mask options that were visible in the previous one.)

 

Users have the ability to change any of these existing rules or add their own. This post is just a brief overview of the product, so I won’t go into details on how to configure a rule at this time. Let me know if you would be interested in more posts that dive into more details on the configuration of GhostDoc.

If you don’t already use it, I encourage you to download it and give it a try. The learning curve is not steep and the benefits are great. Plus, it’s free! One more quick note, VB support is experimental at this time, and it is turned off by default. To enable it, go to the Options tab on the config screen.

GhostDoc download links are here.

My sample project (VS2008) can be downloaded here.