将分隔符添加到要显示的项目列表中

我有一个项目列表,我希望在C#中显示它们之间的分隔符。 使用普通的迭代器,我会在开头或结尾处添加一个额外的分隔符:

string[] sa = {"one", "two", "three", "four"}; string ns = ""; foreach(string s in sa) { ns += s + " * "; } // ns has a trailing *: // one * two * three * four * 

现在我可以使用for循环来解决这个问题:

 ns = ""; for(int i=0; i<sa.Length; i++) { ns += sa[i]; if(i != sa.Length-1) ns += " * "; } // this works: // one * two * three * four 

虽然第二种解决方案有效但看起来并不优雅。 有一个更好的方法吗?

您需要内置的String.Join方法:

 string ns = string.Join(" * ", sa); 

如果你想对其他集合类型做同样的事情,那么如果你首先使用LINQ的ToArray方法创建一个数组,你仍然可以使用String.Join

 string ns = string.Join(" * ", test.ToArray()); 

除了优雅之外,您可能还需要考虑除String之外的其他类型的速度和可重用性。 为了优雅,我建议使用扩展方法来抽象细节,以便常见的用法看起来像:

 ns = sa.Join(" * "); 

对于速度,请考虑以下变体测试,包括已回答问题的其他人提出的一些解决方案:

 public void Test_variants() { const string item = "a"; const int numberOfTimes = 100000; const string delimiter = ", "; string[] items = new List(Enumerable.Repeat(item, numberOfTimes)).ToArray(); string expected = String.Join(delimiter, items); Time(StringJoin, items, delimiter, expected); Time(Aggregate, items, delimiter, expected); Time(CheckForEndInsideLoop_String, items, delimiter, expected); Time(CheckForBeginningInsideLoop_String, items, delimiter, expected); Time(RemoveFinalDelimiter_String, items, delimiter, expected); Time(CheckForEndInsideLoop_StringBuilder, items, delimiter, expected); Time(RemoveFinalDelimiter_StringBuilder, items, delimiter, expected); } private static void Time(Func func, string[] items, string delimiter, string expected) { Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); string result = func(items, delimiter); stopwatch.Stop(); bool isValid = result == expected; Console.WriteLine("{0}\t{1}\t{2}", stopwatch.Elapsed, isValid, func.Method.Name); } private static string CheckForEndInsideLoop_String(string[] items, string delimiter) { string result = ""; for (int i = 0; i < items.Length; i++) { result += items[i]; if (i != items.Length - 1) { result += delimiter; } } return result; } private static string RemoveFinalDelimiter_String(string[] items, string delimiter) { string result = ""; for (int i = 0; i < items.Length; i++) { result += items[i] + delimiter; } return result.Substring(0, result.Length - delimiter.Length); } private static string CheckForBeginningInsideLoop_String(string[] items, string delimiter) { string result = ""; foreach (string s in items) { if (result.Length != 0) { result += delimiter; } result += s; } return result; } private static string CheckForEndInsideLoop_StringBuilder(string[] items, string delimiter) { StringBuilder result = new StringBuilder(); for (int i = 0; i < items.Length; i++) { result.Append(items[i]); if (i != items.Length - 1) { result.Append(delimiter); } } return result.ToString(); } private static string RemoveFinalDelimiter_StringBuilder(string[] items, string delimiter) { StringBuilder result = new StringBuilder(); for (int i = 0; i < items.Length; i++) { result.Append(items[i]); result.Append(delimiter); } result.Length = result.Length - delimiter.Length; return result.ToString(); } private static string StringJoin(string[] items, string delimiter) { return String.Join(delimiter, items); } private static string Aggregate(string[] items, string delimiter) { return items.Aggregate((c, s) => c + delimiter + s); } 

我的方框上的结果如下:

 00:00:00.0027745 True StringJoin
 00:00:24.5523967 True Aggregate
 00:00:47.8091632 True CheckForEndInsideLoop_String
 00:00:47.4682981 True CheckForBeginningInsideLoop_String
 00:00:23.7972864 True RemoveFinalDelimiter_String
 00:00:00.0076439 True CheckForEndInsideLoop_StringBuilder
 00:00:00.0052803 True RemoveFinalDelimiter_StringBuilder

这意味着你最好的选择,如果你只使用字符串数组,String.Join紧跟StringBuilder变种。 请注意,在使用字符串时检查循环中的最后一项比使用StringBuilder时产生的差异要大得多。 当要分隔的项目列表很小时,基于字符串的实现的性能也会提高很多。 我将numberOfItems设置为10运行相同的测试,并收到以下结果:

 00:00:00.0001788 True StringJoin
 00:00:00.0014983 True Aggregate
 00:00:00.0001666 True CheckForEndInsideLoop_String
 00:00:00.0002202 True CheckForBeginningInsideLoop_String
 00:00:00.0002061 True RemoveFinalDelimiter_String
 00:00:00.0002663 True CheckForEndInsideLoop_StringBuilder
 00:00:00.0002278 True RemoveFinalDelimiter_StringBuilder

您可能要考虑的下一件事是可重用性。 如果你想从一个由分隔符String.Join分隔的整数列表中构建一个字符串,那么在每个整数上运行.ToString()并创建一个字符串数组之后它只是一个选项(因为String.Join不能对IEnumerable起作用) <字符串>)。

因此,总而言之,您可以考虑使用以下几行的扩展方法来获得优雅,速度和可重用性的良好组合:

 public static string Join([CanBeNull] this IEnumerable items, [CanBeNull] string delimiter) { StringBuilder result = new StringBuilder(); if (items != null && items.Any()) { delimiter = delimiter ?? ""; foreach (T item in items) { result.Append(item); result.Append(delimiter); } result.Length = result.Length - delimiter.Length; } return result.ToString(); } 

用法:

 ns = sa.Join(" * "); 

这种方法的优点是你可以在任何类型的序列上使用它,而不仅仅是字符串数组。

 var ns = sa.Aggregate( (c, s) => c + " * " + s); 

我更喜欢卢克的解决方案。

 string ns = string.Join(" * ", sa); 

或者,如果您的集合不可索引但只是可枚举,则可以执行此操作:

 string ns = ""; foreach(string s in sa) { if (ns.Length != 0) { ns += " * "; } ns += s; } 

这就像你的第二个例子,但是它将测试置于循环的开始,并且不太可能像第二个例子中那样遇到一次性错误。 数组显然是可索引的,但在某些情况下,你会得到不可索引的容器(即System.Collections.Generic.Dictionary .Values),你会想要这样的东西。