Utilizzo di LINQ per concatenare le stringhe


345

Qual è il modo più efficace per scrivere alla vecchia scuola:

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?


1
Hai scoperto altri modi LINQ super cool di fare le cose?
Robert S.

3
Bene, la risposta selezionata e tutte le altre opzioni non funzionano in Linq to Entities.
Binoj Antony,

3
@Binoj Antony, non fare in modo che il tuo database esegua la concatenazione di stringhe.
Amy B,

6
@ Pr0fess0rX: perché non può e perché non dovrebbe. Non conosco altri database ma in SQL Server puoi solo concatenare (n) varcahr che ti limita a (n) varchar (max). Non dovrebbe perché la logica aziendale non dovrebbe essere implementata nel livello dati.
the_drow,

qualche soluzione finale con codice sorgente completo e prestazioni elevate?
Kiquenet,

Risposte:


528

Questa risposta mostra l'utilizzo di LINQ ( Aggregate) come richiesto nella domanda e non è inteso per l'uso quotidiano. Poiché questo non utilizza un StringBuilder, avrà prestazioni orribili per sequenze molto lunghe. Per un uso regolare del codice, String.Joincome mostrato nell'altra risposta

Usa query aggregate come questa:

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

Questo produce:

, uno due tre

Un aggregato è una funzione che accetta una raccolta di valori e restituisce un valore scalare. Esempi di T-SQL includono min, max e sum. Sia VB che C # supportano gli aggregati. Sia VB che C # supportano aggregati come metodi di estensione. Usando la notazione punto, si chiama semplicemente un metodo su un oggetto IEnumerable .

Ricorda che le query aggregate vengono eseguite immediatamente.

Ulteriori informazioni - MSDN: query aggregate


Se vuoi davvero Aggregateusare la variante di utilizzo usando StringBuilderproposta nel commento di CodeMonkeyKing che dovrebbe essere più o meno lo stesso codice del normale String.Joininclusa una buona prestazione per un gran numero di oggetti:

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

4
Il primo esempio non genera "uno, due, tre", produce ", uno, due, tre" (Nota la virgola iniziale).
Mort,

Nel tuo primo esempio, poiché esegui il seeding con "", il primo valore utilizzato in currentè una stringa vuota. Quindi, per 1 o più elementi, otterrai sempre , l'inizio della stringa.
Michael Yanni,

@Mort Ho risolto questo
problema

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

In .Net 4, c'è un nuovo sovraccarico per string.Joinquello che accetta IEnumerable<string>. Il codice sarebbe quindi simile a:

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

2
OK, quindi la soluzione non utilizza Linq, ma sembra funzionare abbastanza bene per me
Mat Roberts,

33
ToArray is linq :)
Amy B,

18
Questa è la risposta più corretta È più veloce sia della domanda che della risposta accettata ed è molto più chiaro di Aggregate, il che richiede una spiegazione lunga di paragrafo ogni volta che viene utilizzata.
PRMan,


125

Perché usare Linq?

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

Funziona perfettamente e accetta qualsiasi IEnumerable<string>per quanto mi ricordo. Non c'è bisogno di Aggregatenulla qui che è molto più lento.


19
Imparare LINQ può essere interessante, e LINQ può essere un modo carino per raggiungere il fine, ma usare LINQ per ottenere effettivamente il risultato finale sarebbe negativo, per non dire altro, se non del tutto stupido
Jason Bunting,

9
.NET 4.0 ha un sovraccarico IEnumerable <string> e IEnumrable <T>, che renderà molto più facile l'uso
Cine

3
Come sottolinea Cine, .NET 4.0 ha il sovraccarico. Le versioni precedenti no. Puoi comunque String.Join(",", s.ToArray())nelle versioni precedenti.
Martijn,


@ Shog9 La fusione qui fa apparire le risposte come sforzi duplicati, e i timestamp non aiutano affatto .. Comunque la strada da percorrere.
nawfal,

77

Hai esaminato il metodo di estensione aggregata?

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

23
Probabilmente è più lento di String.Join () e più difficile da leggere nel codice. Risponde alla domanda per un "modo LINQ", però :-)
Chris Wenham,

5
Sì, non volevo contaminare la risposta con le mie opinioni. : P
Robert S.,

2
È senza dubbio un po 'più lento, in realtà. Anche l'utilizzo di Aggregate con StringBuilder anziché la concatenazione è più lento di String.Join.
Joel Mueller,

4
Effettuato un test con 10.000.000 di iterazioni, l'aggregato ha richiesto 4,3 secondi e lo string.join ha richiesto 2,3 secondi. Quindi direi che il diff diff non è importante per il 99% dei casi d'uso comuni. Quindi, se stai già facendo molto linq per elaborare i tuoi dati, di solito non è necessario interrompere quella sintassi e usare string.join imo. gist.github.com/joeriks/5791981
joeriks,


56

Vero esempio dal mio codice:

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

Una query è un oggetto che ha una proprietà Name che è una stringa e voglio i nomi di tutte le query nell'elenco selezionato, separati da virgole.


2
Dati i commenti sulle prestazioni, dovrei aggiungere che l'esempio proviene dal codice che viene eseguito una volta alla chiusura di una finestra di dialogo e che è improbabile che l'elenco contenga più di una decina di stringhe!
Daniel Earwicker,

Qualche idea su come eseguire questa stessa attività in Linq to Entities?
Binoj Antony,

1
Ottimo esempio Grazie per aver inserito questo in uno scenario del mondo reale. Ho avuto la stessa situazione esatta, con una proprietà di un oggetto che doveva essere concisa.
Jessy Houle,

1
Ho votato per avermi aiutato a capire che la prima parte della selezione della proprietà stringa del mio Elenco <T>
Nikki9696

1
Scrivi sulle prestazioni di questo approccio con array più grandi.
Giulio Caccin,

31

Ecco l'approccio combinato Join / Linq che ho deciso dopo aver esaminato le altre risposte e le questioni affrontate in una domanda simile (ovvero che Aggregate e Concatenate falliscono con 0 elementi).

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

o (se snon è una stringa)

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

  • Semplice
  • facile da leggere e capire
  • funziona per elementi generici
  • consente di utilizzare oggetti o proprietà dell'oggetto
  • gestisce il caso di elementi di lunghezza 0
  • potrebbe essere utilizzato con un filtro Linq aggiuntivo
  • si comporta bene (almeno nella mia esperienza)
  • non richiede la creazione (manuale) di un oggetto aggiuntivo (ad es. StringBuilder) da implementare

E ovviamente Join si prende cura della fastidiosa virgola finale che a volte si intrufola in altri approcci ( for, foreach), motivo per cui stavo cercando una soluzione Linq in primo luogo.


1
parentesi errata.
ctrl-alt-delor,


3
Mi piace questa risposta perché l'utilizzo in .Select()questo modo fornisce un posto facile per modificare ogni elemento durante questa operazione. Ad esempio, avvolgendo ogni oggetto in un personaggio del generestring Result = String.Join(",", split.Select(s => "'" + s + "'"));
Sam Storie, il

29

Puoi usare 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());

Selectlì solo per mostrare che puoi fare più cose LINQ.)


2
+1 simpatico. Tuttavia, IMO è meglio evitare di aggiungere "," in più che cancellarlo in seguito. Qualcosa di similenew[] {"one", "two", "three"}.Aggregate(new StringBuilder(), (sb, s) =>{if (sb.Length > 0) sb.Append(", ");sb.Append(s);return sb;}).ToString();
dss539,

5
Risparmieresti preziosi cicli di clock non controllando if (length > 0)in linq ed eliminandolo.
Binoj Antony,

1
Sono d'accordo con dss539. La mia versione è sulla new[] {"", "one", "two", "three"}.Aggregate(new StringBuilder(), (sb, s) => (String.IsNullOrEmpty(sb.ToString())) ? sb.Append(s) : sb.Append(", ").Append(s)).ToString();
falsariga

22

dati rapidi sulle prestazioni per il caso StringBuilder vs Select & Aggregate con oltre 3000 elementi:

Unit test - Durata (secondi)
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);
    }

Utile nel decidere di seguire la strada non LINQ per questo
crabCRUSHERclamCOLLECTOR

4
La differenza oraria è probabilmente StringBuilder vs String Concatination usando +. Niente a che fare con LINQ o Aggregate. Inserisci StringBuilder in LINQ Aggregate (molti esempi su SO) e dovrebbe essere altrettanto veloce.
controlbox

16

Uso sempre il metodo di estensione:

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

5
string.Joinin .net 4 può già prendere un IEnumerable<T>per qualsiasi arbitrario T.
ricorsivo


12

Con " modo LINQ super cool " potresti parlare del modo in cui LINQ rende la programmazione funzionale molto più appetibile con l'uso di metodi di estensione. Voglio dire, lo zucchero sintattico che consente alle funzioni di essere concatenate in modo visivamente lineare (uno dopo l'altro) invece di nidificare (uno dentro l'altro). Per esempio:

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

può essere scritto così:

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

Puoi vedere come il secondo esempio è più facile da leggere. Puoi anche vedere come aggiungere più funzioni con meno problemi di rientro o le parentesi di chiusura di Lispy che appaiono alla fine dell'espressione.

Molte altre risposte affermano che String.Joinè la strada da percorrere perché è la più veloce o più semplice da leggere. Ma se prendi la mia interpretazione del " modo LINQ super-cool ", allora la risposta è da usare, String.Joinma è racchiusa in un metodo di estensione in stile LINQ che ti permetterà di incatenare le tue funzioni in un modo visivamente piacevole. Quindi se vuoi scrivere sa.Concatenate(", ")devi solo creare qualcosa del genere:

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

Ciò fornirà codice altrettanto performante della chiamata diretta (almeno in termini di complessità dell'algoritmo) e in alcuni casi potrebbe rendere il codice più leggibile (a seconda del contesto) soprattutto se altri codici nel blocco utilizzano lo stile della funzione concatenata .


1
Il numero di errori di battitura in questa discussione è folle: seperator => separator, Concatinate => Concatenate
SilverSideDown


5

Esistono varie risposte alternative a questa domanda precedente , che era certamente indirizzata a un array intero come sorgente, ma che ha ricevuto risposte generalizzate.


5

Qui sta usando LINQ puro come singola espressione:

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

Ed è dannatamente veloce!


3

Ho intenzione di imbrogliare un po 'e dare una nuova risposta a questo che sembra riassumere il meglio di tutto qui invece di inserirlo in un commento.

Quindi puoi una riga questo:

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

Modifica: prima vorrai controllare un enumerabile vuoto o aggiungere un .Replace("\a",string.Empty);alla fine dell'espressione. Immagino che avrei potuto provare a diventare un po 'troppo intelligente.

La risposta di @ a.friend potrebbe essere leggermente più performante, non sono sicuro di cosa faccia Sostituisci sotto il cofano rispetto a Rimuovi. L'unica altra avvertenza se per qualche motivo volessi concatenare stringhe che terminavano in \ a's perderei i tuoi separatori ... Lo trovo improbabile. In questo caso hai altri personaggi fantastici tra cui scegliere.


2

Puoi combinare LINQ e string.join()abbastanza efficacemente. Qui sto rimuovendo un oggetto da una stringa. Ci sono anche modi migliori per farlo, ma eccolo qui:

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


1

Molte scelte qui. Puoi usare LINQ e StringBuilder in modo da ottenere le prestazioni in questo modo:

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

Sarebbe più veloce non controllare builder.Length > 0nel ForEach e rimuovendo la prima virgola dopo il ForEach
Binoj Antony,

1

Ho fatto quanto segue rapidamente e sporco quando ho analizzato un file di registro IIS usando linq, ha funzionato abbastanza bene @ 1 milione di righe (15 secondi), anche se ho provato un errore di memoria insufficiente quando ho provato 2 milioni di righe.

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

    }

La vera ragione per cui ho usato linq era per un Distinct () di cui avevo bisogno in precedenza:

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


0

Ho scritto un blog su questo un po 'di tempo fa, quello che ho fatto cuciture è esattamente quello che stai cercando:

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

Nel post del blog descrivi come implementare metodi di estensione che funzionano su IEnumerable e sono chiamati Concatenate, questo ti permetterà di scrivere cose come:

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

O cose più elaborate come:

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


Puoi concatenare il codice qui in modo che la risposta sia più facile da capire?
Giulio Caccin,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.