¿Cuántos objetos String se crearán al usar un signo más?


115

¿Cuántos objetos String se crearán cuando se use un signo más en el siguiente código?

String result = "1" + "2" + "3" + "4";

Si fuera como se muestra a continuación, habría dicho tres objetos String: "1", "2", "12".

String result = "1" + "2";

También sé que los objetos String se almacenan en caché en String Intern Pool / Table para mejorar el rendimiento, pero esa no es la cuestión.


Las cadenas solo se internan si llama explícitamente a String.Intern.
Joe White

7
@JoeWhite: ¿lo son?
Igor Korkhov

13
No exactamente. Todos los literales de cadena se internan automáticamente. Los resultados de las operaciones con cadenas no lo son.
Stefan Paul Noack

Además, en el ejemplo de OP, solo hay una constante de cadena y está interna. Actualizaré mi respuesta para ilustrar.
Chris Shain

+1. Para un ejemplo de la vida real de la necesidad de codificar una cadena de catenados en ese estilo, la sección de Ejemplos de msdn.microsoft.com/en-us/library/… tiene uno que no sería posible si el compilador no pudiera optimizarlo a una sola constante, debido a las restricciones sobre los valores asignados a los parámetros de atributo.
ClickRick

Respuestas:


161

Sorprendentemente, depende.

Si haces esto en un método:

void Foo() {
    String one = "1";
    String two = "2";
    String result = one + two + "34";
    Console.Out.WriteLine(result);
}

entonces el compilador parece emitir el código usando String.Concatcomo respondió @Joachim (+1 a él por cierto).

Si los define como constantes , por ejemplo:

const String one = "1";
const String two = "2";
const String result = one + two + "34";

o como literales , como en la pregunta original:

String result = "1" + "2" + "3" + "4";

entonces el compilador optimizará esos +signos. Es equivalente a:

const String result = "1234";

Además, el compilador eliminará expresiones constantes extrañas y solo las emitirá si se usan o se exponen. Por ejemplo, este programa:

const String one = "1";
const String two = "1";
const String result = one + two + "34";

public static void main(string[] args) {
    Console.Out.WriteLine(result);
}

Solo genera una cadena, la constante result(igual a "1234"). oney twono aparecen en el IL resultante.

Tenga en cuenta que puede haber más optimizaciones en tiempo de ejecución. Solo me guío por lo que IL se produce.

Finalmente, en lo que respecta al internado, se internan constantes y literales, pero el valor que se interpone es el valor constante resultante en el IL, no el literal. Esto significa que puede obtener incluso menos objetos de cadena de los que espera, ya que múltiples constantes o literales idénticamente definidos serán en realidad el mismo objeto. Esto se ilustra con lo siguiente:

public class Program
{
    private const String one = "1";
    private const String two = "2";
    private const String RESULT = one + two + "34";

    static String MakeIt()
    {
        return "1" + "2" + "3" + "4";
    }   

    static void Main(string[] args)
    {
        string result = "1" + "2" + "34";

        // Prints "True"
        Console.Out.WriteLine(Object.ReferenceEquals(result, MakeIt()));

        // Prints "True" also
        Console.Out.WriteLine(Object.ReferenceEquals(result, RESULT));
        Console.ReadKey();
    }
}

En el caso de que las cadenas se concatenan en un bucle (o de otro modo de forma dinámica), se termina con una cadena adicional por concatenación. Por ejemplo, lo siguiente crea 12 instancias de cadena: 2 constantes + 10 iteraciones, cada una de las cuales da como resultado una nueva instancia de cadena:

public class Program
{
    static void Main(string[] args)
    {
        string result = "";
        for (int i = 0; i < 10; i++)
            result += "a";
        Console.ReadKey();
    }
}

Pero (también sorprendentemente), el compilador combina múltiples concatenaciones consecutivas en una sola concatenación de múltiples cadenas. Por ejemplo, este programa también solo produce 12 instancias de cadena. Esto se debe a que " Incluso si utiliza varios operadores + en una declaración, el contenido de la cadena se copia solo una vez " .

public class Program
{
    static void Main(string[] args)
    {
        string result = "";
        for (int i = 0; i < 10; i++)
            result += "a" + result;
        Console.ReadKey();
    }
}

¿qué pasa con String result = "1" + "2" + tres + cuatro; donde dos y tres se declaran como cadena tres = "3"; Cadena cuatro = "4" ;?
The Light

Incluso eso da como resultado una cuerda. Lo pasé por LinqPad para comprobarlo.
Chris Shain

1
@Servy: el comentario parece haberse actualizado. Cuando cambia un comentario, no se marca como cambiado.
Security Hound

1
Un caso que sería bueno considerar para la integridad es la concatenación en un ciclo. Por ejemplo, cuántos objetos de cadena asigna el siguiente código:string s = ""; for (int i = 0; i < n; i++) s += "a";
Joren

1
Yo uso LINQPad ( linqpad.net ) o Reflector ( reflector.net ). El primero le muestra el IL de fragmentos arbitrarios de código, el último descompila ensamblados en IL y puede volver a generar C # equivalente a partir de ese IL. También hay una herramienta incorporada llamada ILDASM ( msdn.microsoft.com/en-us/library/f7dy01k1(v=vs.80).aspx ) Comprender IL es algo complicado; consulte codebetter.com/raymondlewallen/2005/ 02/07 /…
Chris Shain

85

La respuesta de Chris Shain es muy buena. Como persona que escribió el optimizador de concatenación de cadenas, solo agregaría dos puntos interesantes adicionales.

La primera es que el optimizador de concatenación esencialmente ignora tanto el paréntesis como la asociatividad izquierda cuando puede hacerlo de forma segura. Suponga que tiene un método M () que devuelve una cadena. Si usted dice:

string s = M() + "A" + "B";

entonces el compilador razona que el operador de suma es asociativo izquierdo, y por lo tanto esto es lo mismo que:

string s = ((M() + "A") + "B");

Pero esto:

string s = "C" + "D" + M();

es lo mismo que

string s = (("C" + "D") + M());

de modo que esa es la concatenación de la cadena constante "CD" con M().

De hecho, el optimizador de concatenación se da cuenta de que la concatenación de cadenas es asociativa y genera String.Concat(M(), "AB")para el primer ejemplo, aunque eso viola la asociatividad izquierda.

Incluso puedes hacer esto:

string s = (M() + "E") + ("F" + M()));

y todavía generaremos String.Concat(M(), "EF", M()).

El segundo punto interesante es que las cadenas nulas y vacías se optimizan. Entonces, si haces esto:

string s = (M() + "") + (null + M());

obtendrás String.Concat(M(), M())

Entonces surge una pregunta interesante: ¿qué pasa con esto?

string s = M() + null;

No podemos optimizar eso hasta

string s = M();

porque M()podría devolver nulo, pero String.Concat(M(), null)devolvería una cadena vacía si M()devuelve nulo. Entonces, lo que hacemos es reducir

string s = M() + null;

a

string s = M() ?? "";

De este modo, se demuestra que la concatenación de cadenas no necesita llamar String.Concaten absoluto.

Para obtener más información sobre este tema, consulte

¿Por qué String.Concat no está optimizado para StringBuilder.Append?


Creo que se han introducido un par de errores. Seguramente ("C" + "D") + M())genera String.Concat("CD", M()), no String.Concat(M(), "AB"). Y más abajo, (M() + "E") + (null + M())debe generar String.Concat(M(), "E", M()), no String.Concat(M(), M()).
Hammar

21
+1 para el párrafo inicial. :) Respuestas como esta son lo que siempre me sorprende de Stack Overflow.
brichins

23

Encontré la respuesta en MSDN. Uno.

Cómo: Concatenar varias cadenas (Guía de programación de C #)

La concatenación es el proceso de agregar una cadena al final de otra cadena. Cuando concatenas literales de cadena o constantes de cadena mediante el operador +, el compilador crea una sola cadena. No se produce ninguna concatenación del tiempo de ejecución. Sin embargo, las variables de cadena solo se pueden concatenar en tiempo de ejecución. En este caso, debe comprender las implicaciones de rendimiento de los distintos enfoques.


22

Solo uno. El compilador de C # plegará las constantes de cadena y, por lo tanto, esencialmente compila hasta

String result = "1234";

Pensé que cada vez que usa "", crea un objeto String.
The Light

1
@William en general sí. Pero el plegado constante eliminará los pasos intermedios innecesarios
JaredPar

13

Dudo que esto sea obligatorio por cualquier estándar o especificación. Es probable que una versión pueda hacer algo diferente a otra.


3
Es un comportamiento documentado al menos para el compilador C # de Microsoft para VS 2008 y 2010 (consulte la respuesta de @ David-Stratton). Dicho esto, tiene razón: por lo que puedo decir de una lectura rápida, la especificación de C # no especifica esto y probablemente debería considerarse un detalle de implementación.
Chris Shain

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.