Saída de string: formato ou concat em C #?

178

Digamos que você deseja gerar ou concatenar strings. Qual dos seguintes estilos você prefere?

  • var p = new { FirstName = "Bill", LastName = "Gates" };

  • Console.WriteLine("{0} {1}", p.FirstName, p.LastName);

  • Console.WriteLine(p.FirstName + " " + p.LastName);

Você prefere usar o formato ou simplesmente concatena as strings? Qual é o seu favorito? Um desses está machucando seus olhos?

Você tem algum argumento racional para usar um e não o outro?

Eu iria para o segundo.

Philippe
fonte

Respostas:

88

Experimente este código.

É uma versão ligeiramente modificada do seu código.
1. Removai o Console.WriteLine, pois provavelmente há poucas ordens de magnitude mais lentas do que o que estou tentando medir.
2. Estou iniciando o cronômetro antes do loop e interrompendo-o logo depois, desta forma não perderei precisão se a função levar, por exemplo, 26,4 ticks para executar.
3. A maneira como você dividiu o resultado por algumas iterações estava errada. Veja o que acontece se você tiver 1000 milissegundos e 100 milissegundos. Nas duas situações, você receberá 0 ms depois de dividir por 1000000.

Stopwatch s = new Stopwatch();

var p = new { FirstName = "Bill", LastName = "Gates" };

int n = 1000000;
long fElapsedMilliseconds = 0, fElapsedTicks = 0, cElapsedMilliseconds = 0, cElapsedTicks = 0;

string result;
s.Start();
for (var i = 0; i < n; i++)
    result = (p.FirstName + " " + p.LastName);
s.Stop();
cElapsedMilliseconds = s.ElapsedMilliseconds;
cElapsedTicks = s.ElapsedTicks;
s.Reset();
s.Start();
for (var i = 0; i < n; i++)
    result = string.Format("{0} {1}", p.FirstName, p.LastName);
s.Stop();
fElapsedMilliseconds = s.ElapsedMilliseconds;
fElapsedTicks = s.ElapsedTicks;
s.Reset();


Console.Clear();
Console.WriteLine(n.ToString()+" x result = string.Format(\"{0} {1}\", p.FirstName, p.LastName); took: " + (fElapsedMilliseconds) + "ms - " + (fElapsedTicks) + " ticks");
Console.WriteLine(n.ToString() + " x result = (p.FirstName + \" \" + p.LastName); took: " + (cElapsedMilliseconds) + "ms - " + (cElapsedTicks) + " ticks");
Thread.Sleep(4000);

Esses são os meus resultados:

1000000 x resultado = string.Format ("{0} {1}", p.FirstName, p.LastName); levou: 618ms - 2213706 ticks
1000000 x resultado = (p.PrimeiroNome + "" + p.ÚltimoNome); levou: 166ms - 595610 carrapatos

Michał Piaskowski
fonte
1
Muito interessante. Eu tenho uma média de 224ms vs. 48ms, uma melhoria de x4,66, ainda melhor que o seu x3,72. Gostaria de saber se existe uma ferramenta de pós-compilação que pode reescrever o IL string.Formatque não usa nenhum recurso de formatação composta (isto é, simples {0}) e substitui-os pela concatenação de cadeias consideravelmente mais rápida. Gostaria de saber que tal feito é possível com um reescritor de IL existente, como o PostSharp.
Allon Guralnek
31
As strings são imutáveis, isso significa que o mesmo pequeno pedaço de memória é usado repetidamente em seu código. Adicionar as mesmas duas seqüências e criar a mesma nova sequência repetidamente não afeta a memória. .Net é inteligente o suficiente apenas para usar a mesma referência de memória. Portanto, seu código não testa verdadeiramente a diferença entre os dois métodos concat. Veja o código na minha resposta abaixo.
quer
1
Honestamente, eu sempre concatenar uma vez que é mais fácil de ler para mim e wow é mais rápido :)
puretppc
Então a velocidade é a única razão para escolher um sobre o outro?
Niico 26/10/16
158

Estou surpreso que tantas pessoas imediatamente desejem encontrar o código que executa o mais rápido. Se UM MILHÃO de iterações AINDA demorar menos de um segundo para processar, isso será de QUALQUER FORMA perceptível para o usuário final? Não muito provável.

Otimização prematura = FAIL.

Eu iria com a String.Formatopção, apenas porque faz mais sentido do ponto de vista arquitetônico. Eu não me importo com o desempenho até que isso se torne um problema (e, caso isso acontecesse, eu me perguntaria: preciso concatenar um milhão de nomes de uma vez? Certamente nem todos caberão na tela ...)

Considere se o seu cliente deseja alterá-lo posteriormente para que ele possa configurar se deseja exibir "Firstname Lastname"ou "Lastname, Firstname."com a opção Formato, isso é fácil - basta trocar a string de formato. Com o concat, você precisará de código extra. Certamente isso não soa muito nesse exemplo em particular, mas extrapola.

Fredrik Kalseth
fonte
47
Bom ponto em termos de "Otimização prematura == FAIL", sim. Mas, quando você começa a pagar pela presença de execução (nuvem e infraestrutura como serviço, alguém?) E / ou começa a dar suporte a 1 milhão de usuários em algo, a resposta a um único usuário em uma solicitação não é a questão. O custo de manutenção de um pedido de um usuário é um custo para sua linha de fundo, bem como uma questão de escala, se / quando vários milhares de outras chamadas estão vindo através de ...
Aidanapword
23
Isso é totalmente errado. Em um ambiente de desenvolvimento da Web, muitas vezes o código de geração de string é profundo no seu modelo, visualizações e controladores e pode ser chamado dezenas de milhares de vezes por carregamento de página. Reduzir o tempo gasto avaliando o código de geração de cadeias em 50% pode ser uma vitória enorme.
Benjamin Sussman 29/07
2
Uma pergunta como essa não será aplicada apenas na instância única do OP. A resposta é o tipo de coisa que as pessoas podem lembrar como "de que maneira devo montar as cordas?" enquanto eles escrevem todo o seu código.
Phil Miller
6
@ Benjamin: ... nesse caso, você daria um perfil e descobriria que esse seria o seu gargalo. Aposto que você está apenas tirando isso do nada; Depois de escrever e criar um perfil de vários aplicativos da Web no passado, quase sempre achei o gargalo nos tempos de resposta (no lado do servidor) as consultas ao banco de dados.
BlueRaja - Danny Pflughoeft
2
Definitivamente, essa não é a otimização prematura. Bastante a falácia. O desempenho de cadeias de caracteres pode paralisar completamente as interfaces de usuário, especialmente no .NET se você estiver fazendo muita formatação e criação de cadeias. ubiquity.acm.org/article.cfm?id=1513451
user99999991
54

Oh, meu Deus - depois de ler uma das outras respostas, tentei reverter a ordem das operações - executando primeiro a concatenação e depois o String.Format ...

Bill Gates
Console.WriteLine(p.FirstName + " " + p.LastName); took: 8ms - 30488 ticks
Bill Gates
Console.WriteLine("{0} {1}", p.FirstName, p.LastName); took: 0ms - 182 ticks

Portanto, a ordem das operações faz uma enorme diferença, ou melhor, a primeira operação é SEMPRE muito mais lenta.

Aqui estão os resultados de uma execução em que as operações são concluídas mais de uma vez. Tentei alterar as ordens, mas as coisas geralmente seguem as mesmas regras, uma vez que o primeiro resultado é ignorado:

Bill Gates
Console.WriteLine(FirstName + " " + LastName); took: 5ms - 20335 ticks
Bill Gates
Console.WriteLine(FirstName + " " + LastName); took: 0ms - 156 ticks
Bill Gates
Console.WriteLine(FirstName + " " + LastName); took: 0ms - 122 ticks
Bill Gates
Console.WriteLine("{0} {1}", FirstName, LastName); took: 0ms - 181 ticks
Bill Gates
Console.WriteLine("{0} {1}", FirstName, LastName); took: 0ms - 122 ticks
Bill Gates
String.Concat(FirstName, " ", LastName); took: 0ms - 142 ticks
Bill Gates
String.Concat(FirstName, " ", LastName); took: 0ms - 117 ticks

Como você pode ver, as execuções subseqüentes do mesmo método (refatorei o código em três métodos) são incrementalmente mais rápidas. O mais rápido parece ser o método Console.WriteLine (String.Concat (...)), seguido pela concatenação normal e depois pelas operações formatadas.

O atraso inicial na inicialização provavelmente é a inicialização do Stream do console, como colocar um Console.Writeline ("Start!") Antes da primeira operação trazer todos os tempos de volta à linha.

samjudson
fonte
2
Em seguida, remova completamente o Console.WriteLine dos seus testes. Está distorcendo os resultados!
CShark
Eu sempre começo com um cenário descartável ou de "controle" ao executar testes de desempenho exatamente por esse motivo
drzaus
36

As strings são imutáveis, isso significa que o mesmo pequeno pedaço de memória é usado repetidamente em seu código. Adicionar as mesmas duas seqüências e criar a mesma nova sequência repetidamente não afeta a memória. .Net é inteligente o suficiente apenas para usar a mesma referência de memória. Portanto, seu código não testa verdadeiramente a diferença entre os dois métodos concat.

Tente isso para obter o tamanho:

Stopwatch s = new Stopwatch();

int n = 1000000;
long fElapsedMilliseconds = 0, fElapsedTicks = 0, cElapsedMilliseconds = 0, cElapsedTicks = 0, sbElapsedMilliseconds = 0, sbElapsedTicks = 0;

Random random = new Random(DateTime.Now.Millisecond);

string result;
s.Start();
for (var i = 0; i < n; i++)
    result = (random.Next().ToString() + " " + random.Next().ToString());
s.Stop();
cElapsedMilliseconds = s.ElapsedMilliseconds;
cElapsedTicks = s.ElapsedTicks;
s.Reset();

s.Start();
for (var i = 0; i < n; i++)
    result = string.Format("{0} {1}", random.Next().ToString(), random.Next().ToString());
s.Stop();
fElapsedMilliseconds = s.ElapsedMilliseconds;
fElapsedTicks = s.ElapsedTicks;
s.Reset();

StringBuilder sb = new StringBuilder();
s.Start();
for(var i = 0; i < n; i++){
    sb.Clear();
    sb.Append(random.Next().ToString());
    sb.Append(" ");
    sb.Append(random.Next().ToString());
    result = sb.ToString();
}
s.Stop();
sbElapsedMilliseconds = s.ElapsedMilliseconds;
sbElapsedTicks = s.ElapsedTicks;
s.Reset();

Console.WriteLine(n.ToString() + " x result = string.Format(\"{0} {1}\", p.FirstName, p.LastName); took: " + (fElapsedMilliseconds) + "ms - " + (fElapsedTicks) + " ticks");
Console.WriteLine(n.ToString() + " x result = (p.FirstName + \" \" + p.LastName); took: " + (cElapsedMilliseconds) + "ms - " + (cElapsedTicks) + " ticks");
Console.WriteLine(n.ToString() + " x sb.Clear();sb.Append(random.Next().ToString()); sb.Append(\" \"); sb.Append(random.Next().ToString()); result = sb.ToString(); took: " + (sbElapsedMilliseconds) + "ms - " + (sbElapsedTicks) + " ticks");
Console.WriteLine("****************");
Console.WriteLine("Press Enter to Quit");
Console.ReadLine();

Saída de amostra:

1000000 x result = string.Format("{0} {1}", p.FirstName, p.LastName); took: 513ms - 1499816 ticks
1000000 x result = (p.FirstName + " " + p.LastName); took: 393ms - 1150148 ticks
1000000 x sb.Clear();sb.Append(random.Next().ToString()); sb.Append(" "); sb.Append(random.Next().ToString()); result = sb.ToString(); took: 405ms - 1185816 ticks
MrPhil
fonte
1
Adicionado um StringBuilder e saída de amostra para a resposta
mikeschuld
Vejo como o uso string.Formatvale o pequeno desempenho atingido aqui. Arquitetonicamente, é melhor, pois significa que você pode alterar o formato mais facilmente. Mas construtor de cordas realmente não entendo o ponto. Todos os outros tópicos aqui dizem que você deve usar o Stringbuilder em vez de concatenar as strings. Qual a vantagem? Claramente, não velocidade, como este benchmark prova.
Rogonline #
22

Tenha pena dos pobres tradutores

Se você sabe que seu aplicativo permanecerá em inglês, tudo bem, salve o relógio. No entanto, muitas culturas costumam ver Sobrenome Nome em, por exemplo, endereços.

Portanto string.Format(), use , especialmente se você quiser que seu aplicativo vá para qualquer lugar em que o inglês não seja o primeiro idioma.

Jeremy McGee
fonte
2
Como se string.Format()comportaria diferente em diferentes culturas? Ainda não imprimiria o nome e o sobrenome? Parece que você teria que levar em conta a cultura diferente nas duas situações. Eu sinto que estou perdendo alguma coisa aqui.
Broots Waymb
2
Concordo com o @DangerZone. Como string.Format()saberia que você estava usando um nome para um endereço? Se string.Format()trocado com {0} {1}base na cultura, consideraria quebrado.
Alex McMillan
2
Acredito que o ponto que Jeremy estava tentando enfatizar é que, no cenário descrito para oferecer suporte a diferentes países, pode ser apropriado extrair a própria string de formato para um recurso de idioma. Na maioria dos países, essa sequência seria "{0} {1}", mas nos países onde o sobrenome é a operação típica (por exemplo, Hungria, Hong Kong, Camboja, China, Japão, Coréia, Madagascar, Taiwan, Vietnã e partes da Índia) essa string seria "{1} {0}".
Richard J Foster
De fato. Ou, mais sutilmente, adicione a string de formato como um atributo da pessoa. Eu, por exemplo, gosto de ter meu sobrenome depois do meu primeiro nome, mas meu colega Beng não.
22417 Jeremy McGee
14

Aqui estão meus resultados com mais de 100.000 iterações:

Console.WriteLine("{0} {1}", p.FirstName, p.LastName); took (avg): 0ms - 689 ticks
Console.WriteLine(p.FirstName + " " + p.LastName); took (avg): 0ms - 683 ticks

E aqui está o código do banco:

Stopwatch s = new Stopwatch();

var p = new { FirstName = "Bill", LastName = "Gates" };

//First print to remove the initial cost
Console.WriteLine(p.FirstName + " " + p.LastName);
Console.WriteLine("{0} {1}", p.FirstName, p.LastName);

int n = 100000;
long fElapsedMilliseconds = 0, fElapsedTicks = 0, cElapsedMilliseconds = 0, cElapsedTicks = 0;

for (var i = 0; i < n; i++)
{
    s.Start();
    Console.WriteLine(p.FirstName + " " + p.LastName);
    s.Stop();
    cElapsedMilliseconds += s.ElapsedMilliseconds;
    cElapsedTicks += s.ElapsedTicks;
    s.Reset();
    s.Start();
    Console.WriteLine("{0} {1}", p.FirstName, p.LastName);
    s.Stop();
    fElapsedMilliseconds += s.ElapsedMilliseconds;
    fElapsedTicks += s.ElapsedTicks;
    s.Reset();
}

Console.Clear();

Console.WriteLine("Console.WriteLine(\"{0} {1}\", p.FirstName, p.LastName); took (avg): " + (fElapsedMilliseconds / n) + "ms - " + (fElapsedTicks / n) + " ticks");
Console.WriteLine("Console.WriteLine(p.FirstName + \" \" + p.LastName); took (avg): " + (cElapsedMilliseconds / n) + "ms - " + (cElapsedTicks / n) + " ticks");

Então, eu não sei cuja resposta marcar como resposta :)

Philippe
fonte
Por que o plano de fundo é azul para esta resposta?
user88637
@yossi é azul porque o respondente é o mesmo que o consulente
Davy8
9

Concatenar seqüências de caracteres é bom em um cenário simples como esse - é mais complicado com algo mais complicado que isso, até Sobrenome, Nome. Com o formato, você pode ver, de relance, qual será a estrutura final da string ao ler o código; com concatenação, torna-se quase impossível discernir imediatamente o resultado final (exceto com um exemplo muito simples como este).

A longo prazo, o que isso significa é que, quando você voltar para fazer uma alteração no formato da sua string, poderá aparecer e fazer alguns ajustes na string de formatação ou enrugar a sobrancelha e começar a se mover tipos de acessadores de propriedades misturados com texto, com maior probabilidade de apresentar problemas.

Se você estiver usando o .NET 3.5, poderá usar um método de extensão como este e obter uma fluidez fácil, fora da sintaxe do manguito da seguinte maneira:

string str = "{0} {1} is my friend. {3}, {2} is my boss.".FormatWith(prop1,prop2,prop3,prop4);

Por fim, à medida que seu aplicativo cresce em complexidade, você pode decidir que, para manter cordas em seu aplicativo, você deseja movê-las para um arquivo de recurso para localizar ou simplesmente para um auxiliar estático. Isso será MUITO mais fácil de obter se você tiver usado formatos consistentemente e seu código puder ser simplesmente refatorado para usar algo como

string name = String.Format(ApplicationStrings.General.InformalUserNameFormat,this.FirstName,this.LastName);
Nathan
fonte
7

Para manipulação muito simples, eu usaria a concatenação, mas depois que você ultrapassa 2 ou 3 elementos, o Format torna-se IMO mais apropriado.

Outro motivo para preferir String.Format é que as seqüências .NET são imutáveis ​​e, dessa maneira, cria menos cópias temporárias / intermediárias.

user2189331
fonte
6

Embora eu compreenda totalmente a preferência de estilo e escolhi a concatenação para minha primeira resposta parcialmente com base em minha própria preferência, parte da minha decisão foi baseada no pensamento de que a concatenação seria mais rápida. Então, por curiosidade, eu testei e os resultados foram surpreendentes, especialmente para uma corda tão pequena.

Usando o seguinte código:

    System.Diagnostics.Stopwatch s = new System.Diagnostics.Stopwatch();

    var p = new { FirstName = "Bill", LastName = "Gates" };

    s.Start();
    Console.WriteLine("{0} {1}", p.FirstName, p.LastName);
    s.Stop();
    Console.WriteLine("Console.WriteLine(\"{0} {1}\", p.FirstName, p.LastName); took: " + s.ElapsedMilliseconds + "ms - " + s.ElapsedTicks + " ticks");

    s.Reset();
    s.Start();
    Console.WriteLine(p.FirstName + " " + p.LastName);
    s.Stop();

    Console.WriteLine("Console.WriteLine(p.FirstName + \" \" + p.LastName); took: " + s.ElapsedMilliseconds + "ms - " + s.ElapsedTicks + " ticks");

Eu obtive os seguintes resultados:

Bill Gates
Console.WriteLine("{0} {1}", p.FirstName, p.LastName); took: 2ms - 7280 ticks
Bill Gates
Console.WriteLine(p.FirstName + " " + p.LastName); took: 0ms - 67 ticks

O uso do método de formatação é 100 vezes mais lento !! A concatenação nem se registrou como 1 ms, e é por isso que eu mostro os tiques do timer também.

Adam Haile
fonte
2
Mas é claro que você deve executar a operação mais de uma vez para obter medições.
31710 Erikkallen
2
E perde a chamada para Console.Writeline (), pois está além do escopo da pergunta?
Aidanapword
você testou com um construtor de cordas? ;)
niico
6

A partir do C # 6.0, seqüências interpoladas podem ser usadas para fazer isso, o que simplifica ainda mais o formato.

var name = "Bill";
var surname = "Gates";
MessageBox.Show($"Welcome to the show, {name} {surname}!");

Uma expressão de cadeia interpolada se parece com uma cadeia de modelo que contém expressões. Uma expressão de cadeia interpolada cria uma cadeia de caracteres substituindo as expressões contidas pelas representações de ToString dos resultados das expressões.

As seqüências de caracteres interpoladas têm desempenho semelhante ao String.Format, mas melhoram a legibilidade e a sintaxe mais curta, devido ao fato de que valores e expressões são inseridos em linha.

Consulte também este artigo do dotnetperls sobre interpolação de strings.

Se você está procurando uma maneira padrão de formatar suas seqüências, isso faz sentido em termos de legibilidade e desempenho (exceto se microssegundos fará diferença no seu caso de uso específico).

Saragis
fonte
5

Para concatenação básica de strings, geralmente uso o segundo estilo - mais fácil de ler e mais simples. No entanto, se estou fazendo uma combinação de strings mais complicada, geralmente opto por String.Format.

String.Format economiza em muitas citações e vantagens ...

Console.WriteLine("User {0} accessed {1} on {2}.", user.Name, fileName, timestamp);
vs
Console.WriteLine("User " + user.Name + " accessed " + fileName + " on " + timestamp + ".");

Apenas alguns caracteres salvos, mas acho que, neste exemplo, o formato o torna muito mais limpo.

Mike
fonte
5

Um teste melhor seria observar sua memória usando o Perfmon e os contadores de memória CLR. Meu entendimento é que todo o motivo pelo qual você deseja usar String.Format, em vez de apenas concatenar cadeias, é que, como as cadeias são imutáveis, você está sobrecarregando desnecessariamente o coletor de lixo com cadeias temporárias que precisam ser recuperadas na próxima passagem.

StringBuilder e String.Format, embora potencialmente mais lentos, são mais eficientes em termos de memória.

O que há de tão ruim na concatenação de strings?

David Hill
fonte
Concordo; toda operação de string cria uma nova cópia da string. Toda essa memória será recuperada pelo coletor de lixo mais cedo ou mais tarde. Portanto, alocar muitas seqüências pode voltar a morder você mais tarde.
Marnix van Valen
5

Geralmente eu prefiro o primeiro, pois especialmente quando as cordas ficam longas, pode ser muito mais fácil de ler.

O outro benefício é que acredito que seja o desempenho, pois ele efetivamente executa duas instruções de criação de sequência antes de passar a sequência final para o método Console.Write. String.Format usa um StringBuilder sob as capas, acredito, para evitar várias concatenações.

Deve-se notar, no entanto, que se os parâmetros que você está transmitindo para String.Format (e outros métodos como o Console.Write) são tipos de valor, eles serão colocados em caixas antes da transmissão, o que pode fornecer seus próprios resultados de desempenho. Postagem de blog sobre isso aqui .

samjudson
fonte
1
Essa postagem do blog está agora em: jeffbarnes.net/blog/post/2006/08/08/… . Eu sofro de representante insuficiente para editar.
Richard Slater
5

Daqui a uma semana, a partir de 19 de agosto de 2015, esta pergunta terá exatamente sete (7) anos. Agora existe uma maneira melhor de fazer isso. Melhor em termos de manutenção, pois não realizei nenhum teste de desempenho em comparação com apenas concatenar seqüências de caracteres (mas isso importa hoje em dia? Alguns milissegundos de diferença?). A nova maneira de fazer isso com o C # 6.0 :

var p = new { FirstName = "Bill", LastName = "Gates" };
var fullname = $"{p.FirstName} {p.LastName}";

Esse novo recurso é melhor , IMO e, na verdade, melhor no nosso caso, pois temos códigos nos quais construímos cadeias de consulta cujos valores dependem de alguns fatores. Imagine uma querystring em que temos 6 argumentos. Então, em vez de fazer um, por exemplo:

var qs = string.Format("q1={0}&q2={1}&q3={2}&q4={3}&q5={4}&q6={5}", 
    someVar, anotherVarWithLongName, var3, var4, var5, var6)

in pode ser escrito assim e é mais fácil ler:

var qs=$"q1={someVar}&q2={anotherVarWithLongName}&q3={var3}&q4={var4}&q5={var5}&q6={var6}";
von v.
fonte
De fato, a nova maneira do C # 6.0 é melhor que as alternativas anteriores - pelo menos do ponto de vista da legibilidade.
Philippe
Está certo. E também é mais seguro, pois você não precisa se preocupar com qual objeto vai para qual índice (espaço reservado), pois você colocará diretamente os objetos onde deseja que estejam.
von v.
Aliás, ele realmente chama Format (pelo menos com Roslyn).
Philippe
BTW, o que este pôster está se referindo é chamado de "interpolação de string" e é abordado em outra parte deste segmento.
CShark 26/05
4
  1. A formatação é a maneira ".NET" de fazer isso. Certas ferramentas de refatoração (Refactor! For one) até proporão refatorar o código no estilo concat para usar o estilo de formatação.
  2. A formatação é mais fácil de otimizar para o compilador (embora o segundo provavelmente seja refatorado para usar o método 'Concat' que é rápido).
  3. A formatação geralmente é mais clara de se ler (especialmente com formatação "sofisticada").
  4. Formatação significa chamadas implícitas para '.ToString' em todas as variáveis, o que é bom para facilitar a leitura.
  5. De acordo com “C # efetivo”, as implementações .NET 'WriteLine' e 'Format' estão desarrumadas, elas automaticamente importam todos os tipos de valor (o que é ruim). “C # eficaz” aconselha a executar chamadas '.ToString' explicitamente, o que IMHO é falso (veja a publicação de Jeff )
  6. No momento, as dicas de tipo de formatação não são verificadas pelo compilador, resultando em erros de tempo de execução. No entanto, isso pode ser alterado em versões futuras.
Konrad Rudolph
fonte
4

Eu escolho com base na legibilidade. Eu prefiro a opção de formato quando há algum texto em torno das variáveis. Neste exemplo:

Console.WriteLine("User {0} accessed {1} on {2}.", 
                   user.Name, fileName, timestamp);

você entende o significado mesmo sem nomes de variáveis, enquanto o concat está cheio de aspas e sinais de + e confunde meus olhos:

Console.WriteLine("User " + user.Name + " accessed " + fileName + 
                  " on " + timestamp + ".");

(Emprestei o exemplo de Mike porque eu gosto)

Se a string de formato não significa muito sem nomes de variáveis, tenho que usar concat:

   Console.WriteLine("{0} {1}", p.FirstName, p.LastName);

A opção de formato me faz ler os nomes das variáveis ​​e mapeá-los para os números correspondentes. A opção concat não exige isso. Ainda estou confuso com as aspas e os sinais +, mas a alternativa é pior. Rubi?

   Console.WriteLine(p.FirstName + " " + p.LastName);

Em termos de desempenho, espero que a opção de formato seja mais lenta que a concat, pois o formato requer que a string seja analisada . Não me lembro de ter que otimizar esse tipo de instrução, mas, se o fizesse, examinaria stringmétodos como Concat()e Join().

A outra vantagem do formato é que a string de formato pode ser colocada em um arquivo de configuração. Muito útil com mensagens de erro e texto da interface do usuário.

DonkeyMaster
fonte
4

Eu usaria o String.Format, mas também teria a string de formato nos arquivos de recursos para que ela possa ser localizada em outros idiomas. O uso de uma simples concat de strings não permite que você faça isso. Obviamente, se você nunca precisar localizar essa string, esse não é um motivo para pensar. Realmente depende do objetivo da string.

Se ele for mostrado ao usuário, eu usaria String.Format para localizar se precisar - e o FxCop fará a verificação ortográfica para mim, apenas no caso :)

Se ele contiver números ou qualquer outra coisa que não seja uma string (por exemplo, datas), eu usaria String.Format porque isso me dá mais controle sobre a formatação .

Se for para criar uma consulta como SQL, eu usaria o Linq .

Se para concatenar seqüências de caracteres dentro de um loop, eu usaria o StringBuilder para evitar problemas de desempenho.

Se for para alguma saída, o usuário não verá, e não afetará o desempenho, eu usaria String.Format porque tenho o hábito de usá-lo de qualquer maneira e estou acostumado a isso :)

Wilka
fonte
3

Se você está lidando com algo que precisa ser fácil de ler (e isso é mais código), eu continuaria com a versão de sobrecarga do operador, A menos que:

  • O código precisa ser executado milhões de vezes
  • Você está fazendo toneladas de concats (mais de 4 é uma tonelada)
  • O código é direcionado para o Compact Framework

Sob pelo menos duas dessas circunstâncias, eu usaria o StringBuilder.

plinto
fonte
3

Se você deseja localizar o resultado, String.Format é essencial porque diferentes idiomas naturais podem até não ter os dados na mesma ordem.

Christian Hayter
fonte
2

Eu acho que isso depende muito de quão complexa é a saída. Costumo escolher o cenário que melhor funciona no momento.

Escolha a ferramenta certa com base no trabalho: D O que parecer mais limpo!

mercutio
fonte
2

Prefiro o segundo também, mas não tenho argumentos racionais no momento para apoiar essa posição.

Mandril
fonte
2

Agradável!

Acabei de adicionar

        s.Start();
        for (var i = 0; i < n; i++)
            result = string.Concat(p.FirstName, " ", p.LastName);
        s.Stop();
        ceElapsedMilliseconds = s.ElapsedMilliseconds;
        ceElapsedTicks = s.ElapsedTicks;
        s.Reset();

E é ainda mais rápido (acho que string.Concat é chamado nos dois exemplos, mas o primeiro requer algum tipo de tradução).

1000000 x result = string.Format("{0} {1}", p.FirstName, p.LastName); took: 249ms - 3571621 ticks
1000000 x result = (p.FirstName + " " + p.LastName); took: 65ms - 944948 ticks
1000000 x result = string.Concat(p.FirstName, " ", p.LastName); took: 54ms - 780524 ticks
Philippe
fonte
2
Leva exatamente a mesma quantidade de tempo desde que as concatenações de seqüência de caracteres baseadas no operador são convertidas pelo compilador nas chamadas para string.Concat(...). Isso é feito durante a compilação, portanto, não afeta o desempenho em tempo de execução. Se você executar seus testes várias vezes ou executá-los em uma amostra maior, verá que eles são idênticos.
Allon Guralnek
2

Como não acho que as respostas aqui abranjam tudo, gostaria de fazer uma pequena adição aqui.

Console.WriteLine(string format, params object[] pars) chamadas string.Format . O '+' implica concatenação de string. Eu não acho que isso sempre tenha a ver com estilo; Eu costumo misturar os dois estilos, dependendo do contexto em que estou.

Resposta curta

A decisão que você está enfrentando tem a ver com a alocação de string. Vou tentar simplificar.

Diga que você tem

string s = a + "foo" + b;

Se você executar isso, ele avaliará da seguinte maneira:

string tmp1 = a;
string tmp2 = "foo" 
string tmp3 = concat(tmp1, tmp2);
string tmp4 = b;
string s = concat(tmp3, tmp4);

tmpaqui não é realmente uma variável local, mas é temporária para o JIT (é empurrada na pilha IL). Se você pressionar uma string na pilha (como ldstrem IL para literais), você colocará uma referência a um ponteiro de string na pilha.

O momento em que você chama concatessa referência se torna um problema, porque não há nenhuma referência de string disponível que contenha as duas strings. Isso significa que o .NET precisa alocar um novo bloco de memória e preenchê-lo com as duas seqüências de caracteres. A razão pela qual isso é um problema é porque a alocação é relativamente cara.

O que muda a pergunta para: Como você pode reduzir o número de concatoperações?

Portanto, a resposta aproximada é: string.Formatpara> 1 concats, '+' funcionará bem para 1 concat. E se você não se importa em fazer otimizações de micro-desempenho, string.Formatfuncionará bem no caso geral.

Uma nota sobre Cultura

E depois há algo chamado cultura ...

string.Formatpermite usar CultureInfona sua formatação. Um operador simples '+' usa a cultura atual.

Esta é uma observação especialmente importante se você estiver escrevendo formatos de arquivo e f.ex. doublevalores que você 'adiciona' a uma sequência. Em máquinas diferentes, você pode acabar com cadeias diferentes se não usar string.Formatcom um explícito CultureInfo.

F.ex. considere o que acontece se você alterar um '.' para um ',' enquanto escreve seu arquivo de valores separados por vírgula ... em holandês, o separador decimal é uma vírgula; portanto, o usuário pode ter uma surpresa 'engraçada'.

Resposta mais detalhada

Se você não souber o tamanho exato da string de antemão, é melhor usar uma política como essa para generalizar os buffers que você usa. O espaço livre é preenchido primeiro, após o qual os dados são copiados.

Crescer significa alocar um novo bloco de memória e copiar os dados antigos para o novo buffer. O antigo bloco de memória pode ser liberado. Você obtém os resultados neste ponto: crescer é uma operação cara.

A maneira mais prática de fazer isso é usar uma política de alocação geral. A política mais comum é alocar buffers em potências de 2. É claro que você precisa fazê-lo um pouco mais inteligente do que isso (já que não faz sentido aumentar de 1,2,4,8 se você já sabe que precisa de 128 caracteres ) mas você entendeu. A política garante que você não precise de muitas das operações caras que descrevi acima.

StringBuilderé uma classe que basicamente aloca o buffer subjacente em potências de dois. string.Formatusa StringBuildersob o capô.

Isso faz da sua decisão uma troca básica entre atribuir globalmente e acrescentar (múltiplos) (com duas culturas) ou apenas alocar e acrescentar.

atlaste
fonte
1

Pessoalmente, o segundo como tudo o que você está usando está na ordem direta em que será emitido. Considerando que com o primeiro você precisa combinar o {0} e o {1} com o var apropriado, o que é fácil de bagunçar.

Pelo menos não é tão ruim quanto o sprintf C ++, onde se você errar o tipo de variável, tudo explodirá.

Além disso, como o segundo está todo em linha e não precisa pesquisar e substituir todas as coisas {0}, o último deve ser mais rápido ... embora eu não tenha certeza.

Adam Haile
fonte
1

Na verdade, gosto da primeira, porque quando há muitas variáveis ​​misturadas com o texto, parece mais fácil ler para mim. Além disso, é mais fácil lidar com aspas ao usar o formato string.Format (), uh. Aqui está uma análise decente da concatenação de strings.

adparadox
fonte
1

Eu sempre fui a rota string.Format (). Ser capaz de armazenar formatos em variáveis ​​como o exemplo de Nathan é uma grande vantagem. Em alguns casos, posso anexar uma variável, mas mais de uma variável está sendo concatenada, refatoro para usar a formatação.

Scott Muc
fonte
1

Ah, e apenas para completar, a seguir estão alguns ticks mais rápidos que a concatenação normal:

Console.WriteLine(String.Concat(p.FirstName," ",p.LastName));
samjudson
fonte
1

O primeiro (formato) parece melhor para mim. É mais legível e você não está criando objetos de string temporários extras.

Rismo
fonte
1

Fiquei curioso para saber onde StringBuilder estava com esses testes. Resultados abaixo ...

class Program {
   static void Main(string[] args) {

      var p = new { FirstName = "Bill", LastName = "Gates" };

      var tests = new[] {
         new { Name = "Concat", Action = new Action(delegate() { string x = p.FirstName + " " + p.LastName; }) },
         new { Name = "Format", Action = new Action(delegate() { string x = string.Format("{0} {1}", p.FirstName, p.LastName); }) },
         new { Name = "StringBuilder", Action = new Action(delegate() {
            StringBuilder sb = new StringBuilder();
            sb.Append(p.FirstName);
            sb.Append(" ");
            sb.Append(p.LastName);
            string x = sb.ToString();
         }) }
      };

      var Watch = new Stopwatch();
      foreach (var t in tests) {
         for (int i = 0; i < 5; i++) {
            Watch.Reset();
            long Elapsed = ElapsedTicks(t.Action, Watch, 10000);
            Console.WriteLine(string.Format("{0}: {1} ticks", t.Name, Elapsed.ToString()));
         }
      }
   }

   public static long ElapsedTicks(Action ActionDelg, Stopwatch Watch, int Iterations) {
      Watch.Start();
      for (int i = 0; i < Iterations; i++) {
         ActionDelg();
      }
      Watch.Stop();
      return Watch.ElapsedTicks / Iterations;
   }
}

Resultados:

Concat: 406 carrapatos
Concat: 356 carrapatos
Concat: 411 ticks
Concat: 299 ticks
Concat: 266 carrapatos
Formato: 5269 ticks
Formato: 954 ticks
Formato: 1004 ticks
Formato: 984 ticks
Formato: 974 ticks
StringBuilder: 629 ticks
StringBuilder: 484 ticks
StringBuilder: 482 ticks
StringBuilder: 508 ticks
StringBuilder: 504 ticks
spoulson
fonte
1

De acordo com o material de preparação do MCSD, a Microsoft sugere o uso do operador + ao lidar com um número muito pequeno de concatenações (provavelmente 2 a 4). Ainda não sei por que, mas é algo a considerar.

Babak Naffas
fonte