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


120

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

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é?



Respuestas:


115

Antes de C # 6

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

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

Sospecho que otras respuestas pueden hablar sobre el impacto en el 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 excelentes 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 se convierte en:

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

que es probablemente la mejor opción, en mi opinión.



Sé que sé. Se hizo en broma (he leído el enlace por cierto antes, que fue una buena lectura)
nawfal

Jon. Siempre he sido fanático del Sr. Richter y he seguido la orientación sobre el boxeo, etc. religiosamente. Sin embargo, después de leer su (antiguo) artículo, ahora soy un converso. Gracias
stevethethread

3
@ mbomb007: Ahora está en codeblog.jonskeet.uk/2008/10/08/…
Jon Skeet

4
Ahora que C # 6 está disponible, puede usar la nueva sintaxis de interpolación de cadenas para lo que creo que es aún más fácil de xlsSheet.Write($"C{rowIndex}", null, title);
leer

158

Mi preferencia inicial (proveniente de un fondo de C ++) fue String.Format. Dejé esto 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 ocurre a varios otros desarrolladores) eliminar un parámetro o estropear el orden de los parámetros por error. El compilador no comparará los parámetros con la cadena de formato y terminará con un error en tiempo de ejecución (es decir, si tiene la suerte de no tenerlo en un método poco conocido, 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.Formatpero 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 le gustaría un error en lugar de ignorar silenciosamente un Nombre nulo. Sin embargo, incluso en esta situación, personalmente prefiero verificar si hay nulos y lanzar 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 :).

La idea es que el compilador .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 de la matriz se convierten en su cadena mediante ToString (). Luego se calcula 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 de código inseguro.

¿Razones String.Concates mucho más rápido? Bueno, todos podemos echar un vistazo a lo que String.Formatestá haciendo: se sorprenderá de la cantidad de código necesario para procesar la cadena de formato. Además de esto (he visto comentarios sobre el consumo de memoria), String.Formatutiliza un StringBuilder internamente. Así es cómo:

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

Entonces, por cada argumento pasado, reserva 8 caracteres. Si el argumento es un valor de un dígito, entonces muy mal, tenemos algo de espacio desperdiciado. Si el argumento es un objeto personalizado que devuelve texto extenso ToString(), es posible que incluso se necesite una reasignación (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 de boxeo / unboxing está presente en ambos métodos.

La única razón por la que optaría por String.Format es cuando se trata de la localización. Poner cadenas de formato en los recursos le permite admitir diferentes idiomas sin alterar el código (piense en situaciones en las que los valores formateados cambian de orden según el idioma, es decir, "después de {0} horas y {1} minutos" puede verse bastante diferente en japonés: ).


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

  • la mejor manera (en términos de rendimiento frente a capacidad de mantenimiento / legibilidad) para mí es usar la concatenación de cadenas, sin ninguna ToString()llamada
  • Si buscas una actuación, haz las ToString()llamadas tú mismo para evitar el boxeo (estoy algo sesgado hacia la legibilidad), igual que la primera opción en tu pregunta.
  • si está mostrando cadenas localizadas al usuario (no es el caso aquí), String.Format()tiene una ventaja.

5
1) string.Formates "seguro" cuando se usa ReSharper; es decir, es tan seguro como cualquier otro código que se pueda utilizar [incorrectamente]. 2) string.Format no permitirá un "seguro" null: string.Format("A{0}B", (string)null)los resultados en "AB". 3) Rara vez me preocupo por este nivel de rendimiento (y con ese fin, es un día raro cuando me retiro StringBuilder) ...

De acuerdo en 2), editaré la publicación. No se puede verificar si esto era seguro en 1.1, pero el último marco es seguro para nulos.
Dan C.

¿Se sigue utilizando string.Concat si uno de los operandos es una llamada a método con un valor de retorno, en lugar de ser un parámetro o una variable?
Richard Collette

2
@RichardCollette Sí, String.Concat se utiliza incluso si concatenas valores de retorno de llamadas a métodos, por ejemplo, string s = "This " + MyMethod(arg) + " is a test";se compila en una String.Concat()llamada en modo Release.
Dan C.

Respuesta fantástica; muy bien escrito y explicado.
Frank V

6

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

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

string.Format usa un StringBuilder debajo del capó (verifique con el reflector ) por lo que no tendrá 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 del micro rendimiento 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, escriba primero para mejorar la legibilidad y luego use un generador de perfiles de rendimiento para identificar sus puntos de acceso si realmente cree que tiene problemas de rendimiento.



5

Para un caso simple en el que se trata de una simple concatenación única, creo que no vale la pena la complejidad de 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, con el formato de análisis de cadenas y todo). Como Jon Skeet, prefiero no llamar explícitamente .ToString(), ya que la string.Concat(string, object)sobrecarga lo hará implícitamente , y creo que el código tiene un aspecto más limpio y es más fácil de leer sin él.

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

Si hay muchos parámetros en la cadena de formato (nuevamente, "muchos" es subjetivo), generalmente prefiero incluir índices comentados en los argumentos de reemplazo, para que no pierda 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 string.Formataquí. Y sí, lógica y léxicamente, eso es lo que he hecho. Pero todas las concatenaciones serán optimizadas por el compilador 1 , ya que todas son cadenas literales. Entonces, en 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 todavía esté atascado con C # 5 o anterior. Ahora tenemos cadenas interpoladas , que en términos de legibilidad, son muy superiores a string.Format, en casi todos los casos. En estos días, a menos que solo 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"
);

De esta manera, pierde la concatenación en tiempo de compilación. El string.Formatcompilador convierte cada cadena interpolada en una llamada a , 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 mejorar la legibilidad. La mayoría de las veces, es un sacrificio que vale la pena, porque la penalización en tiempo de ejecución es insignificante. En el código de rendimiento crítico, sin embargo, es posible que deba perfilar diferentes soluciones.


1 Puede ver esto en la especificación de 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";

1
Para tu información, puedes usar @ "... cadena de varias líneas" en lugar de todas las concatenaciones.
Aaron Palmer

Sí, pero luego tienes que justificar a la izquierda tu cadena. Las cadenas @ incluyen todas las líneas nuevas y los caracteres de tabulación entre las comillas.
P Daddy

Sé que esto es antiguo, pero este es un caso en el que diría que coloque la cadena de formato en un archivo resx.
Andy

2
Vaya, todo el mundo está centrado en la cadena literal, en lugar de en el meollo del asunto.
P Daddy

jejeje - Estaba notando la concatenación de cadenas dentro de tuString.Format()
Kristopher

3

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


3

He echado un vistazo a String.Format (usando Reflector) y en realidad crea un StringBuilder y luego llama AppendFormat en él. Por lo que 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, el número de "muchos" es incierto. Usaría + (en realidad, porque soy un programador de VB principalmente) para algo tan simple como tu ejemplo. A medida que se vuelve más complejo, uso String.Format. Si hay MUCHAS variables, optaría por un StringBuilder y Append, por ejemplo, tenemos código que crea código, allí uso una línea de código real para generar una línea de código generado.

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

"C" + rowIndex.ToString();

"C" ya es una cadena.
rowIndex.ToString () crea otra cadena. (@manohard - no ocurrirá ningún boxing de rowIndex)
Luego obtenemos la cadena final.
Si tomamos el ejemplo de

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

entonces tenemos "C {0}" como una cadena
rowIndex se encajona 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 caja en una cadena.
Luego, convierta el constructor de cadenas en una nueva cadena.
Sé que StringBuilders intenta evitar que se realicen copias de memoria sin sentido, pero String.Format aún termina con una sobrecarga adicional en comparación con la concatenación simple.

Si ahora tomamos un ejemplo con algunas cadenas más

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

tenemos 6 cadenas para empezar, que serán las mismas 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 mediante el uso de String, Format (o un 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 la que es potencialmente lenta.


4
Nitpick. En la "a" + ... + "b" + ... + "c" + ..., en realidad no tendrá 4 cadenas intermedias. El compilador generará una llamada al método estático String.Concat (params string [] valores), y todos se concatenarán a la vez. Sin embargo, todavía prefiero la cadena .Formato en aras de la legibilidad.
P Daddy

2

Me gusta String.Format porque puede hacer que su 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 y le permite formatear sus parámetros, sin embargo, para usos cortos como el suyo, no veo ningún problema en la concatenación.

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


2

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

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


1

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 fácil.

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

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

con string.Format todo lo 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 mucho tiempo.


1

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 cuando se usa '+' tomó 1.6 segundos.


7
El compilador reconoce "1" + "2" + "3" + "4" + "5" + "6" + "7" + "8" + "9" + "10"como una cadena literal, por lo que la línea se convierte efectivamente en "12345678910" + icuál es más rápida que la anteriorstring.Format(...)
wertzui

0

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)


0

Hice un poco de perfilado de varios métodos de cadena, incluidos 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 considero que string.Format es más fácil de seguir en el código. (Pero esa es una razón subjetiva) Sin embargo, StringBuilder es probablemente más eficiente con respecto a la utilización de la memoria.



-1

La concatenación de cadenas requiere más memoria en comparación con String.Format. Entonces, la mejor manera de concatenar cadenas es utilizando 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 en String y luego CLR crea memoria para la nueva cadena con ambos valores incluidos.

Donde como string.Format espera el parámetro de objeto y toma rowIndex como un objeto y lo convierte en una cadena internamente, por supuesto, habrá Boxing pero es intrínseco y no ocupará tanta memoria como en el primer caso.

Supongo que para cuerdas cortas no importará mucho ...

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.