Verwenden von LINQ zum Verketten von Zeichenfolgen

345

Was ist der effizienteste Weg, um die alte Schule zu schreiben:

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?

tags2k
quelle
1
Hast du andere super coole LINQ-Methoden entdeckt?
Robert S.
3
Nun, die ausgewählte Antwort und alle anderen Optionen funktionieren in Linq to Entities nicht.
Binoj Antony
3
@Binoj Antony, lassen Sie Ihre Datenbank keine Zeichenfolgenverkettung durchführen.
Amy B
6
@ Pr0fess0rX: Weil es nicht kann und weil es nicht sollte. Ich weiß nichts über andere Datenbanken, aber in SQL Server können Sie nur (n) varcahr zusammenfassen, was Sie auf (n) varchar (max) beschränkt. Dies sollte nicht der Fall sein, da die Geschäftslogik nicht in der Datenschicht implementiert werden sollte.
the_drow
Gibt es eine endgültige Lösung mit vollem Quellcode und hoher Leistung?
Kiquenet

Antworten:

527

Diese Antwort zeigt die Verwendung von LINQ ( Aggregate) wie in der Frage angefordert und ist nicht für den täglichen Gebrauch vorgesehen. Da dies kein verwendet StringBuilder, wird es für sehr lange Sequenzen eine schreckliche Leistung haben. Für den regulären Code verwenden Sie String.Joinwie in der anderen Antwort gezeigt

Verwenden Sie aggregierte Abfragen wie folgt:

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);

Dies gibt aus:

, eins zwei drei

Ein Aggregat ist eine Funktion, die eine Sammlung von Werten verwendet und einen Skalarwert zurückgibt. Beispiele aus T-SQL sind min, max und sum. Sowohl VB als auch C # unterstützen Aggregate. Sowohl VB als auch C # unterstützen Aggregate als Erweiterungsmethoden. Mit der Punktnotation ruft man einfach eine Methode auf einem IEnumerable auf Objekt auf.

Denken Sie daran, dass aggregierte Abfragen sofort ausgeführt werden.

Weitere Informationen - MSDN: Aggregierte Abfragen


Wenn Sie wirklich Aggregateeine Variante verwenden möchten, verwenden Sie StringBuilderdie im CodeMonkeyKing- Vorschlag vorgeschlagene Variante, die ungefähr dem gleichen Code wie normal entspricht, String.Joineinschließlich einer guten Leistung für eine große Anzahl von Objekten:

 var res = words.Aggregate(
     new StringBuilder(), 
     (current, next) => current.Append(current.Length == 0? "" : ", ").Append(next))
     .ToString();
Jorge Ferreira
quelle
4
Das erste Beispiel gibt nicht "eins, zwei, drei" aus, sondern "eins, zwei, drei" (Beachten Sie das führende Komma).
Mort
In Ihrem ersten Beispiel ist ""der erste Wert, der in verwendet currentwird, eine leere Zeichenfolge , da Sie mit gesetzt haben . Für 1 oder mehr Elemente stehen Sie also immer , am Anfang der Zeichenfolge.
Michael Yanni
@ Mort Ich habe dies behoben
sergtk
358
return string.Join(", ", strings.ToArray());

In .Net 4, gibt es eine neue Überlast für string.Joindas akzeptiert IEnumerable<string>. Der Code würde dann so aussehen:

return string.Join(", ", strings);
Amy B.
quelle
2
OK, die Lösung verwendet also kein Linq, aber es scheint mir ziemlich gut zu funktionieren
Mat Roberts
33
ToArray ist linq :)
Amy B
18
Dies ist die richtigste Antwort. Es ist schneller als die Frage und die akzeptierte Antwort und viel klarer als Aggregate, was bei jeder Verwendung eine absatzlange Erklärung erfordert.
PRMan
@realPro Völlig falsch. github.com/microsoft/referencesource/blob/master/mscorlib/… Zeile 161
Amy B
125

Warum Linq verwenden?

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

Das funktioniert perfekt und akzeptiert, IEnumerable<string>soweit ich mich erinnere. Hier brauchen Sie Aggregatenichts, was viel langsamer ist.

Armin Ronacher
quelle
19
LINQ zu lernen mag cool sein, und LINQ mag ein niedliches Mittel sein, um das Ziel zu erreichen, aber LINQ zu verwenden, um tatsächlich das Endergebnis zu erzielen, wäre, gelinde gesagt, schlecht, wenn nicht geradezu dumm
Jason Bunting
9
.NET 4.0 hat eine IEnumerable <string> - und IEnumrable <T> -Überlastung, die die Verwendung erheblich vereinfacht
Cine
3
Wie Cine betont, ist .NET 4.0 überlastet. Frühere Versionen tun dies nicht. Sie können jedoch immer noch String.Join(",", s.ToArray())in den älteren Versionen.
Martijn
1
Zu Ihrer
Information
@ Shog9 Durch das Zusammenführen sehen Antworten hier wie doppelte Bemühungen aus, und die Zeitstempel helfen überhaupt nicht. Immer noch der richtige Weg.
Nawfal
77

Haben Sie sich die Aggregate-Erweiterungsmethode angesehen?

var sa = (new[] { "yabba", "dabba", "doo" }).Aggregate((a,b) => a + "," + b);
Robert S.
quelle
23
Das ist wahrscheinlich langsamer als String.Join () und schwerer in Code einzulesen. Beantwortet die Frage für einen "LINQ-Weg", obwohl :-)
Chris Wenham
5
Ja, ich wollte die Antwort nicht mit meinen Meinungen verunreinigen. : P
Robert S.
2
Es ist zweifellos etwas langsamer. Selbst die Verwendung von Aggregate mit einem StringBuilder anstelle der Verkettung ist langsamer als String.Join.
Joel Mueller
4
Bei einem Test mit 10.000.000 Iterationen dauerte das Aggregat 4,3 Sekunden und string.join 2,3 Sekunden. Daher würde ich sagen, dass der Perf Diff für 99% der gängigen Anwendungsfälle unwichtig ist. Wenn Sie also bereits viel Linq für die Verarbeitung Ihrer Daten verwenden, müssen Sie diese nette Syntax normalerweise nicht brechen und string.join imo verwenden. gist.github.com/joeriks/5791981
joeriks
1
Zu Ihrer
Information
56

Echtes Beispiel aus meinem Code:

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

Eine Abfrage ist ein Objekt mit einer Name-Eigenschaft, bei der es sich um eine Zeichenfolge handelt. Ich möchte, dass die Namen aller Abfragen in der ausgewählten Liste durch Kommas getrennt werden.

Daniel Earwicker
quelle
2
Angesichts der Kommentare zur Leistung sollte ich hinzufügen, dass das Beispiel aus Code stammt, der beim Schließen eines Dialogfelds einmal ausgeführt wird, und dass die Liste wahrscheinlich nie mehr als zehn Zeichenfolgen enthält!
Daniel Earwicker
Gibt es einen Hinweis, wie dieselbe Aufgabe in Linq to Entities ausgeführt werden kann?
Binoj Antony
1
Exzellentes Beispiel. Vielen Dank, dass Sie dies in ein reales Szenario umgesetzt haben. Ich hatte genau die gleiche Situation mit einer Eigenschaft eines Objekts, das präzisiert werden musste.
Jessy Houle
1
Upvoted für die Unterstützung bei der Ermittlung des ersten Teils der Auswahl der Zeichenfolgeeigenschaft meiner Liste <T>
Nikki9696
1
Bitte schreiben Sie über die Leistung dieses Ansatzes mit größerem Array.
Giulio Caccin
31

Hier ist der kombinierte Join / Linq-Ansatz, für den ich mich entschieden habe, nachdem ich mir die anderen Antworten und die in einer ähnlichen Frage behandelten Probleme angesehen habe (nämlich, dass Aggregate und Concatenate mit 0 Elementen fehlschlagen).

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

oder (wenn ses sich nicht um eine Zeichenfolge handelt)

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

  • Einfach
  • leicht zu lesen und zu verstehen
  • funktioniert für generische Elemente
  • ermöglicht die Verwendung von Objekten oder Objekteigenschaften
  • behandelt den Fall von Elementen der Länge 0
  • könnte mit zusätzlicher Linq-Filterung verwendet werden
  • funktioniert gut (zumindest nach meiner Erfahrung)
  • erfordert keine (manuelle) Erstellung eines zusätzlichen Objekts (z. B. StringBuilder) zur Implementierung

Und natürlich kümmert sich Join um das lästige letzte Komma, das sich manchmal in andere Ansätze einschleicht ( for, foreach), weshalb ich überhaupt nach einer Linq-Lösung gesucht habe.

Brichins
quelle
1
falsch angepasste Klammern.
Strg-Alt-Delor
1
Zu Ihrer
Information
3
Diese Antwort .Select()gefällt mir, weil die Verwendung dieser Option einen einfachen Ort bietet, um jedes Element während dieses Vorgangs zu ändern. Zum Beispiel, wickeln Sie jeden Gegenstand in einen Charakter wie diesenstring Result = String.Join(",", split.Select(s => "'" + s + "'"));
Sam Storie
29

Sie können verwenden StringBuilderin 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());

(Das Selectist nur da, um zu zeigen, dass Sie mehr LINQ-Sachen machen können.)

jonathan.s
quelle
2
+1 schön. IMO ist es jedoch besser, das Hinzufügen des zusätzlichen "" zu vermeiden, als es anschließend zu löschen. So etwas wienew[] {"one", "two", "three"}.Aggregate(new StringBuilder(), (sb, s) =>{if (sb.Length > 0) sb.Append(", ");sb.Append(s);return sb;}).ToString();
dss539
5
Sie würden wertvolle Taktzyklen sparen, if (length > 0)indem Sie die Linq nicht überprüfen und herausnehmen.
Binoj Antony
1
Ich stimme dss539 zu. Meine Version ist new[] {"", "one", "two", "three"}.Aggregate(new StringBuilder(), (sb, s) => (String.IsNullOrEmpty(sb.ToString())) ? sb.Append(s) : sb.Append(", ").Append(s)).ToString();
ProfNimrod
22

Schnelle Leistungsdaten für den Fall StringBuilder vs Select & Aggregate über 3000 Elemente:


Komponententest - Dauer (Sekunden) 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);
    }
user337754
quelle
Hilfreich bei der Entscheidung, die Route ohne LINQ zu
wählen
4
Der Zeitunterschied ist wahrscheinlich StringBuilder vs String Concatination mit +. Nichts mit LINQ oder Aggregate zu tun. Fügen Sie StringBuilder in LINQ Aggregate ein (viele Beispiele für SO), und es sollte genauso schnell sein.
Controlbox
16

Ich benutze immer die Erweiterungsmethode:

public static string JoinAsString<T>(this IEnumerable<T> input, string seperator)
{
    var ar = input.Select(i => i.ToString()).ToArray();
    return string.Join(seperator, ar);
}
Kieran Benton
quelle
5
string.Joinin .net 4 kann man schon eine IEnumerable<T>beliebige nehmen T.
rekursiv
1
Zu Ihrer
Information
12

Mit " supercooler LINQ-Methode " könnten Sie darüber sprechen, wie LINQ die funktionale Programmierung durch die Verwendung von Erweiterungsmethoden viel angenehmer macht. Ich meine, der syntaktische Zucker, mit dem Funktionen visuell linear (nacheinander) verkettet werden können, anstatt (ineinander) zu verschachteln. Zum Beispiel:

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

kann so geschrieben werden:

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

Sie können sehen, wie das zweite Beispiel leichter zu lesen ist. Sie können auch sehen, wie mehr Funktionen mit weniger Einrückungsproblemen oder dem Lispy hinzugefügt werden können Abschlussparens am Ende des Ausdrucks erscheinen.

Viele der anderen Antworten besagen, dass dies der String.Joinrichtige Weg ist, da es am schnellsten oder am einfachsten zu lesen ist. Wenn Sie jedoch meine Interpretation von " supercooler LINQ-Methode " verwenden, lautet die Antwort, sie zu verwenden String.Join, sie jedoch in eine Erweiterungsmethode im LINQ-Stil einzuschließen, mit der Sie Ihre Funktionen auf visuell ansprechende Weise verketten können. Wenn Sie also schreiben möchten, müssen sa.Concatenate(", ")Sie nur Folgendes erstellen:

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

Dies liefert Code, der genauso performant ist wie der direkte Aufruf (zumindest in Bezug auf die Komplexität des Algorithmus) und in einigen Fällen den Code (je nach Kontext) besser lesbar macht, insbesondere wenn anderer Code im Block den verketteten Funktionsstil verwendet .

tpower
quelle
1
Die Anzahl der Tippfehler in diesem Thread ist verrückt: seperator => separator, Concatinate => Concatenate
SilverSideDown
1
Zu Ihrer
Information
5

Bei dieser vorherigen Frage gibt es verschiedene alternative Antworten - die zwar auf ein ganzzahliges Array als Quelle abzielten, aber allgemeine Antworten erhielten.

Jon Skeet
quelle
5

Hier wird reines LINQ als einzelner Ausdruck verwendet:

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));
}

Und es ist verdammt schnell!

cdiggins
quelle
3

Ich werde ein wenig schummeln und eine neue Antwort darauf herauswerfen, die das Beste von allem hier zusammenzufassen scheint, anstatt es in einen Kommentar zu stecken.

Sie können dies also in einer Zeile festhalten:

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); 

Bearbeiten: Sie möchten entweder zuerst nach einer leeren Aufzählung suchen oder eine hinzufügen.Replace("\a",string.Empty); am Ende des Ausdrucks . Ich schätze, ich habe versucht, ein bisschen zu schlau zu werden.

Die Antwort von @ a.friend ist möglicherweise etwas performanter. Ich bin mir nicht sicher, was Replace unter der Haube im Vergleich zu Remove tut. Die einzige andere Einschränkung, wenn Sie aus irgendeinem Grund Zeichenfolgen zusammenfassen wollten, die mit \ a endeten, würden Sie Ihre Trennzeichen verlieren ... Ich finde das unwahrscheinlich. In diesem Fall haben Sie andere ausgefallene Charaktere zur Auswahl.

Chris Marisic
quelle
2

Sie können LINQ string.join()sehr effektiv kombinieren . Hier entferne ich ein Element aus einer Zeichenfolge. Es gibt auch bessere Möglichkeiten, dies zu tun, aber hier ist es:

filterset = String.Join(",",
                        filterset.Split(',')
                                 .Where(f => mycomplicatedMatch(f,paramToMatch))
                       );
Andiih
quelle
1
Zu Ihrer
Information
1

Viele Möglichkeiten hier. Sie können LINQ und einen StringBuilder verwenden, damit Sie die Leistung auch so erhalten:

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();
Kelly
quelle
Es wäre schneller, das builder.Length > 0im ForEach nicht zu überprüfen und das erste Komma nach dem ForEach zu entfernen
Binoj Antony
1

Ich habe Folgendes schnell und schmutzig gemacht, als ich eine IIS-Protokolldatei mit linq analysiert habe. Es hat bei 1 Million Zeilen ziemlich gut funktioniert (15 Sekunden), obwohl beim Versuch, 2 Millionen Zeilen zu speichern, ein Speicherfehler aufgetreten ist.

    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");

    }

Der wahre Grund, warum ich linq verwendet habe, war für ein Distinct (), das ich zuvor brauchte:

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()
    ;
Andy S.
quelle
1
Zu Ihrer
Information
0

Ich habe vor einiger Zeit darüber gebloggt, was ich getan habe, um genau das zu sein, wonach Sie suchen:

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

In diesem Blogbeitrag wird beschrieben, wie Sie Erweiterungsmethoden implementieren, die unter IEnumerable funktionieren und den Namen "Verketten" tragen. Auf diese Weise können Sie Folgendes schreiben:

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

Oder ausgefeiltere Dinge wie:

var methodNames = typeof(IFoo).GetMethods().Select(x => x.Name);
string result = methodNames.Concatenate(", ");
Patrik Hägne
quelle
1
Zu Ihrer
Information
Können Sie hier Code verketten, damit die Antwort leichter zu verstehen ist?
Giulio Caccin