Pergunta

No .NET, strings são imutáveis ​​e são variáveis ​​de tipo de referência.Isso geralmente é uma surpresa para os desenvolvedores .NET mais recentes, que podem confundi-los com objetos de tipo de valor devido ao seu comportamento.No entanto, além da prática de usar StringBuilder para concatenação longa esp.em loops, existe alguma razão na prática para que seja necessário conhecer essa distinção?

Quais cenários do mundo real são ajudados ou evitados pela compreensão da distinção de referência de valor em relação a strings .NET vs.apenas fingindo/entendendo que eles são tipos de valor?

Foi útil?

Solução

O projeto de strings foi deliberadamente tal que você não deveria se preocupar muito com isso como programador.Em muitas situações, isso significa que você pode simplesmente atribuir, mover, copiar, alterar strings sem pensar muito nas possíveis consequências intrincadas se outra referência à sua string existisse e fosse alterada ao mesmo tempo (como acontece com referências de objeto).

Parâmetros de string em uma chamada de método

(EDITAR:esta seção adicionada posteriormente)
Quando strings são passadas para um método, elas são passadas por referência.Quando eles são lidos apenas no corpo do método, nada de especial acontece.Mas quando elas são alteradas, uma cópia é criada e a variável temporária é usada no restante do método.Este processo é chamado copiar na gravação.

O que incomoda os juniores é que eles estão acostumados com o fato de que os objetos são referências e são alterados em um método que altera o parâmetro passado.Para fazer o mesmo com strings, eles precisam usar o ref palavra-chave.Na verdade, isso permite que a referência da string seja alterada e retornada para a função de chamada.Caso contrário, a string não poderá ser alterada pelo corpo do método:

void ChangeBad(string s)      { s = "hello world"; }
void ChangeGood(ref string s) { s = "hello world"; }

// in calling method:
string s1 = "hi";
ChangeBad(s1);       // s1 remains "hi" on return, this is often confusing
ChangeGood(ref s1);  // s1 changes to "hello world" on return

No StringBuilder

Essa distinção é importante, mas geralmente é melhor para programadores iniciantes não saberem muito sobre isso.Usando StringBuilder quando você faz muita "construção" de strings é bom, mas muitas vezes, sua aplicação terá muito mais peixes para fritar e o pequeno ganho de desempenho de StringBuilder é insignificante.Desconfie de programadores que dizem isso todos a manipulação de strings deve ser feita usando StringBuilder.

Como regra geral:StringBuilder tem algum custo de criação, mas anexar é barato.String tem um custo de criação barato, mas a concatenação é relativamente cara. O ponto de viragem é em torno de 400-500 concatenações, dependendo do tamanho:depois disso, o StringBuilder se torna mais eficiente.

Mais sobre StringBuilder vs desempenho de string

EDITAR:com base em um comentário de Konrad Rudolph, adicionei esta seção.

Se a regra anterior faz você se perguntar, considere as seguintes explicações um pouco mais detalhadas:

  • StringBuilder com muitos anexos de string pequenos ultrapassa a concatenação de strings rapidamente (30, 50 acréscimos), mas em 2 µs, mesmo o ganho de desempenho de 100% é frequentemente insignificante (seguro para algumas situações raras);
  • StringBuilder com alguns anexos de string grandes (80 caracteres ou strings maiores) ultrapassa a concatenação de string somente após milhares, às vezes centésimos de milhares de iterações e a diferença geralmente é de apenas algumas porcentagens;
  • A mistura de ações de string (substituir, inserir, substring, regex etc.) geralmente torna o uso de StringBuilder ou concatenação de string igual;
  • A concatenação de constantes de strings pode ser otimizada pelo compilador, pelo CLR ou pelo JIT, mas não pelo StringBuilder;
  • O código geralmente mistura concatenação +, StringBuilder.Append, String.Format, ToString e outras operações de string, usar StringBuilder nesses casos dificilmente é eficaz.

Então quando é é eficiente?Nos casos em que muitas strings pequenas são anexadas, ou seja, para serializar dados em um arquivo, por exemplo, e quando você não precisa alterar os dados "gravados" uma vez "gravados" no StringBuilder.E nos casos em que muitos métodos precisam acrescentar algo, porque StringBuilder é um tipo de referência e as strings são copiadas quando são alteradas.

Em cordas internadas

Surge um problema - não apenas com programadores juniores - quando eles tentam fazer uma comparação de referência e descobrem que às vezes o resultado é verdadeiro, e às vezes é falso, aparentemente nas mesmas situações.O que aconteceu?Quando as strings foram internadas pelo compilador e adicionadas ao conjunto de strings interno estático global, a comparação entre duas strings pode apontar para o mesmo endereço de memória.Quando (referência!) Comparar duas strings iguais, uma internada e outra não, resultará em falso.Usar = comparação, ou Equals e não brinque com ReferenceEquals ao lidar com cordas.

Em String.Empty

Na mesma liga se enquadra um comportamento estranho que às vezes ocorre ao usar String.Empty:a estática String.Empty é sempre internado, mas uma variável com um valor atribuído não é.No entanto, por padrão, o compilador atribuirá String.Empty e aponte para o mesmo endereço de memória.Resultado:uma variável de string mutável, quando comparada com ReferenceEquals, retorna verdadeiro, embora você possa esperar falso.

// emptiness is treated differently:
string empty1 = String.Empty;
string empty2 = "";
string nonEmpty1 = "something";
string nonEmpty2 = "something";

// yields false (debug) true (release)
bool compareNonEmpty = object.ReferenceEquals(nonEmpty1, nonEmpty2);

// yields true (debug) false (release, depends on .NET version and how it's assigned)
bool compareEmpty = object.ReferenceEquals(empty1, empty2);

Em profundidade

Você basicamente perguntou sobre quais situações podem ocorrer aos não iniciados.Acho que meu objetivo se resume a evitar object.ReferenceEquals porque não é confiável quando usado com strings.O motivo é que a string interna é usada quando a string é constante no código, mas nem sempre.Você não pode confiar nesse comportamento.No entanto String.Empty e "" estão sempre internados, não é quando o compilador acredita que o valor é mutável.Diferentes opções de otimização (depuração vs lançamento e outras) produzirão resultados diferentes.

Quando fazer você precisa ReferenceEquals de qualquer forma?Com objetos faz sentido, mas com strings não.Ensine qualquer pessoa que trabalhe com strings a evitar seu uso, a menos que também entenda unsafe e objetos fixados.

Desempenho

Quando o desempenho é importante, você pode descobrir que as strings são na verdade não imutável e que usando StringBuilder é não sempre a abordagem mais rápida.

Muitas das informações que usei aqui são detalhadas neste excelente artigo sobre strings, junto com um "como fazer" para manipular strings no local (strings mutáveis).

Atualizar: exemplo de código adicionado
Atualizar: adicionada seção 'em profundidade' (espero que alguém ache isso útil;)
Atualizar: adicionou alguns links, adicionou seção sobre parâmetros de string
Atualizar: estimativa adicionada de quando mudar de strings para stringbuilder
Atualizar: adicionou uma seção extra sobre desempenho de StringBuilder vs String, após um comentário de Konrad Rudolph

Outras dicas

A única distinção que realmente importa para a maioria dos códigos é o fato de que null pode ser atribuído a variáveis ​​de string.

Uma classe imutável atua como um tipo de valor em todas as situações comuns, e você pode programar bastante sem se importar muito com a diferença.

É quando você se aprofunda um pouco mais e se preocupa com o desempenho que você tem uma utilidade real para a distinção.Por exemplo, saber que embora passar uma string como parâmetro para um método atue como se uma cópia da string fosse criada, a cópia na verdade não ocorre.Isso pode ser uma surpresa para pessoas acostumadas com linguagens onde strings são realmente tipos de valor (como VB6?), e passar muitas strings como parâmetros não seria bom para o desempenho.

String é uma raça especial.Eles são do tipo de referência, mas usados ​​pela maioria dos codificadores como um tipo de valor.Ao torná-lo imutável e usar o pool interno, otimiza o uso de memória, que será enorme se for um tipo de valor puro.

Mais leituras aqui:
O objeto C# .NET String é realmente por referência?em SO
Método String.Intern no MSDN
string (referência C#) no MSDN

Atualizar:
Consulte abelcomentário para esta postagem.Ele corrigiu minha declaração enganosa.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top