Concatenating Delimited Strings With Generic Delegates

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.

What others have said

Requesting Gravatar... Kevin Dente Nov 24, 2006 9:21 PM
# re: Concatenating Delimited Strings With Generic Delegates
Why define a new kind of delegate when you can just use the built-in Converter<> delegate?
Requesting Gravatar... Haacked Nov 25, 2006 1:06 AM
# re: Concatenating Delimited Strings With Generic Delegates
I should've known there'd already be a perfect delegate already defined! I'll update my code example.
Requesting Gravatar... Matt Lester Nov 25, 2006 4:53 AM
# re: Concatenating Delimited Strings With Generic Delegates
<p>Hey there,</p>

<p>This is excellent stuff. I had a similar problem and took a slightly different appproach. I made a wholly inferior version of Map. And now I'm at home, and don't have the original code to refer to.</p>

<p>First, I made a generic MapDelegate (should have looked for an existing delegate):</p>

<div class="code">public delegate TOut MapDelegate&lt;TIn,TOut&gt;(TIn Input)</div>

<p>And added a generic Map function:</p>

<div class="code">
public IEnumerable&lt;TOut&gt;Map&lt;TIn,TOut&gt;(IEnumerable&lt;TIn&gt; inputs,MapDelegate&lt;TIn,TOut&gt; mapFunction)
{
List&lt;TOut&gt; result = new List&lt;TOut&gt;();
foreach (TIn input in inputs)
{
result.Add(mapFunction(input));
}
return result;
}

</div>
Requesting Gravatar... Grim Nov 25, 2006 5:05 AM
# re: Concatenating Delimited Strings With Generic Delegates
Why not go back to your string.Join() method, but use the Generic List<>.ToArray() instead of a hard string[]?

public static string Join<T>(
char delimiter,
IEnumerable<T> items,
Converter<T, string> converter
)
{
List<string> stringList = new List<string>();

foreach (T item in items)
stringList.Add(converter(item));

return string.Join(
delimiter.ToString(),
stringList.ToArray()
);
}

Requesting Gravatar... Kevin Dente Nov 25, 2006 8:13 AM
# re: Concatenating Delimited Strings With Generic Delegates
Since Code Lives Forever on the net, I'll make one more niggly suggestion (boy, everyone's a critic, aren't they). Having the delimiter passed in as a string rather than a char (as it is in String.Join) would make the function a little more flexible.

For example, I've had need in the past to specify ",<space>" as the delimiter for a list of things presented in a UI.

Requesting Gravatar... Haacked Nov 25, 2006 10:33 AM
# re: Concatenating Delimited Strings With Generic Delegates
@Kevin: Good point. I made that change.

@Grim: Either way works just fine.
Requesting Gravatar... Kevin Dente Nov 25, 2006 11:00 AM
# re: Concatenating Delimited Strings With Generic Delegates
Er..you need to adjust the trailing delimiter stripping code to account for the fact that it could be more than one character now.
Requesting Gravatar... Haacked Nov 25, 2006 3:25 PM
# re: Concatenating Delimited Strings With Generic Delegates
Done! See, I knew there was a reason I chose "char". ;)
Requesting Gravatar... Victor Robinson Nov 27, 2006 7:24 AM
# re: Concatenating Delimited Strings With Generic Delegates
Very slick!

See - that's why I love C# and NET 2.0.
Requesting Gravatar... DotNetKicks.com Nov 28, 2006 9:11 AM
# Concatenating Delimited Strings With Generic Delegates
You've been kicked (a good thing) - Trackback from DotNetKicks.com
Requesting Gravatar... Richard Olson Mar 03, 2008 9:25 AM
# re: Concatenating Delimited Strings With Generic Delegates
Not a comment, just a question: Could this process be used to convert strings of data from cell phone text messages into delimited strings? Like, say a customer texting a specific pizza order (pan crust extra cheese pepperoni family size) to a pizza shop? Or a taxpayer texting his tax payment to a government office (bank routing number, account number, amount of payment)?

What do you have to say?

(will show your gravatar)
Please add 5 and 6 and type the answer here: