Reputation: 84315
What is the most efficient way to write the old-school:
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();
...in LINQ?
Upvotes: 410
Views: 408522
Reputation: 60556
FWIW I benchmarked string.Join
vs .Aggregate
on a string array of 15 strings using BDN:
Method | Mean | Error | StdDev | Gen0 | Allocated |
---|---|---|---|---|---|
String_Join | 92.99 ns | 9.905 ns | 0.543 ns | 0.0560 | 352 B |
LING_Aggregate | 406.00 ns | 74.662 ns | 4.092 ns | 0.4640 | 2912 B |
The gap increases with bigger arrays
Upvotes: 1
Reputation: 8890
I always use the extension method:
public static string JoinAsString<T>(this IEnumerable<T> input, string seperator)
{
var ar = input.Select(i => i.ToString());
return string.Join(seperator, ar);
}
Upvotes: 22
Reputation: 97839
This answer shows usage of LINQ (Aggregate
) as requested in the question and is not intended for everyday use. Because this does not use a StringBuilder
it will have horrible performance for very long sequences. For regular code use String.Join
as shown in the other answer
Use aggregate queries like this:
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);
This outputs:
, 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();
Upvotes: 583
Reputation: 110071
return string.Join(", ", strings.ToArray());
In .Net 4, there's a new overload for string.Join
that accepts IEnumerable<string>
. The code would then look like:
return string.Join(", ", strings);
Upvotes: 443
Reputation: 221
quick performance data for the StringBuilder vs Select & Aggregate case over 3000 elements:
Unit test - Duration (seconds)
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);
}
Upvotes: 22
Reputation: 4055
Here is the combined Join/Linq approach I settled on after looking at the other answers and the issues addressed in a similar question (namely that Aggregate and Concatenate fail with 0 elements).
string Result = String.Join(",", split.Select(s => s.Name));
or (if s
is not a string)
string Result = String.Join(",", split.Select(s => s.ToString()));
StringBuilder
) to implementAnd of course Join takes care of the pesky final comma that sometimes sneaks into other approaches (for
, foreach
), which is why I was looking for a Linq solution in the first place.
Upvotes: 35
Reputation: 56866
By 'super-cool LINQ way' you might be talking about the way that LINQ makes functional programming a lot more palatable with the use of extension methods. I mean, the syntactic sugar that allows functions to be chained in a visually linear way (one after the other) instead of nesting (one inside the other). For example:
int totalEven = Enumerable.Sum(Enumerable.Where(myInts, i => i % 2 == 0));
can be written like this:
int totalEven = myInts.Where(i => i % 2 == 0).Sum();
You can see how the second example is easier to read. You can also see how more functions can be added with less of the indentation problems or the Lispy closing parens appearing at the end of the expression.
A lot of the other answers state that the String.Join
is the way to go because it is the fastest or simplest to read. But if you take my interpretation of 'super-cool LINQ way' then the answer is to use String.Join
but have it wrapped in a LINQ style extension method that will allow you to chain your functions in a visually pleasing way. So if you want to write sa.Concatenate(", ")
you just need to create something like this:
public static class EnumerableStringExtensions
{
public static string Concatenate(this IEnumerable<string> strings, string separator)
{
return String.Join(separator, strings);
}
}
This will provide code that is as performant as the direct call (at least in terms of algorithm complexity) and in some cases may make the code more readable (depending on the context) especially if other code in the block is using the chained function style.
Upvotes: 15
Reputation: 33098
I'm going to cheat a little and throw out a new answer to this that seems to sum up the best of everything on here instead of sticking it inside of a comment.
So you can one line this:
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);
Edit: You'll either want to check for an empty enumerable first or add an .Replace("\a",string.Empty);
to the end of the expression. Guess I might have been trying to get a little too smart.
The answer from @a.friend might be slightly more performant, I'm not sure what Replace does under the hood compared to Remove. The only other caveat if some reason you wanted to concat strings that ended in \a's you would lose your separators... I find that unlikely. If that is the case you do have other fancy characters to choose from.
Upvotes: 3
Reputation: 18203
Here it is using pure LINQ as a single expression:
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));
}
And its pretty damn fast!
Upvotes: 5
Reputation: 51
I did the following quick and dirty when parsing an IIS log file using linq, it worked @ 1 million lines pretty well (15 seconds), although got an out of memory error when trying 2 millions lines.
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");
}
The real reason I used linq was for a Distinct() I neede previously:
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()
;
Upvotes: 1
Reputation: 12413
You can combine LINQ and string.join()
quite effectively. Here I am removing an item from a string. There are better ways of doing this too but here it is:
filterset = String.Join(",",
filterset.Split(',')
.Where(f => mycomplicatedMatch(f,paramToMatch))
);
Upvotes: 2
Reputation: 17141
I blogged about this a while ago, what I did seams to be exactly what you're looking for:
http://ondevelopment.blogspot.com/2009/02/string-concatenation-made-easy.html
In the blog post describe how to implement extension methods that works on IEnumerable and are named Concatenate, this will let you write things like:
var sequence = new string[] { "foo", "bar" };
string result = sequence.Concatenate();
Or more elaborate things like:
var methodNames = typeof(IFoo).GetMethods().Select(x => x.Name);
string result = methodNames.Concatenate(", ");
Upvotes: 0
Reputation: 349
You can use StringBuilder
in Aggregate
:
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());
(The Select
is in there just to show you can do more LINQ stuff.)
Upvotes: 29
Reputation: 7183
Lots of choices here. You can use LINQ and a StringBuilder so you get the performance too like so:
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();
Upvotes: 2
Reputation: 116654
Real example from my code:
return selected.Select(query => query.Name).Aggregate((a, b) => a + ", " + b);
A query is an object that has a Name property which is a string, and I want the names of all the queries on the selected list, separated by commas.
Upvotes: 61
Reputation: 32533
Why use Linq?
string[] s = {"foo", "bar", "baz"};
Console.WriteLine(String.Join(", ", s));
That works perfectly and accepts any IEnumerable<string>
as far as I remember. No need Aggregate
anything here which is a lot slower.
Upvotes: 137
Reputation: 25294
Have you looked at the Aggregate extension method?
var sa = (new[] { "yabba", "dabba", "doo" }).Aggregate((a,b) => a + "," + b);
Upvotes: 81