Quando é melhor usar String.Format vs concatenação de string?

120

Eu tenho um pequeno código que analisa um valor de índice para determinar uma entrada de célula no Excel. Isso me fez pensar ...

Qual é a diferença entre

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

e

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

Um é melhor que o outro? E porque?

Gavin Miller
fonte
4
Isso é semelhante a stackoverflow.com/questions/16432/…
Jonathan S.
possível duplicata de Por que usar String.Format?
Druida de

Respostas:

115

Antes de C # 6

Para ser honesto, acho que a primeira versão é mais simples - embora eu a simplificasse para:

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

Eu suspeito que outras respostas podem falar sobre o impacto no desempenho, mas para ser honesto, será mínimo se estiver presente - e essa versão de concatenação não precisa analisar a string de formato.

As strings de formato são ótimas para fins de localização, etc., mas em um caso como este, a concatenação é mais simples e funciona tão bem.

Com C # 6

A interpolação de string torna muitas coisas mais simples de ler em C # 6. Nesse caso, seu segundo código se torna:

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

qual é provavelmente a melhor opção, IMO.

Jon Skeet
fonte
Eu sei eu sei. Foi feito em tom de brincadeira (li o link antes, que foi uma boa leitura)
nawfal
Jon. Sempre fui fã do Sr. Richter e segui religiosamente as orientações sobre boxe etc. No entanto, depois de ler seu (antigo) artigo, agora sou um convertido. Obrigado
stevethethread
3
@ mbomb007: Está agora em codeblog.jonskeet.uk/2008/10/08/…
Jon Skeet
4
Agora que C # 6 está disponível, você pode usar a nova sintaxe de interpolação de string para o que eu acho que é ainda mais legível:xlsSheet.Write($"C{rowIndex}", null, title);
HotN
158

Minha preferência inicial (vindo de um background de C ++) era por String.Format. Abandonei isso mais tarde pelos seguintes motivos:

  • A concatenação de strings é indiscutivelmente "mais segura". Aconteceu comigo (e já vi acontecer com vários outros desenvolvedores) remover um parâmetro ou bagunçar a ordem do parâmetro por engano. O compilador não verificará os parâmetros em relação à string de formato e você terminará com um erro de tempo de execução (isto é, se você tiver sorte o suficiente para não tê-lo em um método obscuro, como registrar um erro). Com a concatenação, a remoção de um parâmetro é menos sujeita a erros. Você pode argumentar que a chance de erro é muito pequena, mas pode acontecer.

- A concatenação de strings permite valores nulos, String.Formatnão. Escrever " s1 + null + s2" não quebra, apenas trata o valor nulo como String.Empty. Bem, isso pode depender do seu cenário específico - há casos em que você gostaria de um erro em vez de ignorar silenciosamente um FirstName nulo. No entanto, mesmo nessa situação, eu pessoalmente prefiro verificar se há nulos e lançar erros específicos em vez do ArgumentNullException padrão que recebo de String.Format.

  • A concatenação de strings tem um desempenho melhor. Alguns dos posts acima já mencionam isso (sem realmente explicar o porquê, o que me determinou a escrever este post :).

A ideia é que o compilador .NET é inteligente o suficiente para converter este pedaço 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;
}

para isso:

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

O que acontece sob o capô do String.Concat é fácil de adivinhar (use o Reflector). Os objetos na matriz são convertidos em suas strings por meio de ToString (). Em seguida, o comprimento total é calculado e apenas uma string é alocada (com o comprimento total). Por fim, cada string é copiada para a string resultante por meio de wstrcpy em algum trecho de código não seguro.

Razões String.Concaté muito mais rápido? Bem, todos nós podemos dar uma olhada no que String.Formatestá acontecendo - você ficará surpreso com a quantidade de código necessária para processar a string de formato. Além disso (vi comentários sobre o consumo de memória), String.Formatusa um StringBuilder internamente. Veja como:

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

Portanto, para cada argumento aprovado, ele reserva 8 caracteres. Se o argumento for um valor de um dígito, uma pena, temos algum espaço desperdiçado. Se o argumento for um objeto personalizado que retorna algum texto longo ToString(), então pode haver até mesmo alguma realocação necessária (pior cenário, é claro).

Comparada a isso, a concatenação desperdiça apenas o espaço do array do objeto (não muito, levando em consideração que é um array de referências). Não há análise para especificadores de formato e nenhum StringBuilder intermediário. A sobrecarga de boxing / unboxing está presente em ambos os métodos.

A única razão pela qual eu escolheria String.Format é quando a localização está envolvida. Colocar strings de formato em recursos permite que você ofereça suporte a idiomas diferentes sem mexer com o código (pense em cenários em que os valores formatados mudam de ordem dependendo do idioma, ou seja, "após {0} horas e {1} minutos" pode parecer bem diferente em japonês: )


Para resumir minha primeira (e bastante longa) postagem:

  • melhor maneira (em termos de desempenho vs. facilidade de manutenção / legibilidade) para mim é usando concatenação de string, sem quaisquer ToString()chamadas
  • se você está atrás de uma performance, faça as ToString()ligações você mesmo para evitar boxe (eu sou um pouco tendencioso para a legibilidade) - o mesmo que a primeira opção em sua pergunta
  • se você estiver mostrando strings localizadas para o usuário (não é o caso aqui), String.Format()tem uma vantagem.
Dan C.
fonte
5
1) string.Formaté "seguro" ao usar o ReSharper; ou seja, é tão seguro quanto qualquer outro código que pode ser usado [incorretamente]. 2) string.Format não permitem um "seguro" null: string.Format("A{0}B", (string)null)resultado em "AB". 3) Raramente me importo com esse nível de desempenho (e para esse fim, é um dia raro quando eu StringBuilder
Concordo em 2), vou editar a postagem. Não é possível verificar se isso era seguro na versão 1.1, mas o framework mais recente é de fato seguro para nulos.
Dan C.
String.Concat ainda é usado se um dos operandos for uma chamada de método com um valor de retorno, em vez de ser um parâmetro ou variável?
Richard Collette
2
@RichardCollette Sim, String.Concat é usado até mesmo para concatenar valores de retorno de chamadas de método, por exemplo, string s = "This " + MyMethod(arg) + " is a test";é compilado para uma String.Concat()chamada no modo Release.
Dan C.
Resposta fantástica; muito bem escrito e explicado.
Frank V
6

Acho que a primeira opção é mais legível e deve ser sua principal preocupação.

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

string.Format usa um StringBuilder sob o capô (verifique com o refletor ), portanto, não terá nenhum benefício de desempenho, a menos que você esteja fazendo uma quantidade significativa de concatenação. Será mais lento para o seu cenário, mas a realidade é que essa decisão de otimização de micro desempenho é inadequada na maioria das vezes e você deve realmente se concentrar na legibilidade do seu código, a menos que esteja em um loop.

De qualquer forma, primeiro escreva para facilitar a leitura e, em seguida, use um criador de perfil de desempenho para identificar seus pontos de acesso se você realmente acha que tem problemas de desempenho.

Martin Hollingsworth
fonte
5

Para um caso simples em que é uma concatenação única simples, sinto que não vale a pena a complexidade de string.Format(e não testei, mas suspeito que para um caso simples como este, string.Format pode ser um pouco mais lento, com a análise de string de formato e tudo). Como Jon Skeet, prefiro não chamar explicitamente .ToString(), já que isso será feito implicitamente pela string.Concat(string, object)sobrecarga, e acho que o código tem uma aparência mais limpa e mais fácil de ler sem ele.

Mas para mais do que algumas concatenações (quantas são subjetivas), eu definitivamente prefiro string.Format. A certa altura, acho que tanto a legibilidade quanto o desempenho sofrem desnecessariamente com a concatenação.

Se houver muitos parâmetros para a string de formato (novamente, "muitos" é subjetivo), geralmente prefiro incluir índices comentados nos argumentos de substituição, para não perder o controle de qual valor vai para qual parâmetro. Um exemplo inventado:

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]
);

Atualizar

Ocorre-me que o exemplo que eu dei é um pouco confuso, porque parece que eu usei tanto concatenação e string.Formataqui. E sim, lógica e lexicamente, foi o que fiz. Mas as concatenações serão todas otimizadas pelo compilador 1 , já que são todas strings literais. Portanto, em tempo de execução, haverá uma única string. Portanto, devo dizer que prefiro evitar muitas concatenações em tempo de execução .

É claro que a maior parte deste tópico está desatualizado agora, a menos que você ainda esteja preso ao uso do C # 5 ou mais antigo. Agora temos strings interpoladas , que para facilitar a leitura, são muito superiores string.Format, em quase todos os casos. Atualmente, a menos que esteja apenas concatenando um valor diretamente no início ou no final de um literal de string, quase sempre uso a interpolação de string. Hoje, eu escreveria meu exemplo anterior assim:

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"
);

Você perde a concatenação de tempo de compilação dessa maneira. Cada string interpolada é transformada em uma chamada para string.Formatpelo compilador e seus resultados são concatenados em tempo de execução. Isso significa que é um sacrifício do desempenho do tempo de execução para facilitar a leitura. Na maioria das vezes, é um sacrifício que vale a pena, porque a penalidade do tempo de execução é insignificante. No código de desempenho crítico, entretanto, você pode precisar criar perfis de soluções diferentes.


1 Você pode ver isso na especificação C # :

... as seguintes construções são permitidas em expressões constantes:

...

  • O operador binário + ... predefinido ...

Você também pode verificar isso com um pequeno código:

const string s =
    "This compiles successfully, " +
    "and you can see that it will " +
    "all be one string (named `s`) " +
    "at run time";
P papai
fonte
1
fyi, você pode usar @ "... string de várias linhas" em vez de todas as concatenações.
Aaron Palmer
Sim, mas então você tem que justificar sua string à esquerda. @ strings incluem todas as novas linhas e caracteres de tabulação entre as aspas.
P Daddy
Eu sei que isso é antigo, mas este é um caso em que eu diria colocar a string de formato em um arquivo resx.
Andy
2
Uau, todo mundo está focado na string literal, em vez de no cerne da questão.
P Daddy
heheh - Eu estava apenas notando a concatenação de String dentro de seuString.Format()
Kristopher
3

Se sua string fosse mais complexa com muitas variáveis ​​sendo concatenadas, eu escolheria string.Format (). Mas para o tamanho da string e o número de variáveis ​​sendo concatenadas em seu caso, eu escolheria sua primeira versão, é mais espartana .

Aaron Palmer
fonte
3

Dei uma olhada em String.Format (usando Reflector) e ele realmente cria um StringBuilder e chama AppendFormat nele. Portanto, é mais rápido do que concat para vários movimentos. O mais rápido (acredito) seria criar um StringBuilder e fazer as chamadas para acrescentar manualmente. É claro que o número de "muitos" está para ser adivinhado. Eu usaria + (na verdade & porque sou um programador VB principalmente) para algo tão simples como o seu exemplo. À medida que fica mais complexo, eu uso String.Format. Se houver MUITAS variáveis, então eu escolheria StringBuilder e Append, por exemplo, temos código que constrói código, lá eu uso uma linha de código real para produzir uma linha de código gerado.

Parece haver alguma especulação sobre quantas strings são criadas para cada uma dessas operações, então vamos dar alguns exemplos simples.

"C" + rowIndex.ToString();

"C" já é uma string.
rowIndex.ToString () cria outra string. (@manohard - nenhum boxing de rowIndex ocorrerá)
Então nós obtemos a string final.
Se tomarmos o exemplo de

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

então temos "C {0}" como string
rowIndex é encaixotado para ser passado para a função.
Um novo stringbuilder é criado
AppendFormat é chamado no construtor de string - eu não sei os detalhes de como AppendFormat funciona, mas vamos assumir que é ultra eficiente, ele ainda terá que converter o rowIndex em uma caixa de caracteres.
Em seguida, converta o construtor de strings em uma nova string.
Eu sei que StringBuilders tentam impedir que cópias de memória inúteis ocorram, mas o String.Format ainda termina com sobrecarga extra em comparação com a concatenação simples.

Se tomarmos agora um exemplo com mais algumas strings

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

temos 6 strings para começar, que serão iguais para todos os casos.
Usando a concatenação, também temos 4 strings intermediárias mais o resultado final. São esses resultados intermediários que são eliminados usando String, Format (ou um StringBuilder).
Lembre-se de que para criar cada string intermediária, a anterior deve ser copiada para um novo local de memória, não é apenas a alocação de memória que é potencialmente lenta.

pipTheGeek
fonte
4
Nitpick. No "a" + ... + "b" + ... + "c" + ..., você não terá na verdade 4 strings intermediárias. O compilador irá gerar uma chamada para o método estático String.Concat (params string [] values), e todos eles serão concatenados de uma vez. Eu ainda prefiro string.Format por uma questão de legibilidade, no entanto.
P Daddy
2

Eu gosto de String.Format porque pode tornar seu texto formatado muito mais fácil de seguir e ler do que a concatenação embutida, também é muito mais flexível permitindo que você formate seus parâmetros, no entanto, para usos curtos como o seu, não vejo problema em concatenar.

Para concatenações dentro de loops ou em strings grandes, você deve sempre tentar usar a classe StringBuilder.

CMS
fonte
2

Esse exemplo provavelmente é muito trivial para notar a diferença. Na verdade, acho que na maioria dos casos o compilador pode otimizar qualquer diferença.

No entanto, se eu tivesse que adivinhar, daria string.Format()uma vantagem para cenários mais complicados. Mas isso é mais um pressentimento de que provavelmente fará um trabalho melhor utilizando um buffer em vez de produzir várias strings imutáveis ​​e não com base em dados reais.

Joel Coehoorn
fonte
1

Eu concordo com muitos dos pontos acima, outro ponto que acredito que deve ser mencionado é a sustentabilidade do código. string.Format permite uma mudança de código mais fácil.

ou seja, tenho uma mensagem "The user is not authorized for location " + locationou "The User is not authorized for location {0}"

se eu quisesse mudar a mensagem para dizer: location + " does not allow this User Access"ou "{0} does not allow this User Access"

com string.Format tudo o que tenho a fazer é mudar a string. para concatenação eu tenho que modificar essa mensagem

se usado em vários lugares pode economizar muito tempo.

decanato
fonte
1

Tive a impressão de que string.format foi mais rápido, parece ser 3 x mais lento neste teste

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 levou 4,6 segundos e ao usar '+' levou 1,6 segundos.

Kitemark76
fonte
7
O compilador reconhece "1" + "2" + "3" + "4" + "5" + "6" + "7" + "8" + "9" + "10"como um literal de string, então a linha efetivamente se torna a "12345678910" + ique é mais rápida que a anteriorstring.Format(...)
wertzui
0

string.Format é provavelmente uma escolha melhor quando o modelo de formato ("C {0}") é armazenado em um arquivo de configuração (como Web.config / App.config)

Andrei Rînea
fonte
0

Eu fiz um pouco de perfil de vários métodos de string, incluindo string.Format, StringBuilder e concatenação de string. A concatenação de strings quase sempre superou os outros métodos de construção de strings. Então, se o desempenho é fundamental, então é melhor. No entanto, se o desempenho não for crítico, eu pessoalmente acho que string.Format é mais fácil de seguir no código. (Mas essa é uma razão subjetiva) StringBuilder, no entanto, é provavelmente mais eficiente no que diz respeito à utilização de memória.

dviljoen
fonte
0

Eu prefiro String.Format em relação ao desempenho

Farzad J
fonte
-1

A concatenação de string ocupa mais memória em comparação com String.Format. Portanto, a melhor maneira de concatenar strings é usando String.Format ou System.Text.StringBuilder Object.

Vamos considerar o primeiro caso: "C" + rowIndex.ToString () Vamos supor que rowIndex é um tipo de valor, então o método ToString () tem que Box para converter o valor em String e então CLR cria memória para a nova string com ambos os valores incluídos.

Onde como string.Format espera o parâmetro do objeto e pega rowIndex como um objeto e o converte em string internamente, é claro que haverá Boxing, mas é intrínseco e também não vai ocupar tanta memória como no primeiro caso.

Para strings curtas, não importa muito, eu acho ...


fonte