Disclaimer I used this as an excuse to play with new technologies, so my solutions do not meet Eric's original requirements for clarity and ease of maintenance.
Enumerator's naive solution (I admit that the foreach option for this is excellent, as it does not require manual tinkering with the enumerator.)
public static string NaiveConcatenate(IEnumerable<string> sequence) { StringBuilder sb = new StringBuilder(); sb.Append('{'); IEnumerator<string> enumerator = sequence.GetEnumerator(); if (enumerator.MoveNext()) { string a = enumerator.Current; if (!enumerator.MoveNext()) { sb.Append(a); } else { string b = enumerator.Current; while (enumerator.MoveNext()) { sb.Append(a); sb.Append(", "); a = b; b = enumerator.Current; } sb.AppendFormat("{0} and {1}", a, b); } } sb.Append('}'); return sb.ToString(); }
LINQ Solution
public static string ConcatenateWithLinq(IEnumerable<string> sequence) { return (from item in sequence select item) .Aggregate( new {sb = new StringBuilder("{"), a = (string) null, b = (string) null}, (s, x) => { if (sa != null) { s.sb.Append(sa); s.sb.Append(", "); } return new {s.sb, a = sb, b = x}; }, (s) => { if (sb != null) if (sa != null) s.sb.AppendFormat("{0} and {1}", sa, sb); else s.sb.Append(sb); s.sb.Append("}"); return s.sb.ToString(); }); }
Solution with TPL
This solution uses a producer-consumer queue to supply an input sequence to the processor, while storing at least two elements buffered in the queue. As soon as the manufacturer reaches the end of the input sequence, the last two elements can be processed with special processing.
In retrospect, there is no reason for the consumer to work asynchronously, which would eliminate the need for a parallel queue, but, as I said earlier, I just used this as an excuse to play with new technologies :-)
public static string ConcatenateWithTpl(IEnumerable<string> sequence) { var queue = new ConcurrentQueue<string>(); bool stop = false; var consumer = Future.Create( () => { var sb = new StringBuilder("{"); while (!stop || queue.Count > 2) { string s; if (queue.Count > 2 && queue.TryDequeue(out s)) sb.AppendFormat("{0}, ", s); } return sb; });
Refined tests for brevity.