Création d'une liste séparée par des virgules à partir de IList <string> ou IEnumerable <string>

851

Quelle est la façon la plus simple de créer une liste de valeurs de chaîne séparées par des virgules à partir d'un IList<string>ou IEnumerable<string>?

String.Join(...)fonctionne sur un string[]donc peut être fastidieux de travailler avec lorsque des types tels que IList<string>ou IEnumerable<string>ne peuvent pas facilement être convertis en un tableau de chaînes.

Daniel Fortunov
la source
5
Oh ... oups. J'ai raté l'ajout de la méthode d'extension ToArray dans 3.5:public static TSource[] ToArray<TSource>(this IEnumerable<TSource> source)
Daniel Fortunov
1
Si vous êtes venu à cette question à la recherche d'un moyen d'écrire CSV, il convient de se rappeler que la simple insertion de virgules entre les éléments est insuffisante et entraînera un échec dans le cas de guillemets et de virgules dans les données source.
dépenser

Réponses:

1448

.NET 4+

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

Détails et solutions pré-Net 4.0

IEnumerable<string>peut être converti en un tableau de chaînes très facilement avec LINQ (.NET 3.5):

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

Il est assez facile d'écrire la méthode d'assistance équivalente si vous devez:

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

Ensuite, appelez-le comme ceci:

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

Vous pouvez ensuite appeler string.Join. Bien sûr, vous n'avez pas besoin d'utiliser une méthode d'assistance:

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

Ce dernier est un peu bouchée cependant :)

C'est probablement le moyen le plus simple de le faire, et assez performant également - il y a d'autres questions sur la nature exacte des performances, y compris (mais sans s'y limiter) celle-ci .

Depuis .NET 4.0, il y a plus de surcharges disponibles dans string.Join, vous pouvez donc simplement écrire:

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

Beaucoup plus simple :)

Jon Skeet
la source
La méthode d'assistance implique la création de deux listes inutiles. Est-ce vraiment la meilleure façon de résoudre le problème? Pourquoi ne pas le concaténer vous-même dans une boucle foreach?
Eric
4
La méthode d'assistance crée uniquement une liste et un tableau. Le fait est que le résultat doit être un tableau, pas une liste ... et vous devez connaître la taille d'un tableau avant de commencer. Les meilleures pratiques indiquent que vous ne devez pas énumérer une source plus d'une fois dans LINQ, sauf si vous le devez - cela pourrait faire toutes sortes de choses désagréables. Donc, il vous reste à lire dans les tampons et à redimensionner au fur et à mesure - ce qui est exactement ce qui List<T>fait. Pourquoi réinventer la roue?
Jon Skeet
9
Non, le fait est que le résultat doit être une chaîne concaténée. Il n'est pas nécessaire de créer une nouvelle liste ou un nouveau tableau pour atteindre cet objectif. Ce genre de mentalité .NET me rend triste.
Eric
38
C'est ça. Chaque réponse mène à Jon Skeet. Je vais simplement var PurchaseBooks = AmazonContainer.Where (p => p.Author == "Jon Skeet"). Select ();
Zachary Scott
3
@ codeMonkey0110: Eh bien, il est inutile d'avoir une expression de requête ou d'appeler ToList. C'est bien d'utiliser string myStr = string.Join(",", foo.Select(a => a.someInt.ToString()))cependant.
Jon Skeet
179

Pour info, la version .NET 4.0 de string.Join()comporte des surcharges supplémentaires , qui fonctionnent avec IEnumerableau lieu de simples tableaux, dont un qui peut gérer n'importe quel type T:

public static string Join(string separator, IEnumerable<string> values)
public static string Join<T>(string separator, IEnumerable<T> values)
Xavier Poinas
la source
2
Cela appellera la méthode T.ToString ()?
Philippe Lavoie
Était sur le point de commenter cela sur la réponse de Jon. Merci d'avoir mentionné.
Dan Bechard
2
Quoi qu'il en soit pour le faire sur une propriété d'un objet? (Ex: IEnumerable <Employee> et l'objet Employee a une propriété de chaîne .SSN dessus, et obtenir une liste de SSN séparés par des virgules.)
granadaCoder
1
Vous devez d'abord sélectionner la chaîne, bien que vous puissiez créer une méthode d'extension qui le fasse. str = emps.Select(e => e.SSN).Join(",")
Xavier Poinas
65

La façon la plus simple que je peux voir pour ce faire est d'utiliser la Aggregateméthode LINQ :

string commaSeparatedList = input.Aggregate((a, x) => a + ", " + x)
Daniel Fortunov
la source
20
Ce n'est pas seulement plus compliqué (IMO) que ToArray + Join, c'est aussi quelque peu inefficace - avec une grande séquence d'entrée, cela va commencer à très mal fonctionner.
Jon Skeet
35
Pourtant, c'est la plus jolie.
Merritt
2
Vous pouvez alimenter Aggregate une graine StringBuilder, puis votre Aggregate Func devient Func<StringBuilder,string,StringBuider>. Ensuite, appelez simplement ToString()le StringBuilder retourné. Bien sûr, ce n'est pas aussi joli :)
Matt Greer
3
C'est la manière la plus claire de faire ce que la question demandait à mon humble avis.
Derek Morrison
8
Attention cela input.Countdevrait être plus de 1.
Youngjae
31

Je pense que la façon la plus propre de créer une liste de valeurs de chaînes séparées par des virgules est simplement:

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

Voici un exemple complet:

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

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

Il n'est pas nécessaire de créer une fonction d'assistance, elle est intégrée à .NET 4.0 et supérieur.

Dan VanWinkle
la source
4
Notez que cela s'applique à partir de .NET 4 (comme Xavier l'a souligné dans sa réponse).
Derek Morrison
Du point de vue du débutant .NET 4 avec moins d'un mois d'expérience, cette réponse était une belle combinaison d'exactitude et de
concision
13

En comparant les performances, le gagnant est "Bouclez-le, ajoutez-le et faites un pas en arrière". En fait, "enumerable and manual move next" est le même bien (pensez à 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 a été utilisé

Roman Pokrovskij
la source
11

Depuis que je suis arrivé ici en cherchant à joindre une propriété spécifique d'une liste d'objets (et non le ToString () de celui-ci), voici un ajout à la réponse acceptée:

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

Voici une autre méthode d'extension:

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

J'arrive un peu tard pour cette discussion mais c'est ma contribution fwiw. J'ai un IList<Guid> OrderIdsà convertir en une chaîne CSV mais ce qui suit est générique et fonctionne sans modification avec d'autres types:

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

Court et doux, utilise StringBuilder pour construire une nouvelle chaîne, réduit la longueur de StringBuilder d'une unité pour supprimer la dernière virgule et renvoie la chaîne CSV.

J'ai mis à jour ceci pour utiliser plusieurs Append()pour ajouter chaîne + virgule. D'après les commentaires de James, j'ai utilisé Reflector pour jeter un coup d'œil StringBuilder.AppendFormat(). Il s'avère qu'il AppendFormat()utilise un StringBuilder pour construire la chaîne de formatage, ce qui la rend moins efficace dans ce contexte que la simple utilisation de plusieurs Appends().

David Clarke
la source
Gazumped, merci Xavier, je n'étais pas au courant de cette mise à jour dans .Net4. Le projet sur lequel je travaille n'a pas encore franchi le pas, alors je vais continuer à utiliser mon exemple maintenant piéton en attendant.
David Clarke
2
Cela échouera avec une source IEnumerable sans élément. sb.Length-- nécessite une vérification des limites.
James Dunne
Belle capture merci James, dans le contexte où j'utilise ceci, je suis "garanti" d'avoir au moins un OrderId. J'ai mis à jour l'exemple et mon propre code pour inclure la vérification des limites (juste pour être sûr).
David Clarke
@James, je pense qu'appeler sb.Length-- un hack est un peu dur. En fait, j'évite simplement votre test "if (notdone)" jusqu'à la fin plutôt que de le faire à chaque itération.
David Clarke
1
@James, je veux dire qu'il y a souvent plus d'une bonne réponse aux questions posées ici et en faire référence à un "hack" implique qu'elle est incorrecte, ce que je contesterais. Pour le petit nombre de guides que je concatène, la réponse de Daniel ci-dessus serait probablement parfaitement adéquate et elle est certainement plus succincte / lisible que ma réponse. J'utilise ceci dans un seul endroit de mon code et je n'utiliserai jamais une virgule comme délimiteur. YAGNI dit de ne pas construire quelque chose dont vous n'aurez pas besoin. DRY est applicable si je devais le faire plus d'une fois, auquel cas je créerais une méthode d'extension. HTH.
David Clarke
7

Quelque chose d'un peu énormément, mais cela fonctionne:

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

Vous donne un CSV à partir d'une liste après lui avoir donné le convertisseur (dans ce cas, d => d.DivisionID.ToString ("b")).

Hacky mais fonctionne - pourrait peut-être devenir une méthode d'extension?

Mike Kingscott
la source
7

Voici comment je l'ai fait, en utilisant la façon dont je l'ai fait dans d'autres langues:

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
la source
7

Besoin spécifique quand il faut s'entourer de ', par 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
la source
4

Nous avons une fonction utilitaire, quelque chose comme ceci:

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

Qui peut être utilisé pour rejoindre facilement de nombreuses collections:

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

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

Notez que nous avons le paramètre de collection avant le lambda car intellisense récupère alors le type de collection.

Si vous avez déjà une énumération de chaînes, tout ce que vous devez faire est le ToArray:

string csv = string.Join( ",", myStrings.ToArray() );
Keith
la source
2
J'ai une méthode d'extension qui fait presque exactement la même chose, très utile: stackoverflow.com/questions/696850/…
LukeH
Oui, vous pouvez écrire cela assez facilement en tant que méthode d'extension .ToDelimitedString. J'irais avec ma chaîne de ligne unique. Rejoignez-en une plutôt que d'utiliser un StringBuilder et rognez le dernier caractère.
Keith
3

vous pouvez convertir IList en un tableau à l'aide de ToArray, puis exécuter une commande string.join sur le tableau.

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

Ils peuvent être facilement convertis en tableau à l'aide des extensions Linq dans .NET 3.5.

   var stringArray = stringList.ToArray();
Richard
la source
3

Vous pouvez également utiliser quelque chose comme ce qui suit après l'avoir converti en tableau à l'aide de l'une des méthodes répertoriées par d'autres:

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

Edit: Voici un autre exemple

Brad
la source
3

Je viens de résoudre ce problème avant de passer à travers cet article. Ma solution va quelque chose comme ci-dessous:

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

Appelé comme:

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

J'aurais pu tout aussi bien m'exprimer comme tel et j'aurais aussi été plus efficace:

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

Ma réponse est similaire à la solution d'agrégation ci-dessus, mais devrait être moins lourde en termes de pile d'appels car il n'y a pas d'appels délégués explicites:

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

Bien sûr, on peut étendre la signature pour qu'elle soit indépendante du délimiteur. Je ne suis vraiment pas un fan de l'appel sb.Remove () et je voudrais le refactoriser pour qu'il soit une boucle en ligne droite sur un IEnumerable et utiliser MoveNext () pour déterminer s'il faut ou non écrire une virgule. Je vais tripoter et publier cette solution si j'y tombe dessus.


Voici ce que je voulais au départ:

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

Aucun stockage temporaire de tableau ou de liste requis et aucun StringBuilder Remove()ou Length--piratage requis.

Dans ma bibliothèque de framework, j'ai fait quelques variations sur cette signature de méthode, chaque combinaison d'inclure delimiterles converterparamètres et avec l'utilisation de ","et x.ToString()par défaut, respectivement.

James Dunne
la source
3

J'espère que c'est le moyen le plus simple

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

Je suis tombé sur cette discussion en recherchant une bonne méthode C # pour joindre des chaînes comme cela se fait avec la méthode MySql CONCAT_WS(). Cette méthode diffère de la string.Join()méthode en ce qu'elle n'ajoute pas le signe séparateur si les chaînes sont NULL ou vides.

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

ne retournera que Lastnamesi le prénom est vide, tandis que

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

reviendra strLastname + ", "dans le même cas.

Voulant le premier comportement, j'ai écrit les méthodes suivantes:

    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
la source
2

J'ai écrit quelques méthodes d'extension pour le faire d'une manière efficace:

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

Cela dépend de

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

        return s+delim+item;
    }
Paul Houle
la source
3
L'utilisation de l'opérateur + pour concaténer des chaînes n'est pas géniale car elle entraînera l'allocation d'une nouvelle chaîne à chaque fois. De plus, bien que le StringBuilder puisse être implicitement converti en chaîne, le faire fréquemment (à chaque itération de votre boucle) irait à l'encontre du but d'avoir un générateur de chaîne.
Daniel Fortunov
2

Vous pouvez utiliser .ToArray()sur Listset IEnumerables, puis utiliser String.Join()comme vous le souhaitez.

JoshJordan
la source
0

Si les chaînes que vous souhaitez joindre se trouvent dans la liste d'objets, vous pouvez également faire quelque chose comme ceci:

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