使用LINQ连接字符串

405

最有效的编写老派代码的方法是什么:

StringBuilder sb = new StringBuilder();
if (strings.Count > 0)
{
    foreach (string s in strings)
    {
        sb.Append(s + ", ");
    }
    sb.Remove(sb.Length - 2, 2);
}
return sb.ToString();

如何在LINQ中使用?


1
你发现了其他超酷的 LINQ 实现方式吗? - Robert S.
4
在Linq to Entities中,所选答案和其他选项都无法实现。 - Binoj Antony
3
@Binoj Antony,请勿让您的数据库执行字符串连接操作。 - Amy B
6
@Pr0fess0rX说:“因为它不能,而且也不应该这样做。我不知道其他数据库,但在SQL Server中,你只能连接(n)varchar类型的数据,这将限制你只能使用(n)varchar(max)。不应该这样做,因为业务逻辑不应该在数据层中实现。” - the_drow
没有任何一个答案适用于EntityFramework-请查看我在已标记答案下方的评论。 有人知道解决方案吗? - Matt
显示剩余2条评论
17个回答

579
这个答案展示了如何使用LINQ(Aggregate),以满足问题的要求,但不适用于日常使用。因为它没有使用StringBuilder,所以对于非常长的序列来说性能很差。在正常的代码中,请使用如其他answer所示的String.Join
使用类似以下的聚合查询:
string[] words = { "one", "two", "three" };
var res = words.Aggregate(
   "", // start with empty string to handle empty list case.
   (current, next) => current + ", " + next);
Console.WriteLine(res);

这将输出:

,one,two,three

聚合函数是一种接受值集合并返回标量值的函数。在 T-SQL 中的示例包括 min、max 和 sum。VB 和 C# 都支持聚合函数。VB 和 C# 都支持将聚合函数作为扩展方法。使用点符号表示法,只需在 IEnumerable 对象上调用一个方法。

请记住,聚合查询会立即执行。

更多信息 - MSDN: 聚合查询


如果您真的想使用Aggregate,请使用CodeMonkeyKing在评论中提出的使用StringBuilder的变体,这将是与常规String.Join相同的代码,包括大量对象的良好性能。
 var res = words.Aggregate(
     new StringBuilder(), 
     (current, next) => current.Append(current.Length == 0? "" : ", ").Append(next))
     .ToString();

4
第一个例子输出的不是"one, two, three",而是", one, two, three"(注意前面有逗号)。 - Mort
在你的第一个例子中,由于你使用了 "" 作为种子值,所以 current 中使用的第一个值是空字符串。因此,对于一个或多个元素,你总会在字符串开头得到 , - Michael Yanni
@Mort,我已经修复了这个问题。 - sergtk

436
return string.Join(", ", strings.ToArray());
在 .Net 4 中, string.Join 有一个新的重载方法,接受 IEnumerable<string>。代码看起来像这样:
return string.Join(", ", strings);

3
好的,所以这个解决方案没有使用Linq,但是对我来说似乎工作得很好。 - Mat Roberts
27
这是最正确的答案。它比问题和被接受的答案都快,并且比聚合函数更清晰,每次使用聚合函数时需要写一段解释。 - PRMan

137

为什么要使用Linq?

string[] s = {"foo", "bar", "baz"};
Console.WriteLine(String.Join(", ", s));

据我所记,这可以完美地运行并接受任何 IEnumerable<string>。这里不需要使用Aggregate,因为它会慢很多。


10
.NET 4.0新增了IEnumerable<string>和IEnumerable<T>的重载,这将使其更容易使用。 - Cine
4
正如Cine所指出的,.NET 4.0有这种重载方式,而早期版本则没有。但在旧版本中仍然可以使用String.Join(",", s.ToArray()) - Martijn
1
FYI:合并自http://stackoverflow.com/questions/122670/what-is-the-linq-way-to-implode-join-a-string-array - Shog9
@Shog9 合并会使这里的答案看起来像是重复努力,时间戳一点也不有用。但仍然是前进的道路。 - nawfal
@Armin:如果您的源是流数据而不是有限的已知大小集合,则可能非常有用。此外,流式传输可能是因为数据逐步到达。然后,LINQ解决方案可以在数据到达时处理数据,而无需等待整个集合接收。这样,例如,如果只需要处理对象的一个值,则将其连接起来,丢弃复杂对象,然后可以回收它。 - bkqc

81

你看过Aggregate扩展方法吗?

var sa = (new[] { "yabba", "dabba", "doo" }).Aggregate((a,b) => a + "," + b);

25
这可能比String.Join()慢,代码中也更难读。不过,这确实回答了“使用LINQ的方式”的问题 :-) - Chris Wenham
7
好的,我不想用我的观点影响答案。:P - Robert S.
2
毫无疑问,实际上它要慢得多。即使使用StringBuilder和Aggregate来代替字符串拼接,速度也比String.Join慢。 - Joel Mueller
4
进行了一次1000万次迭代的测试,聚合花费4.3秒,而string.join则花费2.3秒。因此我认为性能差异对于99%的常见用例来说并不重要。所以如果你已经在使用大量的LINQ处理数据,通常没有必要打破那种简洁的语法并使用string.join。 - joeriks
1
FYI:合并自http://stackoverflow.com/questions/122670/what-is-the-linq-way-to-implode-join-a-string-array - Shog9
显示剩余3条评论

60

我的代码中的真实例子:

return selected.Select(query => query.Name).Aggregate((a, b) => a + ", " + b);

查询是一个对象,它有一个名为Name的属性,该属性是一个字符串。我想要选定列表中所有查询的名称,并以逗号分隔。


2
鉴于性能方面的评论,我应该补充说明一下,这个示例是来自代码的一部分,当对话框关闭时只运行一次,并且列表不太可能超过十个字符串! - Daniel Earwicker
1
有什么提示可以在Linq to Entities中完成相同的任务吗? - Binoj Antony
1
很棒的例子。谢谢你将其放入实际场景中。我也遇到了完全相同的情况,需要对对象的属性进行连接。 - Jessy Houle
1
感谢您帮助我解决选择List<T>字符串属性的第一部分。 - Nikki9696
1
请写一篇关于使用更大的数组时该方法的性能表现的文章。 - Giulio Caccin

34

下面是我在查看其他答案和类似问题(即Aggregate和Concatenate在0个元素时会失败)后决定采用的结合了Join/Linq的方法:

string Result = String.Join(",", split.Select(s => s.Name));

或者(如果s不是字符串)

string Result = String.Join(",", split.Select(s => s.ToString()));

  • 简单易用
  • 容易理解和阅读
  • 适用于泛型元素
  • 允许使用对象或对象属性
  • 处理0长度元素的情况
  • 可以与其他Linq过滤器一起使用
  • 表现良好(至少在我的经验中)
  • 不需要(手动)创建额外的对象(例如StringBuilder)来实现

当然,Join会处理有时会出现的讨人厌的末尾逗号(如forforeach),这也是我最初寻找Linq解决方案的原因。


1
括号不匹配。 - ctrl-alt-delor
1
FYI:合并自http://stackoverflow.com/questions/122670/what-is-the-linq-way-to-implode-join-a-string-array - Shog9
4
我喜欢这个答案,因为像这样使用.Select()提供了一个很容易的地方来修改每个元素。例如,像这样将每个项目包装在某个字符中string Result = String.Join(",", split.Select(s => "'" + s + "'")); - Sam Storie

29
您可以在Aggregate中使用StringBuilder:
  List<string> strings = new List<string>() { "one", "two", "three" };

  StringBuilder sb = strings
    .Select(s => s)
    .Aggregate(new StringBuilder(), (ag, n) => ag.Append(n).Append(", "));

  if (sb.Length > 0) { sb.Remove(sb.Length - 2, 2); }

  Console.WriteLine(sb.ToString());

(Select只是为了展示你可以在LINQ中做更多的事情。)


2
+1不错。但是,在我看来,最好避免添加额外的“,”,而不是在之后擦除它。可以使用以下代码:new[] {"one", "two", "three"}.Aggregate(new StringBuilder(), (sb, s) =>{if (sb.Length > 0) sb.Append(", ");sb.Append(s);return sb;}).ToString(); - dss539
5
如果不在linq中检查if (length > 0)并将其删除,您可以节省宝贵的时钟周期。 - Binoj Antony
1
我同意dss539的观点。我的版本类似于new[] {"", "one", "two", "three"}.Aggregate(new StringBuilder(), (sb, s) => (String.IsNullOrEmpty(sb.ToString())) ? sb.Append(s) : sb.Append(", ").Append(s)).ToString(); - ProfNimrod
1
@ProfNimrod,您的代码在每次迭代中将StringBuffer转换为字符串(sb.ToString())。 (它还检查永远不可能为空的东西是否为空。)您完全失去了使用StringBuffer的优势,这与仅串联字符串一样糟糕。 - andrewf

22

以下是针对 3000 个元素,StringBuilder 和 Select & Aggregate 的快速性能数据:

单元测试 - 持续时间(秒)
LINQ_StringBuilder - 0.0036644
LINQ_Select.Aggregate - 1.8012535

    [TestMethod()]
    public void LINQ_StringBuilder()
    {
        IList<int> ints = new List<int>();
        for (int i = 0; i < 3000;i++ )
        {
            ints.Add(i);
        }
        StringBuilder idString = new StringBuilder();
        foreach (int id in ints)
        {
            idString.Append(id + ", ");
        }
    }
    [TestMethod()]
    public void LINQ_SELECT()
    {
        IList<int> ints = new List<int>();
        for (int i = 0; i < 3000; i++)
        {
            ints.Add(i);
        }
        string ids = ints.Select(query => query.ToString())
                         .Aggregate((a, b) => a + ", " + b);
    }

有助于决定不使用LINQ路线来完成此任务。 - crabCRUSHERclamCOLLECTOR
4
时间差异可能是由于使用StringBuilder与使用+进行字符串拼接造成的,与LINQ或Aggregate无关。将StringBuilder放入LINQ Aggregate中(Stack Overflow上有很多示例),它应该同样快。 - controlbox

21

我总是使用扩展方法:

public static string JoinAsString<T>(this IEnumerable<T> input, string seperator)
{
    var ar = input.Select(i => i.ToString());
    return string.Join(seperator, ar);
}

5
在.NET 4中,string.Join可以接受任意类型TIEnumerable<T> - recursive
1
FYI:合并自http://stackoverflow.com/questions/122670/what-is-the-linq-way-to-implode-join-a-string-array - Shog9

13

“超酷LINQ方式” 可能是指使用扩展方法让函数式编程更易接受的方式。我指的是一种语法糖,它允许将函数以视觉线性方式(一个接一个)连接在一起,而不是嵌套在一起。例如:

int totalEven = Enumerable.Sum(Enumerable.Where(myInts, i => i % 2 == 0));

可以这样写:

int totalEven = myInts.Where(i => i % 2 == 0).Sum();

第二个例子更易于阅读,同时您还可以看到如何使用更少的缩进问题或Lispy关闭圆括号出现在表达式末尾时添加更多的函数。

很多其他答案都说String.Join是最快或最简单的方式。但如果你采纳我的“超酷LINQ方式”的解释,那么答案是使用String.Join并将其包装在一个LINQ风格的扩展方法中,这样您就可以以视觉上令人愉悦的方式链接您的函数。因此,如果您想编写sa.Concatenate(", "),只需创建类似于以下内容的东西:

public static class EnumerableStringExtensions
{
   public static string Concatenate(this IEnumerable<string> strings, string separator)
   {
      return String.Join(separator, strings);
   }
}

这将提供与直接调用相同的性能代码(至少从算法复杂度的角度),在某些情况下可能会使代码更易读(取决于上下文), 特别是如果块中的其他代码使用链式函数风格。


1
这个帖子中的错别字数量太多了:seperator => separator,Concatinate => Concatenate。 - SilverSideDown
1
请注意:此内容来自http://stackoverflow.com/questions/122670/what-is-the-linq-way-to-implode-join-a-string-array。请将其合并。 - Shog9

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接