Quantos objetos String serão criados ao usar um sinal de mais?


115

Quantos objetos String serão criados ao usar um sinal de mais no código abaixo?

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

Se fosse como abaixo, eu teria dito três objetos String: "1", "2", "12".

String result = "1" + "2";

Eu também sei que os objetos String são armazenados em cache no Pool / Table Intern de String para melhoria de desempenho, mas essa não é a questão.


Strings só são internadas se você chamar explicitamente String.Intern.
— Joe White

7
@JoeWhite: são eles?
— Igor Korkhov

13
Não exatamente. Todos os literais de string são internados automaticamente. Os resultados das operações de string não são.
— Stefan Paul Noack

Além do mais, no exemplo do OP, há apenas uma constante de string e ela está interna. Vou atualizar minha resposta para ilustrar.
— Chris Shain

+1. Para um exemplo da vida real da necessidade de codificar uma catenação de string nesse estilo, a seção de exemplos de msdn.microsoft.com/en-us/library/… tem um que não seria possível se o compilador não pudesse otimizá-lo a uma única constante, devido às restrições nos valores atribuídos aos parâmetros de atributo.
— ClickRick

Respostas:


161

Surpreendentemente, depende.

Se você fizer isso em um método:

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

então o compilador parece emitir o código usando String.Concatcomo @Joachim respondeu (+1 para ele aliás).

Se você os definir como constantes , por exemplo:

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

ou como literais , como na pergunta original:

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

então o compilador irá otimizar esses +sinais. É equivalente a:

const String result = "1234";

Além disso, o compilador removerá expressões constantes estranhas e apenas as emitirá se forem usadas ou expostas. Por exemplo, 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);
}

Gera apenas uma string - a constante result(igual a "1234"). onee twonão aparecem no IL resultante.

Lembre-se de que pode haver mais otimizações no tempo de execução. Estou apenas analisando o que o IL é produzido.

Finalmente, no que diz respeito ao internamento, constantes e literais são internados, mas o valor que é internado é o valor da constante resultante no IL, não o literal. Isso significa que você pode obter ainda menos objetos de string do que o esperado, já que várias constantes ou literais definidas de forma idêntica serão na verdade o mesmo objeto! Isso é ilustrado pelo seguinte:

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

No caso em que Strings são concatenadas em um loop (ou de outra forma dinamicamente), você acaba com uma string extra por concatenação. Por exemplo, o seguinte cria 12 instâncias de string: 2 constantes + 10 iterações, cada uma resultando em uma nova instância de String:

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

Mas (também surpreendentemente), várias concatenações consecutivas são combinadas pelo compilador em uma única concatenação multi-string. Por exemplo, este programa também produz apenas 12 instâncias de string! Isso ocorre porque " Mesmo se você usar vários operadores + em uma instrução, o conteúdo da string é copiado apenas uma vez. "

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

que tal String result = "1" + "2" + três + quatro; onde dois e três são declarados como string três = "3"; String quatro = "4" ;?
— The Light

Mesmo isso resulta em uma string. Acabei de executá-lo no LinqPad para verificar novamente.
— Chris Shain

1
@Servy - O comentário parece ter sido atualizado. Quando você altera um comentário, ele não é marcado como alterado.
— Security Hound

1
Um caso que seria bom considerar para integridade é concatenar em um loop. Por exemplo, quantos objetos de string o código a seguir aloca:string s = ""; for (int i = 0; i < n; i++) s += "a";
— Joren

1
Eu uso LINQPad ( linqpad.net ) ou Reflector ( reflector.net ). O primeiro mostra o IL de trechos arbitrários de código, o último descompila assemblies em IL e pode regenerar C # equivalente a partir desse IL. Há também uma ferramenta interna chamada ILDASM ( msdn.microsoft.com/en-us/library/f7dy01k1(v=vs.80).aspx ) Entender IL é uma coisa complicada - consulte codebetter.com/raymondlewallen/2005/ 02/07 /…
— Chris Shain

85

A resposta de Chris Shain é muito boa. Como a pessoa que escreveu o otimizador de concatenação de string, eu acrescentaria apenas dois pontos interessantes adicionais.

A primeira é que o otimizador de concatenação essencialmente ignora os parênteses e a associatividade à esquerda quando pode fazer isso com segurança. Suponha que você tenha um método M () que retorna uma string. Se você diz:

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

então, o compilador raciocina que o operador de adição é associativo à esquerda e, portanto, é o mesmo que:

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

Mas isso:

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

é o mesmo que

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

então essa é a concatenação da string constante "CD" com M().

Na verdade, o otimizador de concatenação percebe que a concatenação de string é associativa e gera String.Concat(M(), "AB")para o primeiro exemplo, embora isso viole a associatividade à esquerda.

Você pode até fazer isso:

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

e ainda vamos gerar String.Concat(M(), "EF", M()).

O segundo ponto interessante é que strings nulas e vazias são otimizadas. Então, se você fizer isso:

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

você terá String.Concat(M(), M())

Surge então uma questão interessante: e quanto a isso?

string s = M() + null;

Não podemos otimizar isso para

string s = M();

porque M()pode retornar nulo, mas String.Concat(M(), null)retornaria uma string vazia se M()retornar nulo. Então, o que fazemos é reduzir

string s = M() + null;

para

string s = M() ?? "";

Demonstrando assim que a concatenação de string não precisa realmente ser chamada String.Concat.

Para mais leituras sobre este assunto, consulte

Por que String.Concat não é otimizado para StringBuilder.Append?


Acho que alguns erros podem ter ocorrido ali. Certamente, ("C" + "D") + M())gera String.Concat("CD", M()), não String.Concat(M(), "AB"). E mais abaixo, (M() + "E") + (null + M())deve gerar String.Concat(M(), "E", M()), não String.Concat(M(), M()).
— hammar

21
1 para o parágrafo inicial. :) Respostas como essa são o que sempre me impressiona no Stack Overflow.
— brichins

23

Eu encontrei a resposta no MSDN. 1.

How to: Concatenate Multiple Strings (C # Programming Guide)

Concatenação é o processo de anexar uma string ao final de outra string. Quando você concatena literais de string ou constantes de string usando o operador +, o compilador cria uma única string. Nenhuma concatenação de tempo de execução ocorre. No entanto, as variáveis ​​de string podem ser concatenadas apenas em tempo de execução. Nesse caso, você deve compreender as implicações de desempenho das várias abordagens.


22

Apenas um. O compilador C # dobrará constantes de string e, portanto, essencialmente compilará para

String result = "1234";

Eu pensei que sempre que você usa "", ele cria um objeto String.
— The Light

1
@William em geral, sim. Mas o dobramento constante removerá as etapas intermediárias desnecessárias
— JaredPar

13

Duvido que isso seja exigido por qualquer padrão ou especificação. Uma versão provavelmente pode fazer algo diferente da outra.


3
É um comportamento documentado pelo menos para o compilador C # da Microsoft para VS 2008 e 2010 (consulte a resposta de @David-Stratton). Dito isso, você está certo - pelo que posso dizer a partir de uma leitura rápida, a especificação C # não especifica isso e provavelmente deve ser considerada um detalhe de implementação.
— Chris Shain

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.