Concatenating Delimited Strings With Generic Delegates

archived comments edit

UPDATE: In my original example, I created my own delegate for converting objects to strings. Kevin Dente pointed out that there is already a perfectly fine delegate for this purpose, the Converter delegate. I updated my code to use that instead. Thanks Kevin!  Just shows you the size and depth of the Framework libraries.

My recent post on concatenating a delimited string sparked quite a bit of commentary.  The inspiration for that post was some code I had to write for a project.  One constraint that I neglected to mention was that I was restricted to .NET 1.1.  Today, I revisit this topic, but with the power of .NET 2.0 in my pocket.

Let’s make our requirements a bit more interesting, shall we?

In this scenario, I have a new class creatively named SomeClass.  This class has a property also creatively named, SomeDate (how do I come up with these imaginative names?!). 

class SomeClass
{
    public SomeClass(DateTime someDate)
    {
        this.SomeDate = someDate;
    }

    public DateTime SomeDate;
}

Suppose I want to concatenate instances of this class together, but this time I want a pipe delimited list of the number of days between now and the SomeDate value.  For example, given the date 11/23/2006, the string should have a “1” since that date was one day ago.  Yes, this is a contrived example, but it will do.

Now I’ll define a new Join method that can take in a delimiter, an enumeration, and an instance of the Converter delegate.  The Converter delegate has the following signature.

delegate TOutput Converter<TIn,TOutput> (TIn input)

As an argument to my Join method, I specify that TOutput should be a string, leaving the input to remain generic.

public static string Join<T>(string delimiter
                             , IEnumerable<T> items
                             , Converter<T, string> converter)
{
    StringBuilder builder = new StringBuilder();
    foreach(T item in items)
    {
        builder.Append(converter(item));
        builder.Append(delimiter);
    }
    if (builder.Length > 0)
        builder.Length = builder.Length - delimiter.Length;

    return builder.ToString();
}

Now with this method defined, I can concatenate an array or collection of SomeClass instances like so:

SomeClass[] someClasses = new SomeClass[]
{
  new SomeClass(DateTime.Parse("1/23/2006"))
  , new SomeClass(DateTime.Parse("12/25/2005"))
  , new SomeClass(DateTime.Parse("5/25/2004"))
};

string result = Join<SomeClass>(|, someClasses
  , delegate(SomeClass item)
    {
        TimeSpan ts = DateTime.Now - item.SomeDate;
      return ((int)ts.TotalDays).ToString();
    });

Console.WriteLine(result);

Notice that I make use of an anonymous delegate that examines an instance of SomeClass and calculates the number of days that SomeDate is in the past.  This returns a string that will be concatenated together.

This code produces the following output.

305|334|913

This gives me a nice reusable method to concatenate collections of objects into delimited strings via the Converter generic delegate. This follows a common pattern in .NET 2.0 embodied by such methods as the List.ForEach method which uses the Action generic delegate and the Array.Find method which uses the Predicate generic delegate.

Comments