Salida de cadena: ¿formato o concat en C #?

178

Digamos que desea generar o concatenar cadenas. ¿Cuál de los siguientes estilos prefieres?

  • var p = new { FirstName = "Bill", LastName = "Gates" };

  • Console.WriteLine("{0} {1}", p.FirstName, p.LastName);

  • Console.WriteLine(p.FirstName + " " + p.LastName);

¿Prefieres usar el formato o simplemente concatenas cadenas? ¿Cual es tu favorito? ¿Uno de estos está lastimando tus ojos?

¿Tienes algún argumento racional para usar uno y no el otro?

Yo iría por el segundo.

Philippe
fuente

Respuestas:

88

Prueba este código.

Es una versión ligeramente modificada de su código.
1. Eliminé Console.WriteLine, ya que probablemente sea un orden de magnitud más lento que el que estoy tratando de medir.
2. Estoy iniciando el cronómetro antes del ciclo y deteniéndolo justo después, de esta manera no estoy perdiendo precisión si la función toma, por ejemplo, 26,4 ticks para ejecutarse.
3. La forma en que dividió el resultado por algunas iteraciones fue incorrecta. Vea lo que sucede si tiene 1000 milisegundos y 100 milisegundos. En ambas situaciones, obtendrá 0 ms después de dividirlo por 1000000.

Stopwatch s = new Stopwatch();

var p = new { FirstName = "Bill", LastName = "Gates" };

int n = 1000000;
long fElapsedMilliseconds = 0, fElapsedTicks = 0, cElapsedMilliseconds = 0, cElapsedTicks = 0;

string result;
s.Start();
for (var i = 0; i < n; i++)
    result = (p.FirstName + " " + p.LastName);
s.Stop();
cElapsedMilliseconds = s.ElapsedMilliseconds;
cElapsedTicks = s.ElapsedTicks;
s.Reset();
s.Start();
for (var i = 0; i < n; i++)
    result = string.Format("{0} {1}", p.FirstName, p.LastName);
s.Stop();
fElapsedMilliseconds = s.ElapsedMilliseconds;
fElapsedTicks = s.ElapsedTicks;
s.Reset();


Console.Clear();
Console.WriteLine(n.ToString()+" x result = string.Format(\"{0} {1}\", p.FirstName, p.LastName); took: " + (fElapsedMilliseconds) + "ms - " + (fElapsedTicks) + " ticks");
Console.WriteLine(n.ToString() + " x result = (p.FirstName + \" \" + p.LastName); took: " + (cElapsedMilliseconds) + "ms - " + (cElapsedTicks) + " ticks");
Thread.Sleep(4000);

Esos son mis resultados:

1000000 x resultado = string.Format ("{0} {1}", p.Primer nombre, p.LastName); tomó: 618ms - 2213706 ticks
1000000 x resultado = (p.Primer Nombre + "" + p.LastName); tomó: 166ms - 595610 garrapatas

Michał Piaskowski
fuente
1
Muy interesante. Obtuve un promedio de 224 ms frente a 48 ms, una mejora de x4.66, incluso mejor que su x3.72. Me pregunto si hay una herramienta de postcompilación que pueda reescribir la IL de la string.Formatque no utiliza ninguna función de formato compuesto (es decir, simple {0}) y reemplazarla con la concatenación de cadenas considerablemente más rápida. Me pregunto que tal hazaña se pueda lograr con una reescritura IL existente como PostSharp.
Allon Guralnek
31
Las cadenas son inmutables, esto significa que la misma pequeña porción de memoria se usa una y otra vez en su código. Agregar las mismas dos cadenas juntas y crear la misma cadena nueva una y otra vez no afecta la memoria. .Net es lo suficientemente inteligente como para usar la misma referencia de memoria. Por lo tanto, su código no prueba realmente la diferencia entre los dos métodos concat. Ver código en mi respuesta a continuación.
Ludington el
1
Honestamente, siempre concateno ya que es más fácil de leer para mí y wow es más rápido :)
puretppc
Entonces, ¿la velocidad es la única razón para elegir una sobre la otra?
niico
158

Me sorprende que tanta gente quiera encontrar de inmediato el código que se ejecuta más rápido. Si UN MILLÓN de iteraciones TODAVÍA tardan menos de un segundo en procesarse, ¿será esto en CUALQUIER FORMA notable para el usuario final? No muy probable.

Optimización prematura = FALLO.

Optaría por la String.Formatopción, solo porque tiene más sentido desde el punto de vista arquitectónico. No me importa el rendimiento hasta que se convierta en un problema (y si lo hiciera, me preguntaría: ¿Necesito concatenar un millón de nombres a la vez? Seguramente no encajarán en la pantalla ...)

Considere si su cliente más tarde quiere cambiarlo para que pueda configurar si se muestra "Firstname Lastname"o no. "Lastname, Firstname."Con la opción Formato, esto es fácil: simplemente cambie la cadena de formato. Con el concat, necesitarás un código extra. Claro que eso no suena como un gran problema en este ejemplo en particular, sino extrapolar.

Fredrik Kalseth
fuente
47
Buen punto en términos de "Optimización prematura == FALLO", sí. Pero, cuando comienza a pagar por la huella de ejecución (nube e infraestructura como servicio, ¿alguien?) Y / o comienza a admitir 1 millón de usuarios en algo, entonces la respuesta a un solo usuario en una solicitud no es la pregunta. El costo de atender una solicitud a un usuario es un costo para su
balance
23
Esto es totalmente incorrecto. En un entorno de desarrollo web, a menudo su código de generación de cadenas será profundo tanto en su modelo, vistas y controladores y puede ser llamado decenas de miles de veces por carga de página. Reducir el tiempo dedicado a evaluar el código de generación de cadenas en un 50% podría ser una gran ganancia.
Benjamin Sussman
2
Una pregunta como esta no solo se aplicará en la única instancia del OP. La respuesta es el tipo de cosas que la gente puede recordar como "¿de qué manera debo armar las cuerdas?" mientras escriben todo su código.
Phil Miller
66
@Benjamin: ... en cuyo caso, sería un perfil y encontraría que ese es su cuello de botella. Sin embargo, apostaría dinero a que solo lo estás sacando de la nada; Habiendo escrito y perfilado varias aplicaciones web en el pasado, casi siempre he encontrado que el cuello de botella en los tiempos de respuesta (en el lado del servidor) son las consultas de la base de datos.
BlueRaja - Danny Pflughoeft
2
Esto definitivamente no es una optimización prematura. Toda la falacia. El rendimiento de la cadena puede detener completamente las interfaces de usuario, especialmente en .NET si está formateando y creando cadenas. ubiquity.acm.org/article.cfm?id=1513451
user99999991
54

Dios mío, después de leer una de las otras respuestas, intenté invertir el orden de las operaciones, así que primero realicé la concatenación, luego el String.Format ...

Bill Gates
Console.WriteLine(p.FirstName + " " + p.LastName); took: 8ms - 30488 ticks
Bill Gates
Console.WriteLine("{0} {1}", p.FirstName, p.LastName); took: 0ms - 182 ticks

Entonces, el orden de las operaciones hace una GRAN diferencia, o más bien la primera operación es SIEMPRE mucho más lenta.

Aquí están los resultados de una ejecución en la que las operaciones se completan más de una vez. He intentado cambiar los pedidos, pero las cosas generalmente siguen las mismas reglas, una vez que se ignora el primer resultado:

Bill Gates
Console.WriteLine(FirstName + " " + LastName); took: 5ms - 20335 ticks
Bill Gates
Console.WriteLine(FirstName + " " + LastName); took: 0ms - 156 ticks
Bill Gates
Console.WriteLine(FirstName + " " + LastName); took: 0ms - 122 ticks
Bill Gates
Console.WriteLine("{0} {1}", FirstName, LastName); took: 0ms - 181 ticks
Bill Gates
Console.WriteLine("{0} {1}", FirstName, LastName); took: 0ms - 122 ticks
Bill Gates
String.Concat(FirstName, " ", LastName); took: 0ms - 142 ticks
Bill Gates
String.Concat(FirstName, " ", LastName); took: 0ms - 117 ticks

Como puede ver, las ejecuciones posteriores del mismo método (refactoré el código en 3 métodos) son incrementalmente más rápidas. El más rápido parece ser el método Console.WriteLine (String.Concat (...)), seguido de la concatenación normal, y luego las operaciones formateadas.

El retraso inicial en el inicio es probablemente la inicialización de Console Stream, como colocar una Console.Writeline ("Start!") Antes de que la primera operación vuelva a poner en línea todos los tiempos.

revs samjudson
fuente
2
Luego elimine Console.WriteLine completamente de sus pruebas. ¡Está sesgando los resultados!
CShark
Siempre comienzo con un escenario desechable o de "control" cuando
ejecuto
36

Las cadenas son inmutables, esto significa que la misma pequeña porción de memoria se usa una y otra vez en su código. Agregar las mismas dos cadenas juntas y crear la misma cadena nueva una y otra vez no afecta la memoria. .Net es lo suficientemente inteligente como para usar la misma referencia de memoria. Por lo tanto, su código no prueba realmente la diferencia entre los dos métodos concat.

Probar esto para el tamaño:

Stopwatch s = new Stopwatch();

int n = 1000000;
long fElapsedMilliseconds = 0, fElapsedTicks = 0, cElapsedMilliseconds = 0, cElapsedTicks = 0, sbElapsedMilliseconds = 0, sbElapsedTicks = 0;

Random random = new Random(DateTime.Now.Millisecond);

string result;
s.Start();
for (var i = 0; i < n; i++)
    result = (random.Next().ToString() + " " + random.Next().ToString());
s.Stop();
cElapsedMilliseconds = s.ElapsedMilliseconds;
cElapsedTicks = s.ElapsedTicks;
s.Reset();

s.Start();
for (var i = 0; i < n; i++)
    result = string.Format("{0} {1}", random.Next().ToString(), random.Next().ToString());
s.Stop();
fElapsedMilliseconds = s.ElapsedMilliseconds;
fElapsedTicks = s.ElapsedTicks;
s.Reset();

StringBuilder sb = new StringBuilder();
s.Start();
for(var i = 0; i < n; i++){
    sb.Clear();
    sb.Append(random.Next().ToString());
    sb.Append(" ");
    sb.Append(random.Next().ToString());
    result = sb.ToString();
}
s.Stop();
sbElapsedMilliseconds = s.ElapsedMilliseconds;
sbElapsedTicks = s.ElapsedTicks;
s.Reset();

Console.WriteLine(n.ToString() + " x result = string.Format(\"{0} {1}\", p.FirstName, p.LastName); took: " + (fElapsedMilliseconds) + "ms - " + (fElapsedTicks) + " ticks");
Console.WriteLine(n.ToString() + " x result = (p.FirstName + \" \" + p.LastName); took: " + (cElapsedMilliseconds) + "ms - " + (cElapsedTicks) + " ticks");
Console.WriteLine(n.ToString() + " x sb.Clear();sb.Append(random.Next().ToString()); sb.Append(\" \"); sb.Append(random.Next().ToString()); result = sb.ToString(); took: " + (sbElapsedMilliseconds) + "ms - " + (sbElapsedTicks) + " ticks");
Console.WriteLine("****************");
Console.WriteLine("Press Enter to Quit");
Console.ReadLine();

Salida de muestra:

1000000 x result = string.Format("{0} {1}", p.FirstName, p.LastName); took: 513ms - 1499816 ticks
1000000 x result = (p.FirstName + " " + p.LastName); took: 393ms - 1150148 ticks
1000000 x sb.Clear();sb.Append(random.Next().ToString()); sb.Append(" "); sb.Append(random.Next().ToString()); result = sb.ToString(); took: 405ms - 1185816 ticks
MrPhil
fuente
1
Se agregó un StringBuilder y una salida de muestra a la respuesta
mikeschuld
Veo cómo string.Formatvale la pena usar el pequeño rendimiento aquí. Arquitectónicamente es mejor ya que significa que puedes alterar el formato más fácilmente. Pero el generador de cadenas realmente no veo el punto. Todos los otros hilos aquí dicen que debes usar Stringbuilder en lugar de concatenar cadenas. Cual es la ventaja? Claramente no es velocidad, como lo demuestra este punto de referencia.
roryok
22

Lástima los pobres traductores

Si sabe que su aplicación permanecerá en inglés, entonces bien, guarde los tics del reloj. Sin embargo, muchas culturas generalmente verían Apellido Nombre en, por ejemplo, direcciones.

Así que úsala string.Format(), especialmente si alguna vez vas a hacer que tu aplicación vaya a un lugar donde el inglés no sea el primer idioma.

Jeremy McGee
fuente
2
¿Cómo se string.Format()comportaría diferente en diferentes culturas? ¿No seguiría imprimiendo el nombre y luego el apellido? Parece que tendría que tener en cuenta la cultura diferente en ambas situaciones. Siento que me falta algo aquí.
Broots Waymb
2
Estoy de acuerdo con @DangerZone ... ¿cómo sabrías que string.Format()estás usando un nombre para una dirección? Si se string.Format()cambia {0} {1}según la cultura, lo consideraría roto.
Alex McMillan
2
Creo que el punto que Jeremy estaba tratando de hacer es que, en el escenario descrito para admitir diferentes países, puede ser apropiado extraer la cadena de formato en sí misma a un recurso de idioma. Para la mayoría de los países, esa cadena sería "{0} {1}", pero para los países donde el apellido es la operación típica (por ejemplo, Hungría, Hong Kong, Camboya, China, Japón, Corea, Madagascar, Taiwán, Vietnam y partes de India) esa cadena sería "{1} {0}" en su lugar.
Richard J Foster
En efecto. O, más sutilmente, agregue la cadena de formato como un atributo de la persona. Por ejemplo, me gusta tener mi apellido después de mi nombre de pila, pero mi colega Beng no.
Jeremy McGee
14

Aquí están mis resultados en más de 100,000 iteraciones:

Console.WriteLine("{0} {1}", p.FirstName, p.LastName); took (avg): 0ms - 689 ticks
Console.WriteLine(p.FirstName + " " + p.LastName); took (avg): 0ms - 683 ticks

Y aquí está el código de banco:

Stopwatch s = new Stopwatch();

var p = new { FirstName = "Bill", LastName = "Gates" };

//First print to remove the initial cost
Console.WriteLine(p.FirstName + " " + p.LastName);
Console.WriteLine("{0} {1}", p.FirstName, p.LastName);

int n = 100000;
long fElapsedMilliseconds = 0, fElapsedTicks = 0, cElapsedMilliseconds = 0, cElapsedTicks = 0;

for (var i = 0; i < n; i++)
{
    s.Start();
    Console.WriteLine(p.FirstName + " " + p.LastName);
    s.Stop();
    cElapsedMilliseconds += s.ElapsedMilliseconds;
    cElapsedTicks += s.ElapsedTicks;
    s.Reset();
    s.Start();
    Console.WriteLine("{0} {1}", p.FirstName, p.LastName);
    s.Stop();
    fElapsedMilliseconds += s.ElapsedMilliseconds;
    fElapsedTicks += s.ElapsedTicks;
    s.Reset();
}

Console.Clear();

Console.WriteLine("Console.WriteLine(\"{0} {1}\", p.FirstName, p.LastName); took (avg): " + (fElapsedMilliseconds / n) + "ms - " + (fElapsedTicks / n) + " ticks");
Console.WriteLine("Console.WriteLine(p.FirstName + \" \" + p.LastName); took (avg): " + (cElapsedMilliseconds / n) + "ms - " + (cElapsedTicks / n) + " ticks");

Entonces, no sé de quién es la respuesta para marcar como respuesta :)

Philippe
fuente
¿Por qué el fondo es azul para esta respuesta?
user88637
@yossi es azul porque el que responde es el mismo que el que pregunta
Davy8
9

Concatenar cadenas está bien en un escenario simple como ese: es más complicado con algo más complicado que eso, incluso Apellido, Nombre. Con el formato que puede ver, de un vistazo, cuál será la estructura final de la cadena al leer el código, con la concatenación se vuelve casi imposible discernir inmediatamente el resultado final (excepto con un ejemplo muy simple como este).

Lo que eso significa a largo plazo es que cuando regrese para hacer un cambio en su formato de cadena, tendrá la capacidad de aparecer y hacer algunos ajustes en la cadena de formato, o arrugar la frente y comenzar a moverse. tipos de accesores de propiedades mezclados con texto, lo que es más probable que presente problemas.

Si está usando .NET 3.5, puede usar un método de extensión como este y obtener una sintaxis fluida y sencilla como esta:

string str = "{0} {1} is my friend. {3}, {2} is my boss.".FormatWith(prop1,prop2,prop3,prop4);

Finalmente, a medida que su aplicación crece en complejidad, puede decidir que para mantener cuerdas en su aplicación desea moverlas a un archivo de recursos para localizarlas o simplemente a un ayudante estático. Esto será MUCHO más fácil de lograr si ha usado formatos consistentemente, y su código puede ser simplemente refactorizado para usar algo como

string name = String.Format(ApplicationStrings.General.InformalUserNameFormat,this.FirstName,this.LastName);
Nathan
fuente
7

Para una manipulación muy simple, usaría la concatenación, pero una vez que supere los 2 o 3 elementos, el formato se vuelve más apropiado para la OMI.

Otra razón para preferir String.Format es que las cadenas .NET son inmutables y hacerlo de esta manera crea menos copias temporales / intermedias.

usuario2189331
fuente
6

Si bien entiendo totalmente la preferencia de estilo y la concatenación elegida para mi primera respuesta, en parte basada en mi propia preferencia, parte de mi decisión se basó en el pensamiento de que la concatenación sería más rápida. Entonces, por curiosidad, lo probé y los resultados fueron asombrosos, especialmente para una cadena tan pequeña.

Usando el siguiente código:

    System.Diagnostics.Stopwatch s = new System.Diagnostics.Stopwatch();

    var p = new { FirstName = "Bill", LastName = "Gates" };

    s.Start();
    Console.WriteLine("{0} {1}", p.FirstName, p.LastName);
    s.Stop();
    Console.WriteLine("Console.WriteLine(\"{0} {1}\", p.FirstName, p.LastName); took: " + s.ElapsedMilliseconds + "ms - " + s.ElapsedTicks + " ticks");

    s.Reset();
    s.Start();
    Console.WriteLine(p.FirstName + " " + p.LastName);
    s.Stop();

    Console.WriteLine("Console.WriteLine(p.FirstName + \" \" + p.LastName); took: " + s.ElapsedMilliseconds + "ms - " + s.ElapsedTicks + " ticks");

Obtuve los siguientes resultados:

Bill Gates
Console.WriteLine("{0} {1}", p.FirstName, p.LastName); took: 2ms - 7280 ticks
Bill Gates
Console.WriteLine(p.FirstName + " " + p.LastName); took: 0ms - 67 ticks

¡Usar el método de formateo es más de 100 veces más lento! La concatenación ni siquiera se registró como 1 ms, por lo que también envié los tics del temporizador.

Adam Haile
fuente
2
Pero, por supuesto, debe realizar la operación más de una vez para obtener mediciones.
erikkallen el
2
¿Y perder la llamada a Console.Writeline () ya que está fuera del alcance de la pregunta?
Aidanapword
probaste con un constructor de cuerdas? ;)
niico
6

A partir de C # 6.0 se pueden usar cadenas interpoladas para hacer esto, lo que simplifica aún más el formato.

var name = "Bill";
var surname = "Gates";
MessageBox.Show($"Welcome to the show, {name} {surname}!");

Una expresión de cadena interpolada se parece a una cadena de plantilla que contiene expresiones. Una expresión de cadena interpolada crea una cadena al reemplazar las expresiones contenidas con las representaciones ToString de los resultados de las expresiones.

Las cadenas interpoladas tienen un rendimiento similar al de String.Format, pero una legibilidad mejorada y una sintaxis más corta, debido al hecho de que los valores y expresiones se insertan en línea.

Consulte también este artículo de dotnetperls sobre interpolación de cadenas.

Si está buscando una forma predeterminada de formatear sus cadenas, esto tiene sentido en términos de legibilidad y rendimiento (excepto si los microsegundos harán una diferencia en su caso de uso específico).

Saragis
fuente
5

Para la concatenación básica de cadenas, generalmente utilizo el segundo estilo, más fácil de leer y más simple. Sin embargo, si estoy haciendo una combinación de cadenas más complicada, generalmente opto por String.Format.

String.Format ahorra en muchas citas y ventajas ...

Console.WriteLine("User {0} accessed {1} on {2}.", user.Name, fileName, timestamp);
vs
Console.WriteLine("User " + user.Name + " accessed " + fileName + " on " + timestamp + ".");

Solo se guardaron unos pocos caracteres, pero creo que, en este ejemplo, el formato lo hace mucho más limpio.

Miguel
fuente
5

Una mejor prueba sería mirar su memoria usando Perfmon y los contadores de memoria CLR. Tengo entendido que la razón por la que desea utilizar String.Format en lugar de simplemente concatenar cadenas es que, dado que las cadenas son inmutables, está cargando innecesariamente al recolector de basura con cadenas temporales que deben reclamarse en la próxima pasada.

StringBuilder y String.Format, aunque son potencialmente más lentos, son más eficientes en memoria.

¿Qué tiene de malo la concatenación de cadenas?

David Hill
fuente
Estoy de acuerdo; Cada operación de cadena crea una nueva copia de la cadena. Toda esa memoria será reclamada por el recolector de basura tarde o temprano. Por lo tanto, la asignación de muchas cadenas puede volver a morderte más tarde.
Marnix van Valen
5

En general, prefiero el primero, ya que especialmente cuando las cadenas se alargan puede ser mucho más fácil de leer.

El otro beneficio es que creo que es el rendimiento, ya que este último realmente realiza 2 declaraciones de creación de cadena antes de pasar la cadena final al método Console.Write. String.Format utiliza un StringBuilder debajo de las cubiertas, creo, por lo que se evitan múltiples concatenaciones.

Sin embargo, debe tenerse en cuenta que si los parámetros que está pasando a String.Format (y otros métodos similares como Console.Write) son tipos de valor, se incluirán en un recuadro antes de pasarlos, lo que puede proporcionar sus propios resultados de rendimiento. Publicación de blog sobre esto aquí .

revs samjudson
fuente
1
Esa publicación de blog está ahora en: jeffbarnes.net/blog/post/2006/08/08/… . Sufro de representante insuficiente para editar.
Richard Slater
5

Dentro de una semana a partir del 19 de agosto de 2015, esta pregunta tendrá exactamente siete (7) años. Ahora hay una mejor manera de hacer esto. Mejor en términos de mantenibilidad, ya que no he hecho ninguna prueba de rendimiento en comparación con solo concatenar cadenas (pero ¿importa en estos días? ¿Unos pocos milisegundos de diferencia?). La nueva forma de hacerlo con C # 6.0 :

var p = new { FirstName = "Bill", LastName = "Gates" };
var fullname = $"{p.FirstName} {p.LastName}";

Esta nueva característica es mejor , en mi opinión, y en realidad mejor en nuestro caso, ya que tenemos códigos donde construimos cadenas de consulta cuyos valores dependen de algunos factores. Imagine una cadena de consulta donde tenemos 6 argumentos. Entonces, en lugar de hacer un, por ejemplo:

var qs = string.Format("q1={0}&q2={1}&q3={2}&q4={3}&q5={4}&q6={5}", 
    someVar, anotherVarWithLongName, var3, var4, var5, var6)

se puede escribir así y es más fácil de leer:

var qs=$"q1={someVar}&q2={anotherVarWithLongName}&q3={var3}&q4={var4}&q5={var5}&q6={var6}";
von v.
fuente
De hecho, la nueva forma de C # 6.0 es mejor que las alternativas anteriores, al menos desde un punto de vista de legibilidad.
Philippe
Así es. Y también es más seguro ya que no tiene que preocuparse por qué objeto va a qué índice (marcador de posición) ya que colocará directamente los objetos donde desea que estén.
von v.
Por cierto, en realidad llama Formato (al menos con Roslyn).
Philippe
Por cierto, a lo que se refiere este póster se llama "interpolación de cadenas", y se aborda en otra parte de este hilo.
CShark
4
  1. El formateo es la forma ".NET" de hacerlo. Ciertas herramientas de refactorización (¡Refactor! Para uno) incluso propondrán refactorizar el código de estilo concat para usar el estilo de formato.
  2. El formateo es más fácil de optimizar para el compilador (aunque el segundo probablemente será refactorizado para usar el método 'Concat' que es rápido).
  3. El formato suele ser más claro de leer (especialmente con el formato "elegante").
  4. El formato significa llamadas implícitas a '.ToString' en todas las variables, lo cual es bueno para la legibilidad.
  5. De acuerdo con "C # efectivo", las implementaciones de .NET 'WriteLine' y 'Format' están en mal estado, autoboxing todos los tipos de valores (lo cual es malo). "C # efectivo" aconseja realizar llamadas '.ToString' explícitamente, lo que en mi humilde opinión es falso (ver la publicación de Jeff )
  6. Por el momento, el compilador no comprueba las sugerencias de tipo de formato, lo que genera errores de tiempo de ejecución. Sin embargo, esto podría modificarse en futuras versiones.
Konrad Rudolph
fuente
4

Elijo en función de la legibilidad. Prefiero la opción de formato cuando hay texto alrededor de las variables. En este ejemplo:

Console.WriteLine("User {0} accessed {1} on {2}.", 
                   user.Name, fileName, timestamp);

entiendes el significado incluso sin nombres de variables, mientras que el concat está lleno de comillas y signos + y confunde mis ojos:

Console.WriteLine("User " + user.Name + " accessed " + fileName + 
                  " on " + timestamp + ".");

(Tomé prestado el ejemplo de Mike porque me gusta)

Si la cadena de formato no significa mucho sin nombres de variables, tengo que usar concat:

   Console.WriteLine("{0} {1}", p.FirstName, p.LastName);

La opción de formato me hace leer los nombres de las variables y asignarlos a los números correspondientes. La opción concat no requiere eso. Todavía estoy confundido por las comillas y los signos +, pero la alternativa es peor. ¿Rubí?

   Console.WriteLine(p.FirstName + " " + p.LastName);

En cuanto al rendimiento, espero que la opción de formato sea más lenta que la concat, ya que el formato requiere que se analice la cadena . No recuerdo haber tenido que optimizar este tipo de instrucción, pero si lo hiciera, vería stringmétodos como Concat()y Join().

La otra ventaja del formato es que la cadena de formato se puede colocar en un archivo de configuración. Muy útil con mensajes de error y texto de interfaz de usuario.

DonkeyMaster
fuente
4

Usaría el String.Format, pero también tendría el formato de cadena en los archivos de recursos para que pueda ser localizado para otros idiomas. Usar una cadena simple concat no te permite hacer eso. Obviamente, si nunca necesita localizar esa cadena, esta no es una razón para pensar. Realmente depende de para qué es la cadena.

Si se va a mostrar al usuario, usaría String.Format para poder localizarlo si lo necesito, y FxCop lo corregirá por mí, por si acaso :)

Si contiene números o cualquier otra cosa que no sea de cadena (por ejemplo, fechas), usaría String.Format porque me da más control sobre el formato .

Si es para construir una consulta como SQL, usaría Linq .

Si para concatenar cadenas dentro de un bucle, usaría StringBuilder para evitar problemas de rendimiento.

Si es para algún resultado que el usuario no verá, y no va a afectar el rendimiento, usaría String.Format porque tengo la costumbre de usarlo de todos modos y simplemente estoy acostumbrado :)

Wilka
fuente
3

Si está tratando con algo que necesita ser fácil de leer (y esto es la mayoría del código), me quedaría con la versión de sobrecarga del operador A MENOS QUE:

  • El código debe ejecutarse millones de veces
  • Estás haciendo toneladas de concats (más de 4 es una tonelada)
  • El código está dirigido hacia el Marco Compacto

En al menos dos de estas circunstancias, usaría StringBuilder en su lugar.

pedestal
fuente
3

Si tiene la intención de localizar el resultado, String.Format es esencial porque los diferentes lenguajes naturales podrían no tener los datos en el mismo orden.

Christian Hayter
fuente
2

Creo que esto depende en gran medida de lo complejo que sea el resultado. Tiendo a elegir el escenario que funcione mejor en ese momento.

Elija la herramienta adecuada según el trabajo: D ¡El que se vea más limpio!

mercutio
fuente
2

Prefiero el segundo también, pero no tengo argumentos racionales en este momento para apoyar esa posición.

Arrojar
fuente
2

¡Buena esa!

Recien agregado

        s.Start();
        for (var i = 0; i < n; i++)
            result = string.Concat(p.FirstName, " ", p.LastName);
        s.Stop();
        ceElapsedMilliseconds = s.ElapsedMilliseconds;
        ceElapsedTicks = s.ElapsedTicks;
        s.Reset();

Y es aún más rápido (supongo que string. Concat se llama en ambos ejemplos, pero el primero requiere algún tipo de traducción).

1000000 x result = string.Format("{0} {1}", p.FirstName, p.LastName); took: 249ms - 3571621 ticks
1000000 x result = (p.FirstName + " " + p.LastName); took: 65ms - 944948 ticks
1000000 x result = string.Concat(p.FirstName, " ", p.LastName); took: 54ms - 780524 ticks
Philippe
fuente
2
Lleva exactamente la misma cantidad de tiempo desde que el compilador traduce las concatenaciones de cadenas basadas en operador a las llamadas string.Concat(...). Se realiza durante la compilación, por lo que no tiene impacto en el rendimiento en tiempo de ejecución. Si ejecuta sus pruebas varias veces o las ejecuta en muestras de prueba más grandes, verá que son idénticas.
Allon Guralnek
2

Como no creo que las respuestas aquí cubran todo, me gustaría hacer una pequeña adición aquí.

Console.WriteLine(string format, params object[] pars)llamadas string.Format. El '+' implica concatenación de cadenas. No creo que esto siempre tenga que ver con el estilo; Tiendo a mezclar los dos estilos dependiendo del contexto en el que me encuentre.

Respuesta corta

La decisión que enfrenta tiene que ver con la asignación de cadenas. Trataré de hacerlo simple.

Di que tienes

string s = a + "foo" + b;

Si ejecuta esto, se evaluará de la siguiente manera:

string tmp1 = a;
string tmp2 = "foo" 
string tmp3 = concat(tmp1, tmp2);
string tmp4 = b;
string s = concat(tmp3, tmp4);

tmpaquí no es realmente una variable local, pero es temporal para el JIT (se inserta en la pila IL). Si empuja una cadena en la pila (como ldstren IL para literales), coloca una referencia a un puntero de cadena en la pila.

El momento en que llama a concatesta referencia se convierte en un problema, porque no hay ninguna referencia de cadena disponible que contenga ambas cadenas. Esto significa que .NET necesita asignar un nuevo bloque de memoria y luego llenarlo con las dos cadenas. La razón por la que esto es un problema es porque la asignación es relativamente costosa.

Lo que cambia la pregunta a: ¿Cómo puede reducir el número de concatoperaciones?

Entonces, la respuesta aproximada es: string.Formatpara> 1 concats, '+' funcionará bien para 1 concat. Y si no te importa hacer optimizaciones de micro rendimiento, string.Formatfuncionará bien en el caso general.

Una nota sobre cultura

Y luego hay algo llamado cultura ...

string.Formatle permite usar CultureInfoen su formato. Un operador simple '+' usa la cultura actual.

Esto es especialmente un comentario importante si está escribiendo formatos de archivo y f.ex. doublevalores que 'agrega' a una cadena. En diferentes máquinas, puede terminar con diferentes cadenas si no lo usa string.Formatcon un explícito CultureInfo.

F.ex. considere lo que sucede si cambia un '.' para un ',' mientras escribe su archivo de valores separados por comas ... en holandés el separador decimal es una coma, por lo que su usuario podría recibir una sorpresa 'divertida'.

Respuesta más detallada

Si no conoce el tamaño exacto de la cadena de antemano, es mejor usar una política como esta para sobreasignar los búferes que usa. El espacio flojo se llena primero, después de lo cual se copian los datos.

Crecer significa asignar un nuevo bloque de memoria y copiar los datos antiguos al nuevo búfer. El antiguo bloque de memoria se puede liberar. En este punto se obtiene el resultado final: el crecimiento es una operación costosa.

La forma más práctica de hacerlo es utilizar una política de sobreasignación. La política más común es sobreasignar buffers en potencias de 2. Por supuesto, debe hacerlo un poco más inteligente que eso (ya que no tiene sentido crecer de 1,2,4,8 si ya sabe que necesita 128 caracteres) ) pero te haces una idea. La política garantiza que no necesita muchas de las costosas operaciones que describí anteriormente.

StringBuilderes una clase que básicamente sobreasigna el búfer subyacente en potencias de dos. string.Formatusos StringBuilderdebajo del capó.

Esto hace que su decisión sea una compensación básica entre sobreasignar y agregar (-múltiple) (sin cultura) o simplemente asignar-y-agregar.

atlaste
fuente
1

Personalmente, el segundo, ya que todo lo que está usando está en el orden directo en el que saldrá. Mientras que con el primero, debe hacer coincidir los {0} y {1} con la var adecuada, lo que es fácil de estropear.

Al menos no es tan malo como el sprintf de C ++, donde si se equivoca el tipo de variable, todo explotará.

Además, dado que el segundo es todo en línea y no tiene que buscar y reemplazar todas las {0} cosas, este último debería ser más rápido ... aunque no estoy seguro.

Adam Haile
fuente
1

De hecho, me gusta el primero porque cuando hay muchas variables entremezcladas con el texto, me parece más fácil de leer. Además, es más fácil tratar con comillas cuando se usa la cadena. Formato (), uh, formato. Aquí hay un análisis decente de la concatenación de cadenas.

adparadox
fuente
1

Siempre he seguido la ruta string.Format (). Poder almacenar formatos en variables como el ejemplo de Nathan es una gran ventaja. En algunos casos, puedo agregar una variable, pero una vez que se concatena más de 1 variable, refactorizo ​​para usar el formato.

Scott Muc
fuente
1

Ah, y solo para completar, los siguientes son algunos tics más rápidos que la concatenación normal:

Console.WriteLine(String.Concat(p.FirstName," ",p.LastName));
samjudson
fuente
1

El primero (formato) me parece mejor. Es más legible y no está creando objetos de cadena temporales adicionales.

Rismo
fuente
1

Tenía curiosidad sobre dónde estaba StringBuilder con estas pruebas. Resultados a continuación ...

class Program {
   static void Main(string[] args) {

      var p = new { FirstName = "Bill", LastName = "Gates" };

      var tests = new[] {
         new { Name = "Concat", Action = new Action(delegate() { string x = p.FirstName + " " + p.LastName; }) },
         new { Name = "Format", Action = new Action(delegate() { string x = string.Format("{0} {1}", p.FirstName, p.LastName); }) },
         new { Name = "StringBuilder", Action = new Action(delegate() {
            StringBuilder sb = new StringBuilder();
            sb.Append(p.FirstName);
            sb.Append(" ");
            sb.Append(p.LastName);
            string x = sb.ToString();
         }) }
      };

      var Watch = new Stopwatch();
      foreach (var t in tests) {
         for (int i = 0; i < 5; i++) {
            Watch.Reset();
            long Elapsed = ElapsedTicks(t.Action, Watch, 10000);
            Console.WriteLine(string.Format("{0}: {1} ticks", t.Name, Elapsed.ToString()));
         }
      }
   }

   public static long ElapsedTicks(Action ActionDelg, Stopwatch Watch, int Iterations) {
      Watch.Start();
      for (int i = 0; i < Iterations; i++) {
         ActionDelg();
      }
      Watch.Stop();
      return Watch.ElapsedTicks / Iterations;
   }
}

Resultados:

Concat: 406 garrapatas
Concat: 356 garrapatas
Concat: 411 garrapatas
Concat: 299 garrapatas
Concat: 266 garrapatas
Formato: 5269 garrapatas
Formato: 954 garrapatas
Formato: 1004 garrapatas
Formato: 984 garrapatas
Formato: 974 garrapatas
StringBuilder: 629 ticks
StringBuilder: 484 ticks
StringBuilder: 482 ticks
StringBuilder: 508 ticks
StringBuilder: 504 ticks
revs spoulson
fuente
1

Según el material de preparación de MCSD, Microsoft sugiere usar el operador + cuando se trata de un número muy pequeño de concatenaciones (probablemente de 2 a 4). Todavía no estoy seguro de por qué, pero es algo a considerar.

Babak Naffas
fuente