Implode Extension Method for IList and IEnumerable in C#/.net

One nice function in PHP is the implode function. Update: Embarrassing mistake, I actually confused implode and explode and originally called this "Explode Extension Method". Explode takes a string and returns an array, Implode does the opposite. Thanks Cédric for pointing that out!

It takes an array and a desired delimiter and returns a string. I wanted something like this for a List<String> in C#, so here is an extension method for that:

public static string Implode(this IList input, string delimiter)
{
    if (input.Count == 0) return string.Empty;
    if (input.Count == 1) return input[0].ToString();

    var sb = new StringBuilder();
    for (int i = 0; i < input.Count - 1; i++)
    {
        sb.AppendFormat("{0}{1}", input[i],delimiter);
    }
    sb.Append(input[input.Count - 1]);
    return sb.ToString();
}

This works with any IList as it calls ToString() on every object - naturally this only works well if your object actually has a valid ToString override. Can we improve this? Of course! First action would be to pass in a function that does the conversion - a Func<Object,String> that takes an object (our object) and returns a string.

public static string Implode(this IList input, string delimiter,
                             Func<object,string> stringFunction)
{
    if (input.Count == 0) return string.Empty;
    if (input.Count == 1) return stringFunction.Invoke(input[0]);

    var sb = new StringBuilder();
    for (int i = 0; i < input.Count - 1; i++)
    {
        sb.AppendFormat("{0}{1}", stringFunction.Invoke(input[i]), delimiter);
    }
    sb.Append(input[input.Count - 1]);
    return sb.ToString();
}

Our simpler overload then just becomes:

public static string Implode(this IList input, string delimiter)
{
    return input.Implode(delimiter, obj => obj.ToString());
}

The neat thing is that even the System.Array class is an IList, so this should be useful in most cases - except for IEnumerable.

Now, we can easily create a function like this for IEnumerable, but there is a performance overhead. We need to iterate through the entire IEnumerable at least once, and depending on the source of the IEnumerable, there may be big performance constraints or even side effects (i.e., if the source function accesses an external resource on every iteration). Here are the two extension methods, both Generic and Non-Generic:

Note that I can't call them Implode - IList inherits IEnumerable, so having an extension method both on IList and IEnumerable would result in an ambiguity.

public static string ImplodeIEnumerable(this IEnumerable input, string delimiter,
                                        Func<Object, String> stringFunction)
{
    var tmpList = new List<string>();
    foreach(var obj in input)
    {
        tmpList.Add(stringFunction.Invoke(obj));
    }
    return tmpList.Implode(delimiter);
}

public static string ImplodeIEnumerable<T>(this IEnumerable<T> input, string delimiter,
                                           Func<Object, String> stringFunction)
{
    var tmpList = new List<T>(input);
    return tmpList.Implode(delimiter, stringFunction);
}

I'm not entirely happy with these two as they don't seem very efficient (if you have a large IEnumerable of large objects and an expensive stringFunction), but it's a lot of code reuse that way. Let's take a step back and look at the above statement again: IList inherits IEnumerable. Array is an IList. So instead of making IList.Implode() our "main" function, we should maybe focus on IEnumerable, as this would then work on all Collections. So here is a proper extension method for IEnumerable, that works with both generic and non-generic objects:

public static string ToDelimitedString(this IEnumerable input, string delimiter,
                                       Func<Object, String> stringFunction)
{
    var sb = new StringBuilder();
    int count = 0;
    foreach(var obj in input)
    {
        count++;
        sb.AppendFormat("{0}{1}", stringFunction.Invoke(obj), delimiter);
    }
    if(count > 0)
    {
        // We need to remove the last delimiter
        sb.Remove(sb.Length - delimiter.Length, delimiter.Length);
    }
    return sb.ToString();
}

public static string ToDelimitedString(this IEnumerable input, string delimiter)
{
    return input.ToDelimitedString(delimiter, x => x.ToString());
}

public static string ToDelimitedString(this IEnumerable input)
{
    return input.ToDelimitedString(",");
}

Just always keep in mind: Iterating through an IEnumerable may have side effects, for example the object implementing IEnumerable might hold a connection through a database and iterate through rows of a result set. Or it might hold a network connection and read from it without buffering anything, making it impossible to iterate through it twice. If your IEnumerable has any side effects that only safely allows to iterate through it once, I recommend doing this in your business logic and populating a separate List or even not using an Extension Method in the first place.

Comments (7)

Jesse C. SlicerJanuary 4th, 2010 at 23:00

You may want to have your final override which looks like

public static string ToDelimitedString(this IEnumerable input)
{
return input.ToDelimitedString(",");
}

to actually read

public static string ToDelimitedString(this IEnumerable input)
{
return input.ToDelimitedString(System.Threading.Thread.CurrentThread.CurrentCulture.TextInfo.ListSeparator);
}

To be thread- and culture-safe.

mstumJanuary 5th, 2010 at 02:07

Didn't know that ListSeparator exists, thanks! Well, that override is mainly for the user to choose, it's more of an aesthetic choice (i.e. Trailing Space or not? "," vs. ", "), but it's a good idea.

Thread Safety is actually a good point, because technically none of this is thread-safe, as the list might be modified by another thread halfway through. I just always assume that people who use Threads know how to use them properly 🙂

Bill WoodruffJanuary 5th, 2010 at 05:58

Thanks for a very interesting article and code samples ! Just curious : if you compared performance for creating a delimited string for very large List of the extension method vs. just traversing the List with a 'foreach and using a StringBuilder, would you expect any performance (time, memory) differences ?

mstumJanuary 5th, 2010 at 06:26

I haven't made _any_ performance test here, but I don't see why one would be faster than the other. Internally, the extension method does just that: Traverse the list with foreach and append to a string builder. The overhead for the call to the method should be insignificant (as the function is only called once per list, not once per item and as the compiler turns the extension method into a normal method in a separate class). Not sure if the Func adds any noticeable overhead, I would check how the compiler optimizes this in a release build. As said, I have not made any performance testing as I usually deal with small lists (<1000 items) where code readability counts more than raw performance.

Jesse C. SlicerJanuary 5th, 2010 at 15:38

By the by, I find this to be particularly useful and brilliant for something I'm currently working with. Thank you for the post!

ctApril 9th, 2010 at 00:46

I wrote this particular method like this:

public static string JoinString(this IEnumerable c, string d, Action cb)
{
if (cb == null) cb = (T i, StringBuilder sb) => sb.Append(i);

IEnumerator cEnum = c.GetEnumerator();
string result = string.Empty;

if (cEnum.MoveNext())
{
var buffer = new StringBuilder();
cb(cEnum.Current, buffer);

while (cEnum.MoveNext())
{
buffer.Append(d);
cb(cEnum.Current, buffer);
}

result = buffer.ToString();
}

return result;
}

ÉrickNovember 4th, 2010 at 18:22

string[] pieces = new string[]{
"she loves me",
"she loves me not"
};

string glue = ", ";

string resultingString = String.join(glue, pieces);