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.

A luz
fonte
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();
    }
}
Chris Shain
fonte
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?

Eric Lippert
fonte
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.

David
fonte
22

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

String result = "1234";
JaredPar
fonte
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.

Variável miserável
fonte
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
13

Um, por serem estáticos, o compilador será capaz de otimizá-lo para uma única string em tempo de compilação.

Se fossem dinâmicos, teriam sido otimizados para uma única chamada para String.Concat (string, string, string, string) .

Joachim Isaksson
fonte