Erstellen einer durch Kommas getrennten Liste aus IList <string> oder IEnumerable <string>

849

Was ist der sauberste Weg, um eine durch Kommas getrennte Liste von Zeichenfolgenwerten aus einem IList<string>oder zu erstellen IEnumerable<string>?

String.Join(...)Es string[]kann schwierig sein, mit einem zu arbeiten, wenn Typen wie IList<string>oder IEnumerable<string>nicht einfach in ein String-Array konvertiert werden können.

Daniel Fortunov
quelle
5
Oh ... whoops. Ich habe die Hinzufügung der ToArray-Erweiterungsmethode in 3.5 verpasst:public static TSource[] ToArray<TSource>(this IEnumerable<TSource> source)
Daniel Fortunov
1
Wenn Sie bei dieser Frage nach einer Möglichkeit zum Schreiben von CSV suchen, sollten Sie bedenken, dass das einfache Einfügen von Kommas zwischen Elementen nicht ausreicht und bei Anführungszeichen und Kommas in den Quelldaten zu Fehlern führt.
Spender

Antworten:

1447

.NET 4+

IList<string> strings = new List<string>{"1","2","testing"};
string joined = string.Join(",", strings);

Detail & Pre .Net 4.0-Lösungen

IEnumerable<string>kann mit LINQ (.NET 3.5) sehr einfach in ein String-Array konvertiert werden :

IEnumerable<string> strings = ...;
string[] array = strings.ToArray();

Es ist einfach genug, die entsprechende Hilfsmethode zu schreiben, wenn Sie Folgendes benötigen:

public static T[] ToArray(IEnumerable<T> source)
{
    return new List<T>(source).ToArray();
}

Dann nenne es so:

IEnumerable<string> strings = ...;
string[] array = Helpers.ToArray(strings);

Sie können dann anrufen string.Join. Natürlich müssen Sie nicht haben , um eine Hilfsmethode zu verwenden:

// C# 3 and .NET 3.5 way:
string joined = string.Join(",", strings.ToArray());
// C# 2 and .NET 2.0 way:
string joined = string.Join(",", new List<string>(strings).ToArray());

Letzteres ist allerdings ein bisschen mundvoll :)

Dies ist wahrscheinlich der einfachste und auch sehr performante Weg - es gibt andere Fragen dazu, wie die Aufführung genau aussieht, einschließlich (aber nicht beschränkt auf) diesen .

Ab .NET 4.0 sind mehr Überladungen in verfügbar string.Join, sodass Sie einfach schreiben können:

string joined = string.Join(",", strings);

Viel einfacher :)

Jon Skeet
quelle
Bei der Hilfsmethode werden zwei unnötige Listen erstellt. Ist dies wirklich der beste Weg, um das Problem zu lösen? Warum verketten Sie es nicht selbst in einer foreach-Schleife?
Eric
4
Die Hilfsmethode erstellt nur eine Liste und ein Array. Der Punkt ist, dass das Ergebnis ein Array sein muss, keine Liste ... und Sie müssen die Größe eines Arrays kennen, bevor Sie beginnen. Best Practice besagt, dass Sie eine Quelle in LINQ nicht mehr als einmal aufzählen sollten, es sei denn, Sie müssen - es kann sein, dass es alle möglichen bösen Dinge tut. Sie müssen also nur noch in die Puffer lesen und die Größe ändern - genau das ist es, was Sie tun List<T>. Warum das Rad neu erfinden?
Jon Skeet
9
Nein, der Punkt ist, dass das Ergebnis eine verkettete Zeichenfolge sein muss. Es ist nicht erforderlich, eine neue Liste oder ein neues Array zu erstellen, um dieses Ziel zu erreichen. Diese Art von .NET-Mentalität macht mich traurig.
Eric
38
Das ist es. Jede Antwort führt zu Jon Skeet. Ich gehe nur zu var PurchaseBooks = AmazonContainer.Where (p => p.Author == "Jon Skeet"). Select ();
Zachary Scott
3
@ codeMonkey0110: Nun, es macht keinen Sinn, dort einen Abfrageausdruck zu haben oder aufzurufen ToList. Es ist aber in Ordnung zu benutzen string myStr = string.Join(",", foo.Select(a => a.someInt.ToString())).
Jon Skeet
179

Zu Ihrer Information, die .NET 4.0-Version von string.Join()hat einige zusätzliche Überladungen , die mit IEnumerablenur Arrays funktionieren , einschließlich eines, das mit jedem Typ umgehen kann T:

public static string Join(string separator, IEnumerable<string> values)
public static string Join<T>(string separator, IEnumerable<T> values)
Xavier Poinas
quelle
2
Dies ruft die T.ToString () -Methode auf.
Philippe Lavoie
Ich wollte gerade Jons Antwort kommentieren. Vielen Dank für die Erwähnung.
Dan Bechard
2
Wie auch immer, um dies für eine Eigenschaft eines Objekts zu tun? (Beispiel: IEnumerable <Employee> und das Employee-Objekt haben eine String-SSN-Eigenschaft und eine durch Kommas getrennte Liste von
SSNs
1
Sie müssen zuerst die Zeichenfolge auswählen, obwohl Sie eine Erweiterungsmethode erstellen können, die dies tut. str = emps.Select(e => e.SSN).Join(",")
Xavier Poinas
65

Der einfachste Weg, dies zu sehen, ist die Verwendung der LINQ- AggregateMethode:

string commaSeparatedList = input.Aggregate((a, x) => a + ", " + x)
Daniel Fortunov
quelle
20
Das ist nicht nur komplizierter (IMO) als ToArray + Join, es ist auch etwas ineffizient - bei einer großen Eingabesequenz wird die Leistung sehr schlecht.
Jon Skeet
35
Trotzdem ist es das Schönste.
Merritt
2
Sie können Aggregate einen StringBuilder-Seed füttern, dann wird Ihre Aggregate Func Func<StringBuilder,string,StringBuider>. Rufen Sie dann einfach ToString()den zurückgegebenen StringBuilder auf. Es ist natürlich nicht so hübsch :)
Matt Greer
3
Dies ist der klarste Weg, um das zu tun, was die Frage IMHO gestellt hat.
Derek Morrison
8
Vorsicht, das input.Countsollte mehr als 1 sein.
Youngjae
31

Ich denke, dass der sauberste Weg, eine durch Kommas getrennte Liste von Zeichenfolgenwerten zu erstellen, einfach ist:

string.Join<string>(",", stringEnumerable);

Hier ist ein vollständiges Beispiel:

IEnumerable<string> stringEnumerable= new List<string>();
stringList.Add("Comma");
stringList.Add("Separated");

string.Join<string>(",", stringEnumerable);

Es ist nicht erforderlich, eine Hilfsfunktion zu erstellen. Diese ist in .NET 4.0 und höher integriert.

Dan VanWinkle
quelle
4
Beachten Sie, dass dies ab .NET 4 gilt (wie Xavier in seiner Antwort ausgeführt hat).
Derek Morrison
Aus der Sicht von .NET 4-Neulingen mit weniger als einem Monat Erfahrung war diese Antwort eine schöne Kombination aus Korrektheit und Prägnanz
Dexygen
13

Im Vergleich zur Leistung ist der Gewinner "Loop it, sb.Append it und do back step". Tatsächlich ist "Aufzählung und manuelle Verschiebung als nächstes" das gleiche Gut (siehe stddev).

BenchmarkDotNet=v0.10.5, OS=Windows 10.0.14393
Processor=Intel Core i5-2500K CPU 3.30GHz (Sandy Bridge), ProcessorCount=4
Frequency=3233539 Hz, Resolution=309.2587 ns, Timer=TSC
  [Host] : Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1637.0
  Clr    : Clr 4.0.30319.42000, 64bit RyuJIT-v4.6.1637.0
  Core   : .NET Core 4.6.25009.03, 64bit RyuJIT


                Method |  Job | Runtime |     Mean |     Error |    StdDev |      Min |      Max |   Median | Rank |  Gen 0 | Allocated |
---------------------- |----- |-------- |---------:|----------:|----------:|---------:|---------:|---------:|-----:|-------:|----------:|
            StringJoin |  Clr |     Clr | 28.24 us | 0.4381 us | 0.3659 us | 27.68 us | 29.10 us | 28.21 us |    8 | 4.9969 |   16.3 kB |
 SeparatorSubstitution |  Clr |     Clr | 17.90 us | 0.2900 us | 0.2712 us | 17.55 us | 18.37 us | 17.80 us |    6 | 4.9296 |  16.27 kB |
     SeparatorStepBack |  Clr |     Clr | 16.81 us | 0.1289 us | 0.1206 us | 16.64 us | 17.05 us | 16.81 us |    2 | 4.9459 |  16.27 kB |
            Enumerable |  Clr |     Clr | 17.27 us | 0.0736 us | 0.0615 us | 17.17 us | 17.36 us | 17.29 us |    4 | 4.9377 |  16.27 kB |
            StringJoin | Core |    Core | 27.51 us | 0.5340 us | 0.4995 us | 26.80 us | 28.25 us | 27.51 us |    7 | 5.0296 |  16.26 kB |
 SeparatorSubstitution | Core |    Core | 17.37 us | 0.1664 us | 0.1557 us | 17.15 us | 17.68 us | 17.39 us |    5 | 4.9622 |  16.22 kB |
     SeparatorStepBack | Core |    Core | 15.65 us | 0.1545 us | 0.1290 us | 15.45 us | 15.82 us | 15.66 us |    1 | 4.9622 |  16.22 kB |
            Enumerable | Core |    Core | 17.00 us | 0.0905 us | 0.0654 us | 16.93 us | 17.12 us | 16.98 us |    3 | 4.9622 |  16.22 kB |

Code:

public class BenchmarkStringUnion
{
    List<string> testData = new List<string>();
    public BenchmarkStringUnion()
    {
        for(int i=0;i<1000;i++)
        {
            testData.Add(i.ToString());
        }
    }
    [Benchmark]
    public string StringJoin()
    {
        var text = string.Join<string>(",", testData);
        return text;
    }
    [Benchmark]
    public string SeparatorSubstitution()
    {
        var sb = new StringBuilder();
        var separator = String.Empty;
        foreach (var value in testData)
        {
            sb.Append(separator).Append(value);
            separator = ",";
        }
        return sb.ToString();
    }

    [Benchmark]
    public string SeparatorStepBack()
    {
        var sb = new StringBuilder();
        foreach (var item in testData)
            sb.Append(item).Append(',');
        if (sb.Length>=1) 
            sb.Length--;
        return sb.ToString();
    }

    [Benchmark]
    public string Enumerable()
    {
        var sb = new StringBuilder();
        var e = testData.GetEnumerator();
        bool  moveNext = e.MoveNext();
        while (moveNext)
        {
            sb.Append(e.Current);
            moveNext = e.MoveNext();
            if (moveNext) 
                sb.Append(",");
        }
        return sb.ToString();
    }
}

https://github.com/dotnet/BenchmarkDotNet wurde verwendet

Roman Pokrovskij
quelle
11

Da ich hier angekommen bin, als ich nach einer bestimmten Eigenschaft einer Liste von Objekten gesucht habe (und nicht nach ToString ()), ist hier eine Ergänzung zu der akzeptierten Antwort:

var commaDelimited = string.Join(",", students.Where(i => i.Category == studentCategory)
                                 .Select(i => i.FirstName));
Sam
quelle
9

Hier ist eine weitere Erweiterungsmethode:

    public static string Join(this IEnumerable<string> source, string separator)
    {
        return string.Join(separator, source);
    }
Chris McKenzie
quelle
8

Ich komme etwas spät zu dieser Diskussion, aber das ist mein Beitrag fwiw. Ich muss IList<Guid> OrderIdsin eine CSV-Zeichenfolge konvertiert werden, aber das Folgende ist generisch und funktioniert unverändert mit anderen Typen:

string csv = OrderIds.Aggregate(new StringBuilder(),
             (sb, v) => sb.Append(v).Append(","),
             sb => {if (0 < sb.Length) sb.Length--; return sb.ToString();});

Kurz und bündig, verwendet StringBuilder zum Erstellen eines neuen Strings, verkleinert die StringBuilder-Länge um eins, um das letzte Komma zu entfernen, und gibt den CSV-String zurück.

Ich habe dies aktualisiert, um mehrere zu verwenden Append(), um Zeichenfolge + Komma hinzuzufügen. Aus James 'Feedback habe ich Reflector verwendet, um einen Blick darauf zu werfen StringBuilder.AppendFormat(). Es stellt sich heraus, dass AppendFormat()ein StringBuilder verwendet wird, um die Formatzeichenfolge zu erstellen, was sie in diesem Kontext weniger effizient macht als nur die Verwendung mehrerer Zeichenfolgen Appends().

David Clarke
quelle
Gazumped, danke Xavier, mir war dieses Update in .Net4 nicht bekannt. Das Projekt, an dem ich arbeite, hat den Sprung noch nicht geschafft, daher werde ich in der Zwischenzeit mein Beispiel für Fußgänger weiter verwenden.
David Clarke
2
Dies schlägt mit einer IEnumerable-Quelle mit null Elementen fehl. sb.Length-- benötigt eine Überprüfung der Grenzen.
James Dunne
Netter Fang, danke James. In dem Kontext, in dem ich dies verwende, ist mir "garantiert", dass ich mindestens eine OrderId habe. Ich habe sowohl das Beispiel als auch meinen eigenen Code aktualisiert, um die Grenzüberprüfung einzuschließen (nur um sicherzugehen).
David Clarke
@ James Ich denke, sb.Length anzurufen - ein Hack ist ein wenig hart. Tatsächlich vermeide ich nur Ihren "if (notdone)" - Test bis zum Ende, anstatt ihn in jeder Iteration durchzuführen.
David Clarke
1
@James mein Punkt ist, dass es oft mehr als eine richtige Antwort auf die hier gestellten Fragen gibt und die Bezeichnung einer als "Hack" impliziert, dass es falsch ist, was ich bestreiten würde. Für die geringe Anzahl von Leitfäden, die ich oben verkenne, wäre Daniels Antwort wahrscheinlich vollkommen ausreichend und sie ist sicherlich prägnanter / lesbarer als meine Antwort. Ich verwende dies nur an einer Stelle in meinem Code und werde immer nur ein Komma als Trennzeichen verwenden. YAGNI sagt, baue nichts, was du nicht brauchst. DRY ist anwendbar, wenn ich es mehr als einmal tun musste. Zu diesem Zeitpunkt würde ich eine Erweiterungsmethode erstellen. HTH.
David Clarke
7

Etwas etwas flüchtig, aber es funktioniert:

string divisionsCSV = String.Join(",", ((List<IDivisionView>)divisions).ConvertAll<string>(d => d.DivisionID.ToString("b")).ToArray());

Gibt Ihnen eine CSV aus einer Liste, nachdem Sie ihr den Konverter gegeben haben (in diesem Fall d => d.DivisionID.ToString ("b")).

Hacky aber funktioniert - könnte vielleicht zu einer Erweiterungsmethode gemacht werden?

Mike Kingscott
quelle
7

So habe ich es gemacht, so wie ich es in anderen Sprachen gemacht habe:

private string ToStringList<T>(IEnumerable<T> list, string delimiter)
{
  var sb = new StringBuilder();
  string separator = String.Empty;
  foreach (T value in list)
  {
    sb.Append(separator).Append(value);
    separator = delimiter;
  }
  return sb.ToString();
}
Dale King
quelle
7

Spezifisches Bedürfnis, wann wir umgeben sollten von ', von ex:

        string[] arr = { "jj", "laa", "123" };
        List<string> myList = arr.ToList();

        // 'jj', 'laa', '123'
        Console.WriteLine(string.Join(", ",
            myList.ConvertAll(m =>
                string.Format("'{0}'", m)).ToArray()));
Serhio
quelle
4

Wir haben eine Utility-Funktion, ungefähr so:

public static string Join<T>( string delimiter, 
    IEnumerable<T> collection, Func<T, string> convert )
{
    return string.Join( delimiter, 
        collection.Select( convert ).ToArray() );
}

Was verwendet werden kann, um viele Sammlungen einfach zu verbinden:

int[] ids = {1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233};

string csv = StringUtility.Join(",", ids, i => i.ToString() );

Beachten Sie, dass wir den Sammlungsparameter vor dem Lambda haben, da Intellisense dann den Sammlungstyp aufnimmt.

Wenn Sie bereits eine Aufzählung von Zeichenfolgen haben, müssen Sie nur das ToArray ausführen:

string csv = string.Join( ",", myStrings.ToArray() );
Keith
quelle
2
Ich habe eine Erweiterungsmethode, die fast genau das Gleiche tut, sehr nützlich: stackoverflow.com/questions/696850/…
LukeH
Ja, Sie können dies einfach als .ToDelimitedString-Erweiterungsmethode schreiben. Ich würde mit meiner einzeiligen Zeichenfolge gehen. Verbinden Sie eine, anstatt einen StringBuilder zu verwenden und das letzte Zeichen zu kürzen.
Keith
3

Sie können die IList mit ToArray in ein Array konvertieren und dann einen Befehl string.join für das Array ausführen.

Dim strs As New List(Of String)
Dim arr As Array
arr = strs.ToArray
Vikram
quelle
3

Sie können mithilfe der Linq-Erweiterungen in .NET 3.5 problemlos in ein Array konvertiert werden.

   var stringArray = stringList.ToArray();
Richard
quelle
3

Sie können auch Folgendes verwenden, nachdem Sie es mit einer der von anderen aufgeführten Methoden in ein Array konvertiert haben:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Net;
using System.Configuration;

namespace ConsoleApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            CommaDelimitedStringCollection commaStr = new CommaDelimitedStringCollection();
            string[] itemList = { "Test1", "Test2", "Test3" };
            commaStr.AddRange(itemList);
            Console.WriteLine(commaStr.ToString()); //Outputs Test1,Test2,Test3
            Console.ReadLine();
        }
    }
}

Bearbeiten: Hier ist ein weiteres Beispiel

Brad
quelle
3

Ich habe dieses Problem gerade gelöst, bevor ich diesen Artikel gelesen habe. Meine Lösung sieht ungefähr so ​​aus:

   private static string GetSeparator<T>(IList<T> list, T item)
   {
       return (list.IndexOf(item) == list.Count - 1) ? "" : ", ";
   }

Genannt wie:

List<thing> myThings;
string tidyString;

foreach (var thing in myThings)
{
     tidyString += string.format("Thing {0} is a {1}", thing.id, thing.name) + GetSeparator(myThings, thing);
}

Ich hätte es auch genauso leicht ausdrücken können und wäre auch effizienter gewesen:

string.Join(“,”, myThings.Select(t => string.format(“Thing {0} is a {1}”, t.id, t.name)); 
SpaceKat
quelle
3

Meine Antwort ist wie oben Aggregatlösung, sollte aber weniger Call-Stack-lastig sein, da es keine expliziten Delegate-Aufrufe gibt:

public static string ToCommaDelimitedString<T>(this IEnumerable<T> items)
{
    StringBuilder sb = new StringBuilder();
    foreach (var item in items)
    {
        sb.Append(item.ToString());
        sb.Append(',');
    }
    if (sb.Length >= 1) sb.Length--;
    return sb.ToString();
}

Natürlich kann man die Signatur so erweitern, dass sie vom Trennzeichen unabhängig ist. Ich bin wirklich kein Fan des Aufrufs von sb.Remove () und möchte ihn so umgestalten, dass er eine direkte while-Schleife über eine IEnumerable ist, und mithilfe von MoveNext () bestimmen, ob ein Komma geschrieben werden soll oder nicht. Ich werde herumspielen und diese Lösung veröffentlichen, wenn ich darauf stoße.


Folgendes wollte ich anfangs:

public static string ToDelimitedString<T>(this IEnumerable<T> source, string delimiter, Func<T, string> converter)
{
    StringBuilder sb = new StringBuilder();
    var en = source.GetEnumerator();
    bool notdone = en.MoveNext();
    while (notdone)
    {
        sb.Append(converter(en.Current));
        notdone = en.MoveNext();
        if (notdone) sb.Append(delimiter);
    }
    return sb.ToString();
}

Kein temporärer Array- oder Listenspeicher erforderlich und kein StringBuilder Remove()oder Length--Hack erforderlich.

In meiner Framework-Bibliothek habe ich einige Variationen dieser Methodensignatur vorgenommen, wobei jede Kombination delimiterdie converterParameter und die mit der Verwendung von ","bzw. x.ToString()als Standard enthält.

James Dunne
quelle
3

Hoffentlich ist dies der einfachste Weg

 string Commaseplist;
 string[] itemList = { "Test1", "Test2", "Test3" };
 Commaseplist = string.join(",",itemList);
 Console.WriteLine(Commaseplist); //Outputs Test1,Test2,Test3
Thangamani Palanisamy
quelle
3

Ich bin auf diese Diskussion gestoßen, als ich nach einer guten C # -Methode gesucht habe, um Zeichenfolgen wie mit der MySql-Methode zu verbinden CONCAT_WS(). Diese Methode unterscheidet sich von der string.Join()Methode dadurch, dass das Trennzeichen nicht hinzugefügt wird, wenn Zeichenfolgen NULL oder leer sind.

CONCAT_WS (',', tbl.Lastname, tbl.Firstname)

wird nur zurückgegeben, Lastnamewenn der Vorname leer ist, während

string.Join (",", strLastname, strFirstname)

wird zurückkehren strLastname + ", " im gleichen Fall zurückkehren.

Ich wollte das erste Verhalten und schrieb die folgenden Methoden:

    public static string JoinStringsIfNotNullOrEmpty(string strSeparator, string strA, string strB, string strC = "")
    {
        return JoinStringsIfNotNullOrEmpty(strSeparator, new[] {strA, strB, strC});
    }

    public static string JoinStringsIfNotNullOrEmpty(string strSeparator, string[] arrayStrings)
    {
        if (strSeparator == null)
            strSeparator = "";
        if (arrayStrings == null)
            return "";
        string strRetVal = arrayStrings.Where(str => !string.IsNullOrEmpty(str)).Aggregate("", (current, str) => current + (str + strSeparator));
        int trimEndStartIndex = strRetVal.Length - strSeparator.Length;
        if (trimEndStartIndex>0)
            strRetVal = strRetVal.Remove(trimEndStartIndex);
        return strRetVal;
    }
Håkon Seljåsen
quelle
2

Ich habe einige Erweiterungsmethoden geschrieben, um dies auf effiziente Weise zu tun:

    public static string JoinWithDelimiter(this IEnumerable<String> that, string delim) {
        var sb = new StringBuilder();
        foreach (var s in that) {
            sb.AppendToList(s,delim);
        }

        return sb.ToString();
    }

Das hängt davon ab

    public static string AppendToList(this String s, string item, string delim) {
        if (s.Length == 0) {
            return item;
        }

        return s+delim+item;
    }
Paul Houle
quelle
3
Die Verwendung des Operators + zum Verketten von Zeichenfolgen ist nicht besonders hilfreich, da jedes Mal eine neue Zeichenfolge zugewiesen wird. Obwohl der StringBuilder implizit in einen String umgewandelt werden kann, würde eine häufige Ausführung (jede Iteration Ihrer Schleife) den Zweck eines String-Builders weitgehend zunichte machen.
Daniel Fortunov
2

Sie können .ToArray()auf Listsund verwenden IEnumerablesund dann verwenden, String.Join()wie Sie wollten.

JoshJordan
quelle
0

Wenn sich die Zeichenfolgen, denen Sie beitreten möchten, in der Liste der Objekte befinden, können Sie auch Folgendes tun:

var studentNames = string.Join(", ", students.Select(x => x.name));
Saksham Chaudhary
quelle