¿Cuándo es mejor usar String.Format contra la concatenación de cadenas?

Tengo un pequeño fragmento de código que analiza un valor de índice para determinar la entrada de una celda en Excel. Me hace pensar …

Cuál es la diferencia entre

xlsSheet.Write("C" + rowIndex.ToString(), null, title); 

y

 xlsSheet.Write(string.Format("C{0}", rowIndex), null, title); 

¿Es uno mejor que el otro? ¿Y por qué?

Antes de C # 6

Para ser sincero, creo que la primera versión es más simple, aunque lo simplificaría para:

 xlsSheet.Write("C" + rowIndex, null, title); 

Sospecho que otras respuestas pueden hablar sobre el golpe de rendimiento, pero para ser honesto, será mínimo si está presente , y esta versión de concatenación no necesita analizar la cadena de formato.

Las cadenas de formato son geniales para fines de localización, etc., pero en un caso como este, la concatenación es más simple y funciona igual de bien.

Con C # 6

La interpolación de cadenas facilita la lectura de muchas cosas en C # 6. En este caso, su segundo código pasa a ser:

 xlsSheet.Write($"C{rowIndex}", null, title); 

que es probablemente la mejor opción, IMO.

Mi preferencia inicial (proveniente de un fondo C ++) fue para String.Format. Lo descarté más tarde debido a las siguientes razones:

  • La concatenación de cadenas es posiblemente “más segura”. Me pasó a mí (y he visto que le sucede a muchos otros desarrolladores) eliminar un parámetro, o desordenar el orden de los parámetros por error. El comstackdor no verificará los parámetros con la cadena de formato y terminará con un error de tiempo de ejecución (es decir, si tiene la suerte de no tenerlo en un método oscuro, como registrar un error). Con la concatenación, eliminar un parámetro es menos propenso a errores. Se podría argumentar que la posibilidad de error es muy pequeña, pero puede suceder.

– La concatenación de cadenas permite valores nulos, String.Format no. Escribir ” s1 + null + s2 ” no se rompe, solo trata el valor nulo como String.Empty. Bueno, esto puede depender de su escenario específico: hay casos en los que desea un error en lugar de ignorar silenciosamente un Nombre nulo. Sin embargo, incluso en esta situación, personalmente prefiero comprobar nulos y arrojar errores específicos en lugar de la ArgumentNullException estándar que obtengo de String.Format.

  • La concatenación de cadenas funciona mejor. Algunas de las publicaciones anteriores ya mencionan esto (sin explicar realmente por qué, lo que me determinó a escribir esta publicación :).

Idea es que el comstackdor .NET es lo suficientemente inteligente como para convertir este fragmento de código:

 public static string Test(string s1, int i2, int i3, int i4, string s5, string s6, float f7, float f8) { return s1 + " " + i2 + i3 + i4 + " ddd " + s5 + s6 + f7 + f8; } 

a esto:

 public static string Test(string s1, int i2, int i3, int i4, string s5, string s6, float f7, float f8) { return string.Concat(new object[] { s1, " ", i2, i3, i4, " ddd ", s5, s6, f7, f8 }); } 

Lo que sucede bajo el capó de String.Concat es fácil de adivinar (use Reflector). Los objetos en la matriz se convierten a su cadena a través de ToString (). Luego, se computa la longitud total y solo se asigna una cadena (con la longitud total). Finalmente, cada cadena se copia en la cadena resultante a través de wstrcpy en algún fragmento inseguro de código.

Razones String.Concat es mucho más rápido? Bueno, todos podemos ver lo que está haciendo String.Format : te sorprenderá la cantidad de código requerido para procesar la cadena de formato. Además de esto (he visto comentarios sobre el consumo de memoria), String.Format utiliza un StringBuilder internamente. Así es cómo:

StringBuilder builder = new StringBuilder(format.Length + (args.Length * 8));

Entonces, por cada argumento pasado, se reservan 8 caracteres. Si el argumento tiene un valor de un dígito, es una pena que tengamos un espacio desperdiciado. Si el argumento es un objeto personalizado que devuelve texto largo en ToString() , puede que haya incluso alguna reasignación necesaria (en el peor de los casos, por supuesto).

Comparado con esto, la concatenación solo desperdicia el espacio de la matriz de objetos (no demasiado, teniendo en cuenta que es una matriz de referencias). No hay análisis para especificadores de formato ni StringBuilder intermediario. La sobrecarga del boxeo / unboxing está presente en ambos métodos.

La única razón por la que elegiría String.Format es cuando se trata de localización. Poner cadenas de formato en recursos le permite soportar diferentes idiomas sin interferir con el código (piense en escenarios donde los valores formateados cambian de orden dependiendo del idioma, es decir, “después de {0} horas y {1} minutos” pueden verse bastante diferentes en japonés: )


Para resumir mi primera (y bastante larga) publicación:

  • La mejor manera (en términos de rendimiento frente a facilidad de mantenimiento / legibilidad) para mí es usar concatenación de cadenas, sin ninguna ToString()
  • si buscas el rendimiento, haz que ToString() te llame para evitar el boxeo (estoy algo predispuesto a la legibilidad), igual que la primera opción en tu pregunta
  • si muestra cadenas localizadas para el usuario (no es el caso aquí), String.Format() tiene una ventaja.

Creo que la primera opción es más legible y esa debería ser tu principal preocupación.

 xlsSheet.Write("C" + rowIndex.ToString(), null, title); 

string.Format utiliza un StringBuilder debajo del capó (consulte con el reflector ) para que no tenga ningún beneficio de rendimiento a menos que esté haciendo una cantidad significativa de concatenación. Será más lento para su escenario, pero la realidad es que esta decisión de optimización de microejecución es inapropiada la mayor parte del tiempo y realmente debería centrarse en la legibilidad de su código a menos que esté en un bucle.

De cualquier manera, primero escriba para la legibilidad y luego use un perfilador de desempeño para identificar sus puntos de acceso si realmente cree que tiene problemas de rendimiento.

Para un caso simple donde se trata de una simple concatenación simple, siento que no vale la complejidad de la string.Format . string.Format (y no lo he probado, pero sospecho que para un caso simple como este, string.Format podría ser un poco más lento, qué con el formato cadena de análisis y todo). Al igual que Jon Skeet, prefiero no llamar explícitamente a .ToString() , ya que se hará implícitamente con la string.Concat(string, object) , y creo que el código es más limpio y fácil de leer sin él.

Pero durante más de unas pocas concatenaciones (cuántas son subjetivas), definitivamente prefiero la string.Format . string.Format . En cierto punto, creo que tanto la legibilidad como el rendimiento sufren innecesariamente con la concatenación.

Si hay muchos parámetros para la cadena de formato (de nuevo, “muchos” es subjetivo), generalmente prefiero incluir índices comentados en los argumentos de reemplazo, para no perder la pista de qué valor va a qué parámetro. Un ejemplo artificial:

 Console.WriteLine( "Dear {0} {1},\n\n" + "Our records indicate that your {2}, \"{3}\", is due for {4} {5} shots.\n" + "Please call our office at 1-900-382-5633 to make an appointment.\n\n" + "Thank you,\n" + "Eastern Veterinary", /*0*/client.Title, /*1*/client.LastName, /*2*/client.Pet.Animal, /*3*/client.Pet.Name, /*4*/client.Pet.Gender == Gender.Male ? "his" : "her", /*5*/client.Pet.Schedule[0] ); 

Actualizar

Se me ocurre que el ejemplo que he dado es un poco confuso, porque parece que he usado tanto la concatenación como la string.Format aquí. Y sí, lógica y léxicamente, eso es lo que hice. Pero todas las concatenaciones serán optimizadas por el comstackdor 1 , ya que todas son cadenas literales. Entonces en el tiempo de ejecución, habrá una sola cadena. Así que supongo que debería decir que prefiero evitar muchas concatenaciones en tiempo de ejecución .

Por supuesto, la mayor parte de este tema está desactualizado ahora, a menos que sigas atascado con C # 5 o anterior. Ahora tenemos cadenas interpoladas , que para la legibilidad son muy superiores a la string.Format . string.Format , en casi todos los casos. En estos días, a menos que esté concatenando un valor directamente al principio o al final de un literal de cadena, casi siempre uso la interpolación de cadenas. Hoy, escribiría mi ejemplo anterior así:

 Console.WriteLine( $"Dear {client.Title} {client.LastName},\n\n" + $"Our records indicate that your {client.Pet.Animal}, \"{client.Pet.Name}\", " + $"is due for {(client.Pet.Gender == Gender.Male ? "his" : "her")} " + $"{client.Pet.Schedule[0]} shots.\n" + "Please call our office at 1-900-382-5633 to make an appointment.\n\n" + "Thank you,\n" + "Eastern Veterinary" ); 

Perderás la concatenación en tiempo de comstackción de esta manera. Cada cadena interpolada se convierte en una llamada a string.Format por el comstackdor, y sus resultados se concatenan en tiempo de ejecución. Eso significa que esto es un sacrificio del rendimiento en tiempo de ejecución para la legibilidad. La mayoría de las veces, es un sacrificio que vale la pena, porque la pena de tiempo de ejecución es insignificante. Sin embargo, en el código de rendimiento crítico, es posible que necesite perfilar soluciones diferentes.


1 Puedes ver esto en la especificación C # :

… las siguientes construcciones están permitidas en expresiones constantes:

  • El operador binario + predefinido …

También puedes verificarlo con un pequeño código:

 const string s = "This compiles successfully, " + "and you can see that it will " + "all be one string (named `s`) " + "at run time"; 

Si su cadena era más compleja con muchas variables que se concatenan, entonces elegiría la cadena. Formato (). Pero para el tamaño de la cadena y el número de variables que se concatenan en su caso, elegiría su primera versión, es más espartana .

He echado un vistazo a String.Format (usando Reflector) y realmente crea un StringBuilder y luego llama a AppendFormat sobre él. Por lo tanto, es más rápido que concat para múltiples agitaciones. Lo más rápido (creo) sería crear un StringBuilder y hacer las llamadas a Append manualmente. Por supuesto, la cantidad de “muchos” depende de las suposiciones. Usaría + (en realidad y porque soy un progtwigdor de VB en su mayoría) para algo tan simple como tu ejemplo. A medida que se vuelve más complejo, uso String.Format. Si hay MUCHAS variables, me gustaría ir a un StringBuilder y anexar, por ejemplo, tenemos un código que construye código, allí utilizo una línea de código real para generar una línea de código generado.

Parece haber cierta especulación sobre cuántas secuencias se crean para cada una de estas operaciones, así que vamos a tomar algunos ejemplos simples.

 "C" + rowIndex.ToString(); 

“C” ya es una cadena.
rowIndex.ToString () crea otra cadena. (@manohard – no se realizará el boxeo de rowIndex)
Entonces obtenemos la cadena final.
Si tomamos el ejemplo de

 String.Format("C(0)",rowIndex); 

entonces tenemos “C {0}” como una cadena
rowIndex se pone en caja para pasar a la función
Se crea un nuevo generador de cadenas
Se llama a AppendFormat en el generador de cadenas: no conozco los detalles de cómo funciona AppendFormat, pero supongamos que es ultra eficiente, todavía tendrá que convertir el rowIndex en una cadena.
Luego convierta el generador de cadenas en una nueva cadena.
Sé que StringBuilders intenta evitar que se lleven a cabo copias de memoria sin sentido, pero el String.Format aún termina con una sobrecarga adicional en comparación con la concatenación simple.

Si tomamos un ejemplo con algunas cuerdas más

 "a" + rowIndex.ToString() + "b" + colIndex.ToString() + "c" + zIndex.ToString(); 

tenemos 6 cadenas para empezar, que será la misma para todos los casos.
Usando la concatenación también tenemos 4 cadenas intermedias más el resultado final. Son esos resultados intermedios los que se eliminan usando String, Format (o StringBuilder).
Recuerde que para crear cada cadena intermedia, la anterior debe copiarse en una nueva ubicación de memoria, no es solo la asignación de memoria que es potencialmente lenta.

Ese ejemplo es probablemente demasiado trivial para notar una diferencia. De hecho, creo que en la mayoría de los casos el comstackdor puede optimizar cualquier diferencia.

Sin embargo, si tuviera que adivinar, le daría a string.Format() una ventaja para escenarios más complicados. Pero eso es más una corazonada que es probable que haga un mejor trabajo utilizando un búfer en lugar de producir múltiples cadenas inmutables, y no en base a datos reales.

Me gusta String.Format porque puede hacer que el texto formateado sea mucho más fácil de seguir y leer que la concatenación en línea, también es mucho más flexible, lo que le permite formatear los parámetros; sin embargo, para usos breves como el suyo no veo problema alguno para concatenar.

Para concatenaciones dentro de bucles o en grandes cadenas, siempre debe intentar utilizar la clase StringBuilder.

Estoy de acuerdo con muchos puntos anteriores, otro punto que creo que debería mencionarse es la capacidad de mantenimiento del código. string.Format permite un cambio de código más sencillo.

es decir, tengo un mensaje "The user is not authorized for location " + location o "The User is not authorized for location {0}"

si alguna vez quise cambiar el mensaje para decir: la location + " does not allow this User Access" o "{0} does not allow this User Access"

con cadena. Lo único que tengo que hacer es cambiar la cadena. para la concatenación tengo que modificar ese mensaje

si se usa en varios lugares puede ahorrar tiempo.

Tenía la impresión de que string.format era más rápido, parece ser 3 veces más lento en esta prueba

 string concat = ""; System.Diagnostics.Stopwatch sw1 = new System.Diagnostics.Stopwatch (); sw1.Start(); for (int i = 0; i < 10000000; i++) { concat = string.Format("{0}{1}{2}{3}{4}{5}{6}{7}{8}{9}{10}","1", "2" , "3" , "4" , "5" , "6" , "7" , "8" , "9" , "10" , i); } sw1.Stop(); Response.Write("format: " + sw1.ElapsedMilliseconds.ToString()); System.Diagnostics.Stopwatch sw2 = new System.Diagnostics.Stopwatch(); sw2.Start(); for (int i = 0; i < 10000000; i++) { concat = "1" + "2" + "3" + "4" + "5" + "6" + "7" + "8" + "9" + "10" + i; } sw2.Stop(); 

string.format tardó 4.6 segundos y al usar '+' tomó 1.6 segundos.

string.Format es probablemente una mejor opción cuando la plantilla de formato (“C {0}”) se almacena en un archivo de configuración (como Web.config / App.config)

Hice un poco de perfilado de varios métodos de cadena incluyendo string.Format, StringBuilder y concatenación de cadenas. La concatenación de cadenas casi siempre superó a los otros métodos de construcción de cadenas. Entonces, si el rendimiento es clave, entonces es mejor. Sin embargo, si el rendimiento no es crítico, personalmente encuentro string.Format es más fácil de seguir en el código. (Pero esa es una razón subjetiva) StringBuilder sin embargo, es probablemente el más eficiente con respecto a la utilización de la memoria.

Prefiero String.Format con respecto al rendimiento

La concatenación de cadenas requiere más memoria en comparación con String.Format. Entonces, la mejor forma de concatenar cadenas es usar String.Format o System.Text.StringBuilder Object.

Tomemos el primer caso: “C” + rowIndex.ToString () Supongamos que rowIndex es un tipo de valor, por lo que el método ToString () tiene que Box para convertir el valor a String y luego CLR crea la memoria para la nueva cadena con ambos valores incluidos.

Mientras que string.Format espera el parámetro de objeto y toma rowIndex como un objeto y lo convierte en string internamente, habrá boxeo pero es intrínseco y además no ocupará tanta memoria como en el primer caso.

Para cuerdas cortas, no importará mucho, supongo …