Usando LINQ para concatenar cadenas

345

¿Cuál es la forma más eficiente de escribir la vieja escuela?

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

... en LINQ?

etiquetas2k
fuente
1
¿Descubriste alguna otra forma genial de LINQ de hacer las cosas?
Robert S.
3
Bueno, la respuesta seleccionada y todas las demás opciones no funcionan en Linq to Entities.
Binoj Antony
3
@Binoj Antony, no hagas que tu base de datos haga una concatenación de cadenas.
Amy B
66
@ Pr0fess0rX: Porque no puede y porque no debería. No sé sobre otras bases de datos, pero en SQL Server solo puede concat (n) varcahr que lo limita a (n) varchar (max). No debería porque la lógica de negocios no debería implementarse en la capa de datos.
the_drow
¿alguna solución final con código fuente completo y alto rendimiento?
Kiquenet

Respuestas:

528

Esta respuesta muestra el uso de LINQ ( Aggregate) según lo solicitado en la pregunta y no está destinado para el uso diario. Debido a que esto no usa un StringBuilder, tendrá un rendimiento horrible para secuencias muy largas. Para el uso regular del código String.Joincomo se muestra en la otra respuesta

Use consultas agregadas como esta:

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

Esto produce:

, uno dos tres

Un agregado es una función que toma una colección de valores y devuelve un valor escalar. Los ejemplos de T-SQL incluyen min, max y sum. Tanto VB como C # tienen soporte para agregados. Tanto VB como C # admiten agregados como métodos de extensión. Usando la notación de puntos, uno simplemente llama a un método en un objeto IEnumerable .

Recuerde que las consultas agregadas se ejecutan de inmediato.

Más información - MSDN: consultas agregadas


Si realmente desea Aggregateutilizar la variante de uso StringBuilderpropuesta en el comentario de CodeMonkeyKing, que sería aproximadamente el mismo código que el habitual, String.Joinincluido un buen rendimiento para una gran cantidad de objetos:

 var res = words.Aggregate(
     new StringBuilder(), 
     (current, next) => current.Append(current.Length == 0? "" : ", ").Append(next))
     .ToString();
Jorge Ferreira
fuente
44
El primer ejemplo no genera "uno, dos, tres", genera ", uno, dos, tres" (Observe la coma inicial).
Mort
En su primer ejemplo, dado que inicia con "", el primer valor utilizado currentes una cadena vacía. Entonces, para 1 o más elementos, siempre obtendrá , al comienzo de la cadena.
Michael Yanni
@Mort He arreglado esto
sergtk
358
return string.Join(", ", strings.ToArray());

En .Net 4, hay una nueva sobrecarga para string.Joinque acepte IEnumerable<string>. El código se vería así:

return string.Join(", ", strings);
Amy B
fuente
2
Bien, entonces la solución no usa Linq, pero parece que me funciona bastante bien
Mat Roberts
33
ToArray es linq :)
Amy B
18
Esta es la respuesta más correcta. Es más rápido que la pregunta y la respuesta aceptada y es mucho más claro que Aggregate, que requiere una explicación de un párrafo cada vez que se usa.
PRMan
@realPro Completamente falso. github.com/microsoft/referencesource/blob/master/mscorlib/… línea 161
Amy B
125

¿Por qué usar Linq?

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

Eso funciona perfectamente y acepta cualquiera IEnumerable<string>hasta donde recuerdo. No necesita Aggregatenada aquí, que es mucho más lento.

Armin Ronacher
fuente
19
Aprender LINQ puede ser genial, y LINQ puede ser un buen medio para lograr el final, pero usar LINQ para obtener el resultado final sería malo, por decir lo menos, si no completamente estúpido
Jason Bunting
99
.NET 4.0 tiene una sobrecarga IEnumerable <string> e IEnumrable <T>, lo que hará que sea mucho más fácil de usar
Cine
3
Como señala Cine, .NET 4.0 tiene la sobrecarga. Las versiones anteriores no lo hacen. Sin embargo, aún puede String.Join(",", s.ToArray())en las versiones anteriores.
Martijn
1
FYI: se fusionó de stackoverflow.com/questions/122670/…
Shog9
@ Shog9 Merging hace que las respuestas aquí parezcan esfuerzos duplicados, y las marcas de tiempo no ayudan en absoluto ... Sigue siendo el camino a seguir.
nawfal
77

¿Has mirado el método de extensión Agregado?

var sa = (new[] { "yabba", "dabba", "doo" }).Aggregate((a,b) => a + "," + b);
Robert S.
fuente
23
Probablemente sea más lento que String.Join () y más difícil de leer en código. Sin embargo, responde la pregunta de una "manera LINQ" :-)
Chris Wenham
55
Sí, no quería manchar la respuesta con mis opiniones. : P
Robert S.
2
Es indudablemente un poco más lento, en realidad. Incluso usar Aggregate con un StringBuilder en lugar de concatenación es más lento que String.Join.
Joel Mueller
44
Hizo una prueba con 10,000,000 iteraciones, el agregado tomó 4.3 segundos y string.join tomó 2.3 segundos. Por lo tanto, diría que el rendimiento diferido no es importante para el 99% de los casos de uso común. Entonces, si ya está haciendo un montón de linq para procesar sus datos, generalmente no hay necesidad de romper esa buena sintaxis y usar string.join imo. gist.github.com/joeriks/5791981
joeriks
1
FYI: se fusionó de stackoverflow.com/questions/122670/…
Shog9
56

Ejemplo real de mi código:

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

Una consulta es un objeto que tiene una propiedad Name que es una cadena, y quiero los nombres de todas las consultas en la lista seleccionada, separadas por comas.

Daniel Earwicker
fuente
2
Teniendo en cuenta los comentarios sobre el rendimiento, debo agregar que el ejemplo es del código que se ejecuta una vez cuando se cierra un cuadro de diálogo, ¡y es poco probable que la lista tenga más de diez cadenas!
Daniel Earwicker
¿Alguna idea de cómo hacer esta misma tarea en Linq to Entities?
Binoj Antony
1
Excelente ejemplo Gracias por poner esto en un escenario del mundo real. Tenía la misma situación exacta, con una propiedad de un objeto que necesitaba concatenación.
Jessy Houle
1
Votaron por ayudarme a descubrir esa primera parte de seleccionar la propiedad de cadena de mi Lista <T>
Nikki9696
1
Escriba sobre el rendimiento de este enfoque con una matriz más grande.
Giulio Caccin
31

Aquí está el enfoque combinado Join / Linq que decidí después de mirar las otras respuestas y los problemas abordados en una pregunta similar (a saber, que Aggregate and Concatenate fallan con 0 elementos).

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

o (si sno es una cadena)

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

  • Simple
  • Fácil de leer y entender
  • funciona para elementos genéricos
  • permite usar objetos o propiedades de objetos
  • maneja el caso de elementos de longitud 0
  • podría usarse con filtrado Linq adicional
  • funciona bien (al menos en mi experiencia)
  • no requiere la creación (manual) de un objeto adicional (por ejemplo StringBuilder) para implementar

Y, por supuesto, Join se encarga de la molesta coma final que a veces se cuela en otros enfoques ( for, foreach), por lo que estaba buscando una solución de Linq en primer lugar.

brichins
fuente
1
paréntesis mal emparejado.
ctrl-alt-delor
1
FYI: se fusionó de stackoverflow.com/questions/122670/…
Shog9
3
Me gusta esta respuesta porque el uso de .Select()este tipo proporciona un lugar fácil para modificar cada elemento durante esta operación. Por ejemplo, envolviendo cada elemento en algún personaje asístring Result = String.Join(",", split.Select(s => "'" + s + "'"));
Sam Storie
29

Puedes usar StringBuilderen 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());

(El Selectestá ahí solo para mostrar que puedes hacer más cosas de LINQ).

jonathan.s
fuente
2
+1 bien. Sin embargo, en mi opinión, es mejor evitar agregar el "" extra que borrarlo después. Algo así comonew[] {"one", "two", "three"}.Aggregate(new StringBuilder(), (sb, s) =>{if (sb.Length > 0) sb.Append(", ");sb.Append(s);return sb;}).ToString();
dss539
55
Ahorraría preciosos ciclos de reloj al no verificar if (length > 0)el linq y al sacarlo.
Binoj Antony
1
Estoy de acuerdo con dss539. Mi versión está en la línea denew[] {"", "one", "two", "three"}.Aggregate(new StringBuilder(), (sb, s) => (String.IsNullOrEmpty(sb.ToString())) ? sb.Append(s) : sb.Append(", ").Append(s)).ToString();
ProfNimrod
22

Datos de rendimiento rápido para el caso StringBuilder vs Select & Aggregate sobre más de 3000 elementos:

Prueba unitaria - Duración (segundos)
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);
    }
usuario337754
fuente
Útil para decidir ir por la ruta que no es de LINQ para esto
crabCRUSHERclamCOLLECTOR
44
La diferencia horaria es probablemente StringBuilder vs String Concatination usando +. Nada que ver con LINQ o Aggregate. Coloque StringBuilder en LINQ Aggregate (muchos ejemplos en SO), y debería ser igual de rápido.
controlbox
16

Siempre uso el método de extensión:

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
fuente
55
string.Joinen .net 4 ya puede tomar un IEnumerable<T>para cualquier arbitrario T.
Recurrente
1
FYI: se fusionó de stackoverflow.com/questions/122670/…
Shog9
12

Con la 'manera súper genial de LINQ ', podría estar hablando de la forma en que LINQ hace que la programación funcional sea mucho más aceptable con el uso de métodos de extensión. Quiero decir, el azúcar sintáctico que permite encadenar funciones de una manera visualmente lineal (una después de la otra) en lugar de anidar (una dentro de la otra). Por ejemplo:

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

se puede escribir así:

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

Puede ver cómo el segundo ejemplo es más fácil de leer. También puede ver cómo se pueden agregar más funciones con menos problemas de sangría o los pares de cierre de Lispy que aparecen al final de la expresión.

Muchas de las otras respuestas indican que este String.Joines el camino a seguir porque es el más rápido o el más simple de leer. Pero si toma mi interpretación de la ' forma LINQ súper genial ', entonces la respuesta es usar, String.Joinpero envuélvala en un método de extensión de estilo LINQ que le permitirá encadenar sus funciones de una manera visualmente agradable. Entonces, si quieres escribir sa.Concatenate(", "), solo necesitas crear algo como esto:

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

Esto proporcionará un código que es tan eficaz como la llamada directa (al menos en términos de complejidad del algoritmo) y en algunos casos puede hacer que el código sea más legible (según el contexto), especialmente si otro código en el bloque está usando el estilo de función encadenada .

tpower
fuente
1
El número de errores tipográficos en este hilo es una locura: seperator => separator, Concatinate => Concatenate
SilverSideDown
1
FYI: se fusionó de stackoverflow.com/questions/122670/…
Shog9
5

Hay varias respuestas alternativas en esta pregunta anterior , que ciertamente apuntaba a una matriz entera como fuente, pero recibió respuestas generalizadas.

Jon Skeet
fuente
5

Aquí está usando LINQ puro como una sola expresión:

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

¡Y es bastante rápido!

cdiggins
fuente
3

Voy a hacer un poco de trampa y arrojar una nueva respuesta a esto que parece resumir lo mejor de todo aquí en lugar de incluirlo en un comentario.

Entonces puedes una línea de esto:

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

Editar: primero querrá verificar si hay un enumerable vacío o agregar un .Replace("\a",string.Empty);al final de la expresión. Supongo que podría haber estado tratando de ser demasiado inteligente.

La respuesta de @ a.friend podría ser un poco más eficaz, no estoy seguro de lo que Reemplazar hace bajo el capó en comparación con Eliminar. La única otra advertencia es que si por alguna razón quisieras concatenar cadenas que terminaran en \ a, perderías tus separadores ... Me parece poco probable. Si ese es el caso, tiene otros personajes elegantes para elegir.

Chris Marisic
fuente
2

Puede combinar LINQ y con string.join()bastante eficacia. Aquí estoy eliminando un elemento de una cadena. También hay mejores formas de hacerlo, pero aquí está:

filterset = String.Join(",",
                        filterset.Split(',')
                                 .Where(f => mycomplicatedMatch(f,paramToMatch))
                       );
Andiih
fuente
1
FYI: se fusionó de stackoverflow.com/questions/122670/…
Shog9
1

Muchas opciones aquí. Puede usar LINQ y un StringBuilder para obtener el rendimiento de la siguiente manera:

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
fuente
Sería más rápido no verificar el builder.Length > 0ForEach y eliminar la primera coma después de ForEach
Binoj Antony
1

Hice lo siguiente rápido y sucio cuando analicé un archivo de registro IIS usando linq, funcionó a 1 millón de líneas bastante bien (15 segundos), aunque obtuvo un error de falta de memoria al intentar 2 millones de líneas.

    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 verdadera razón por la que usé linq fue por un Distinct () que necesitaba anteriormente:

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.
fuente
1
FYI: se fusionó de stackoverflow.com/questions/122670/…
Shog9
0

Hace un blog escribí sobre esto hace un tiempo, lo que hice parece ser exactamente lo que estás buscando:

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

En la publicación del blog describa cómo implementar métodos de extensión que funcionen en IEnumerable y se denominen Concatenate, esto le permitirá escribir cosas como:

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

O cosas más elaboradas como:

var methodNames = typeof(IFoo).GetMethods().Select(x => x.Name);
string result = methodNames.Concatenate(", ");
Patrik Hägne
fuente
1
FYI: se fusionó de stackoverflow.com/questions/122670/…
Shog9
¿Puedes concatenar el código aquí para que la respuesta sea más fácil de entender?
Giulio Caccin