使用LINQ连接字符串

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

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吗?

368267 次浏览

您看过聚合扩展方法了吗?

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

为什么使用Linq?

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

这是完美的工作,并接受任何IEnumerable<string>,据我所知。这里不需要Aggregate任何东西,这要慢得多。

我代码中的真实例子:

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

查询是一个具有Name属性的对象,该属性是一个字符串,我想要所选列表上所有查询的名称,用逗号分隔。

这个答案显示了问题中要求的LINQ (Aggregate)的使用,不用于日常使用。因为它没有使用StringBuilder,所以对于很长的序列,它的性能会很糟糕。对于常规代码,使用String.Join,如其他answer

像这样使用聚合查询:

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

An aggregate is a function that takes a collection of values and returns a scalar value. Examples from T-SQL include min, max, and sum. Both VB and C# have support for aggregates. Both VB and C# support aggregates as extension methods. Using the dot-notation, one simply calls a method on an IEnumerable object.

Remember that aggregate queries are executed immediately.

More information - MSDN: Aggregate Queries


If you really want to use Aggregate use variant using StringBuilder proposed in comment by CodeMonkeyKing which would be about the same code as regular String.Join including good performance for large number of objects:

 var res = words.Aggregate(
new StringBuilder(),
(current, next) => current.Append(current.Length == 0? "" : ", ").Append(next))
.ToString();
return string.Join(", ", strings.ToArray());

在。net 4中,有一个新的string.Join过载,它接受IEnumerable<string>。代码将如下所示:

return string.Join(", ", strings);

我总是使用扩展方法:

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

我之前写过一篇博客,我所做的正是你想要的:

http://ondevelopment.blogspot.com/2009/02/string-concatenation-made-easy.html

在博客文章中描述了如何实现工作在IEnumerable上的扩展方法,并命名为Concatenate,这将允许您编写如下内容:

var sequence = new string[] { "foo", "bar" };
string result = sequence.Concatenate();

或者更复杂的事情,比如:

var methodNames = typeof(IFoo).GetMethods().Select(x => x.Name);
string result = methodNames.Concatenate(", ");

这里有很多选择。你可以使用LINQ和StringBuilder,这样你就可以得到这样的性能:

StringBuilder builder = new StringBuilder();
List<string> MyList = new List<string>() {"one","two","three"};


MyList.ForEach(w => builder.Append(builder.Length > 0 ? ", " + w : w));
return builder.ToString();

StringBuilder vs Select &的快速性能数据;聚合案例超过3000个元素:

单元测试-持续时间(秒)
LINQ_StringBuilder - 0.0036644
LINQ_Select。聚合- 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);
}

你可以在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的事情。)

你可以非常有效地结合LINQ和string.join()。这里我正在从字符串中删除一个项。当然也有更好的方法,下面就是:

filterset = String.Join(",",
filterset.Split(',')
.Where(f => mycomplicatedMatch(f,paramToMatch))
);

我要稍微欺骗一下,给出一个新的答案,它似乎总结了这里所有最好的东西,而不是把它粘在注释里。

你可以这样一行:

List<string> strings = new List<string>() { "one", "two", "three" };


string concat = strings
.Aggregate(new StringBuilder("\a"),
(current, next) => current.Append(", ").Append(next))
.ToString()
.Replace("\a, ",string.Empty);

你可以先检查一个空的枚举数,或者在表达式的末尾添加一个.Replace("\a",string.Empty);。我想我可能是太聪明了。

来自@a.friend的答案可能会稍微更好一些,我不确定Replace与Remove相比在底层做了什么。唯一的另一个警告是,如果你想连接以\a结尾的字符串,你会失去分隔符…我觉得这不太可能。如果是这种情况,你确实有其他奇特的角色选择。

当我使用linq解析IIS日志文件时,我做了以下快速和肮脏的操作,它工作得非常好@ 100万行(15秒),尽管在尝试200万行时得到了内存溢出错误。

    static void Main(string[] args)
{


Debug.WriteLine(DateTime.Now.ToString() + " entering main");


// USED THIS DOS COMMAND TO GET ALL THE DAILY FILES INTO A SINGLE FILE: copy *.log target.log
string[] lines = File.ReadAllLines(@"C:\Log File Analysis\12-8 E5.log");


Debug.WriteLine(lines.Count().ToString());


string[] a = lines.Where(x => !x.StartsWith("#Software:") &&
!x.StartsWith("#Version:") &&
!x.StartsWith("#Date:") &&
!x.StartsWith("#Fields:") &&
!x.Contains("_vti_") &&
!x.Contains("/c$") &&
!x.Contains("/favicon.ico") &&
!x.Contains("/ - 80")
).ToArray();


Debug.WriteLine(a.Count().ToString());


string[] b = a
.Select(l => l.Split(' '))
.Select(words => string.Join(",", words))
.ToArray()
;


System.IO.File.WriteAllLines(@"C:\Log File Analysis\12-8 E5.csv", b);


Debug.WriteLine(DateTime.Now.ToString() + " leaving main");


}

我使用linq的真正原因是我之前需要的Distinct():

string[] b = a
.Select(l => l.Split(' '))
.Where(l => l.Length > 11)
.Select(words => string.Format("{0},{1}",
words[6].ToUpper(), // virtual dir / service
words[10]) // client ip
).Distinct().ToArray()
;

通过'超酷的LINQ方式',你可能在谈论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);
}
}

这将提供与直接调用一样的性能代码(至少在算法复杂性方面),并且在某些情况下可能使代码更具可读性(取决于上下文),特别是当块中的其他代码使用链式函数样式时。

这里它使用纯LINQ作为单个表达式:

static string StringJoin(string sep, IEnumerable<string> strings) {
return strings
.Skip(1)
.Aggregate(
new StringBuilder().Append(strings.FirstOrDefault() ?? ""),
(sb, x) => sb.Append(sep).Append(x));
}

而且非常快!

下面是我在看了其他答案和解决在一个类似的问题中(即聚合和连接失败的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解决方案。

FWIW我在使用BDN的15个字符串数组上对string.Join vs .Aggregate进行基准测试:

< span style=" font - family:宋体;">是< / th > < span style=" font - family:宋体;"> < / th >错误 < span style=" font - family:宋体;"> StdDev < / th > < span style=" font - family:宋体;"> Gen0 < / th > < span style=" font - family:宋体;"> < / th >分配 . . . . . . < span style=" font - family:宋体;"道明> > 2912 B < /
方法
String_Join 92.99 ns9.905 ns > 352 B < /
LING_Aggregate 406.00 ns74.662 ns4.092 ns0.4640

阵列越大,间隔越大