Criando uma lista separada por vírgula a partir de IList <string> ou IEnumerable <string>


851

Qual é a maneira mais limpa de criar uma lista separada por vírgula de valores de seqüência de caracteres de um IList<string>ou IEnumerable<string>?

String.Join(...)opera de string[]maneira que pode ser complicado trabalhar quando tipos como IList<string>ou IEnumerable<string>não podem ser facilmente convertidos em uma matriz de cadeias.


5
Oh ... gritos. Eu perdi a adição do método de extensão ToArray em 3.5: #public static TSource[] ToArray<TSource>(this IEnumerable<TSource> source)
— Daniel Fortunov 28/04/09

1
Se você chegou a essa pergunta procurando um meio de escrever CSV, vale lembrar que simplesmente inserir vírgulas entre os itens é insuficiente e causará falhas no caso de aspas e vírgulas nos dados de origem.
— spender

Respostas:


1448

.NET 4+

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

Detalhe e soluções pré .Net 4.0

IEnumerable<string>pode ser convertido em uma matriz de cadeia muito facilmente com LINQ (.NET 3.5):

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

É fácil escrever o método auxiliar equivalente se você precisar:

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

Então chame assim:

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

Você pode ligar string.Join. Obviamente, você não precisa usar um método auxiliar:

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

O último é um pouco de um bocado embora :)

É provável que seja a maneira mais simples de fazê-lo e também tenha um bom desempenho - há outras perguntas sobre exatamente como é o desempenho, incluindo (mas não limitado a) este .

No .NET 4.0, há mais sobrecargas disponíveis no string.Join, então você pode simplesmente escrever:

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

Muito mais simples :)


O método auxiliar envolve a criação de duas listas desnecessárias. Essa é realmente a melhor maneira de resolver o problema? Por que não concatenar você mesmo em um loop foreach?
— Eric

4
O método auxiliar cria apenas uma lista e uma matriz. O ponto é que o resultado precisa ser uma matriz, não uma lista ... e você precisa saber o tamanho de uma matriz antes de iniciar. A prática recomendada diz que você não deve enumerar uma fonte mais de uma vez no LINQ, a menos que seja necessário - pode estar fazendo todos os tipos de coisas desagradáveis. Então, você fica lendo os buffers e redimensionando à medida que avança - o que é exatamente o que List<T>faz. Por que reinventar a roda?
— 23909 Jon Skeet

9
Não, o ponto é que o resultado precisa ser uma sequência concatenada. Não há necessidade de criar uma nova lista ou uma nova matriz para atingir esse objetivo. Esse tipo de mentalidade .NET me deixa triste.
— Eric

38
É isso aí. Toda resposta leva a Jon Skeet. Eu apenas vou var PurchaseBooks = AmazonContainer.Where (p => p.Author == "Jon Skeet"). Select ();
— Zachary Scott

3
@ codeMonkey0110: Bem, não faz sentido ter uma expressão de consulta ou chamada ToList. É bom usar string myStr = string.Join(",", foo.Select(a => a.someInt.ToString()))embora.
— 22814 Jon Skeet

179

FYI, a versão .NET 4.0 do string.Join()possui algumas sobrecargas extras , que funcionam em IEnumerablevez de apenas matrizes, incluindo uma que pode lidar com qualquer tipo T:

public static string Join(string separator, IEnumerable<string> values)
public static string Join<T>(string separator, IEnumerable<T> values)

2
Isso chamará o método T.ToString ()?
— Philippe Lavoie

Estava prestes a comentar isso na resposta de Jon. Obrigado por mencionar.
— precisa

2
Enfim, para fazer isso na propriedade de um objeto? (Ex: IEnumerable <Employee> eo objeto Employee tem uma propriedade string .SSN sobre ele, e se uma vírgula lista de SSN de separados.)
— granadaCoder

1
Você deve selecionar a sequência primeiro, embora possa criar um método de extensão que faça isso. str = emps.Select(e => e.SSN).Join(",")
— Xavier Poinas

65

A maneira mais fácil de ver isso é usando o Aggregatemétodo LINQ :

string commaSeparatedList = input.Aggregate((a, x) => a + ", " + x)

20
Isso não é apenas mais complicado (IMO) que o ToArray + Join, também é um pouco ineficiente - com uma grande sequência de entradas, que começará a apresentar um desempenho muito ruim.
— 31909 Jon Skeet

35
Ainda assim, é a mais bonita.
— Merritt

2
Você pode alimentar Agregar uma semente StringBuilder e, em seguida, tornar-se seu Agregado Func Func<StringBuilder,string,StringBuider>. Em seguida, basta chamar ToString()o StringBuilder retornado. É claro que não tão bonito embora :)
— Matt Greer

3
Esta é a maneira mais clara de fazer o que a pergunta pediu ao IMHO.
— Derek Morrison

8
Tenha em atenção que input.Countdeve ser superior a 1.
— Youngjae

31

Eu acho que a maneira mais limpa de criar uma lista separada por vírgula de valores de string é simplesmente:

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

Aqui está um exemplo completo:

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

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

Não há necessidade de criar uma função auxiliar, isso é incorporado ao .NET 4.0 e superior.


4
Observe que isso é aplicável a partir do .NET 4 (como Xavier apontou em sua resposta).
— Derek Morrison

Do ponto de vista .NET 4 novato com a experiência de menos de um mês esta resposta foi uma boa combinação de correção e concisão
— Dexygen

13

Comparando pelo desempenho, o vencedor é "Faça um loop, sb. Anexe-o e faça o passo anterior". Na verdade, "próximo passo enumerável e manual" é o mesmo bem (considere 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 |

Código:

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 foi usado


11

Desde que cheguei aqui enquanto procurava ingressar em uma propriedade específica de uma lista de objetos (e não o ToString ()), aqui está uma adição à resposta aceita:

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

9

Aqui está outro método de extensão:

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

8

Chegando um pouco tarde para esta discussão, mas esta é a minha contribuição fwiw. Eu tenho IList<Guid> OrderIdsque ser convertido em uma string CSV, mas o seguinte é genérico e funciona sem modificações com outros tipos:

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

Curto e agradável, usa StringBuilder para construir uma nova string, reduz o comprimento de StringBuilder em um para remover a última vírgula e retorna a string CSV.

Eu atualizei isso para usar vários Append()para adicionar string + vírgula. Pelo feedback de James, usei o Reflector para dar uma olhada StringBuilder.AppendFormat(). Acontece que AppendFormat()usa um StringBuilder para construir a string de formato, o que o torna menos eficiente nesse contexto do que apenas o uso de múltiplos Appends().


Gazumped, obrigado Xavier Eu não estava ciente dessa atualização no .Net4. O projeto no qual estou trabalhando ainda não deu o salto, então continuarei usando meu exemplo agora de pedestre.
— David Clarke

2
Isso falhará com uma fonte IEnumerable de item zero. sb.Length-- precisa de uma verificação de limites.
— James Dunne

Boa captura, obrigado James, no contexto em que estou usando isso, estou "garantido" de ter pelo menos um OrderId. Atualizei o exemplo e meu próprio código para incluir a verificação de limites (apenas para ter certeza).
— David Clarke

@ James Eu acho que chamar sb.Length-- um hack é um pouco duro. Efetivamente, estou apenas evitando o teste "if (notdone)" até o final, em vez de fazê-lo em cada iteração.
— David Clarke

1
@ James, meu ponto de vista é que geralmente há mais de uma resposta correta para as perguntas feitas aqui e a referência a uma delas como "hack" implica que está incorreto o que eu contestaria. Pelo pequeno número de guias que estou concatenando, a resposta de Daniel acima provavelmente seria perfeitamente adequada e é certamente mais sucinta / legível do que a minha resposta. Estou usando isso em apenas um lugar no meu código e sempre utilizarei uma vírgula como delimitador. YAGNI diz que não construa algo que você não precisará. DRY é aplicável se eu precisasse fazê-lo mais de uma vez, altura em que criaria um método de extensão. HTH.
— David Clarke

7

Algo um pouco fugaz, mas funciona:

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

Fornece a você um CSV de uma lista depois de atribuir o conversor (neste caso, d => d.DivisionID.ToString ("b")).

Hacky, mas funciona - poderia ser transformado em um método de extensão, talvez?


7

Aqui está o jeito que eu fiz, usando o que eu fiz em outros idiomas:

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

7

Necessidade específica quando devemos cercar por ', por 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()));

4

Temos uma função utilitária, algo como isto:

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

Que pode ser usado para juntar muitas coleções facilmente:

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

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

Observe que temos o parâmetro de coleção antes do lambda porque o intellisense seleciona o tipo de coleção.

Se você já possui uma enumeração de strings, tudo o que você precisa fazer é o ToArray:

string csv = string.Join( ",", myStrings.ToArray() );

2
Eu tenho um método de extensão que faz quase exatamente a mesma coisa, muito útil: stackoverflow.com/questions/696850/…
— #

Sim, você pode escrever isso como um método de extensão .ToDelimitedString com bastante facilidade. Eu iria com a minha única linha string.Join um em vez de usar um StringBuilder e aparar o último char.
— 291 Keith

3

você pode converter o IList em uma matriz usando o ToArray e, em seguida, executar um comando string.join na matriz.

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

3

Eles podem ser facilmente convertidos em uma matriz usando as extensões Linq no .NET 3.5.

   var stringArray = stringList.ToArray();

3

Você também pode usar algo como o seguinte depois de convertê-lo em uma matriz usando um dos métodos listados por outros:

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: Aqui está outro exemplo


3

Acabei de resolver esse problema antes de passar por este artigo. Minha solução é algo como abaixo:

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

Chamado como:

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

Eu também poderia ter sido tão facilmente expresso como tal e também teria sido mais eficiente:

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

3

Minha resposta é como a solução agregada acima, mas deve ter menos pilha de chamadas, pois não há chamadas delegadas explícitas:

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

Obviamente, pode-se estender a assinatura para ser independente do delimitador. Eu realmente não sou fã da chamada sb.Remove () e gostaria de refatorá-la para ser um loop while direto sobre um IEnumerable e usar MoveNext () para determinar se você deve ou não escrever uma vírgula. Vou mexer e postar essa solução, se eu encontrar.


Aqui está o que eu queria inicialmente:

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

Não é necessário armazenamento temporário de matriz ou lista e não é necessário StringBuilder Remove()ou Length--hackear.

Na minha biblioteca de framework, fiz algumas variações nessa assinatura de método, todas as combinações de incluindo delimiteros converterparâmetros e com o uso de ","e x.ToString()como padrões, respectivamente.


3

Espero que esta seja a maneira mais simples

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

3

Eu discuti essa discussão enquanto procurava por um bom método C # para juntar strings, como é feito com o método MySql CONCAT_WS(). Esse método difere do string.Join()método, pois ele não adiciona o sinal separador se as strings forem NULL ou vazias.

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

retornará apenas Lastnamese o primeiro nome estiver vazio, enquanto

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

retornará strLastname + ", "no mesmo caso.

Desejando o primeiro comportamento, escrevi os seguintes métodos:

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

2

Eu escrevi alguns métodos de extensão para fazer isso de uma maneira eficiente:

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

Isso depende de

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

        return s+delim+item;
    }

3
Usar o operador + para concatenar seqüências de caracteres não é bom, pois fará com que uma nova sequência seja alocada a cada vez. Além disso, embora o StringBuilder possa ser implicitamente convertido em uma string, fazê-lo com frequência (toda iteração do loop) anularia amplamente o objetivo de ter um construtor de strings.
— 22330 Daniel Fortunov

2

Você pode usar .ToArray()on Listse IEnumerables, e depois usar String.Join()como quiser.


0

Se as seqüências de caracteres que você deseja ingressar estiverem na Lista de Objetos, também poderá fazer algo assim:

var studentNames = string.Join(", ", students.Select(x => x.name));
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.