To start with I\'ll say that I agree that goto statements are largely made irrelevant by higher level constructs in modern programming languages and shouldn\'t be used when
You can certainly create a goto
solution in C# (note: I didn't add null
checks):
string Join(string[] array, string delimiter) {
var sb = new StringBuilder();
var enumerator = array.GetEnumerator();
if (enumerator.MoveNext()) {
goto start;
loop:
sb.Append(delimiter);
start: sb.Append(enumerator.Current);
if (enumerator.MoveNext()) goto loop;
}
return sb.ToString();
}
For your specific example, this looks pretty straighforward to me (and it's one of the solutions you described):
string Join(string[] array, string delimiter) {
var sb = new StringBuilder();
foreach (string element in array) {
sb.Append(element);
sb.Append(delimiter);
}
if (sb.Length >= delimiter.Length) sb.Length -= delimiter.Length;
return sb.ToString();
}
If you want to get functional, you can try using this folding approach:
string Join(string[] array, string delimiter) {
return array.Aggregate((left, right) => left + delimiter + right);
}
Although it reads really nice, it's not using a StringBuilder
, so you might want to abuse Aggregate
a little to use it:
string Join(string[] array, string delimiter) {
var sb = new StringBuilder();
array.Aggregate((left, right) => {
sb.Append(left).Append(delimiter).Append(right);
return "";
});
return sb.ToString();
}
Or you can use this (borrowing the idea from other answers here):
string Join(string[] array, string delimiter) {
return array.
Skip(1).
Aggregate(new StringBuilder(array.FirstOrDefault()),
(acc, s) => acc.Append(delimiter).Append(s)).
ToString();
}
There are ways you "can" get around the doubled code, but in most cases the duplicated code is much less ugly/dangerous than the possible solutions. The "goto" solution you quote doesn't seem like an improvement to me - I don't really think you really gain anything significant (compactness, readability or efficiency) by using it, while you increase the risk of a programmer getting something wrong at some point in the code's lifetime.
In general I tend to go for the approach:
This removes the inefficiencies introduced by checking if the loop is in the first iteration on every time around, and is really easy to understand. For non-trivial cases, using a delegate or helper method to apply the action can minimise code duplication.
Or another approach I use sometimes where efficiency isn't important:
This can be written to be more compact and readable than the goto approach, and doesn't require any extra variables/storage/tests to detect the "special case" iteraiton.
But I think Mark Byers' approach is a good clean solution for your particular example.
If you want to go the functional route, you could define String.Join like LINQ construct that is reusable across types.
Personally, I would almost always go for code clarity over saving a few opcode executions.
EG:
namespace Play
{
public static class LinqExtensions {
public static U JoinElements<T, U>(this IEnumerable<T> list, Func<T, U> initializer, Func<U, T, U> joiner)
{
U joined = default(U);
bool first = true;
foreach (var item in list)
{
if (first)
{
joined = initializer(item);
first = false;
}
else
{
joined = joiner(joined, item);
}
}
return joined;
}
}
class Program
{
static void Main(string[] args)
{
List<int> nums = new List<int>() { 1, 2, 3 };
var sum = nums.JoinElements(a => a, (a, b) => a + b);
Console.WriteLine(sum); // outputs 6
List<string> words = new List<string>() { "a", "b", "c" };
var buffer = words.JoinElements(
a => new StringBuilder(a),
(a, b) => a.Append(",").Append(b)
);
Console.WriteLine(buffer); // outputs "a,b,c"
Console.ReadKey();
}
}
}