Uma String é um tipo de referência, mesmo que possua a maioria das características de um tipo de valor, como imutável e ter == sobrecarregado para comparar o texto em vez de garantir que eles façam referência ao mesmo objeto.
Por que string não é apenas um tipo de valor?
c#
string
clr
value-type
reference-type
Davy8
fonte
fonte
is
testes de lado), a resposta é provavelmente "por razões históricas". O desempenho da cópia não pode ser o motivo, pois não há necessidade de copiar fisicamente objetos imutáveis. Agora é impossível mudar sem quebrar o código que realmente usais
verificações (ou restrições semelhantes).std::string
comportar como uma coleção é um erro antigo que não pode ser corrigido agora.Respostas:
Strings não são tipos de valor, pois podem ser enormes e precisam ser armazenadas na pilha. Os tipos de valor são (em todas as implementações do CLR ainda) armazenados na pilha. Seqüências de alocação de pilha quebrariam todos os tipos de coisas: a pilha é de apenas 1 MB para 32 bits e 4 MB para 64 bits, você teria que encaixotar cada sequência, incorrendo em uma penalidade de cópia, não seria possível estender seqüências e uso de memória balão, etc ...
(Edit: Esclarecimentos adicionados sobre o armazenamento de tipo de valor ser um detalhe de implementação, o que leva a essa situação em que temos um tipo com semântica de valor que não é herdado de System.ValueType. Obrigado, Ben.)
fonte
String
não é tamanho variável. Quando você adiciona, na verdade você está criando outroString
objeto, alocando nova memória para ele.Int32
é sempre 4 bytes, portanto, o compilador aloca 4 bytes sempre que você define uma variável de sequência. Quanta memória o compilador deve alocar quando encontra umaint
variável (se fosse um tipo de valor)? Entenda que o valor ainda não foi atribuído naquele momento.Int32
é sempre 4 bytes, portanto, o compilador aloca 4 bytes sempre que você define umaint
variável. Quanta memória o compilador deve alocar quando encontra umastring
variável (se fosse um tipo de valor)? Entenda que o valor ainda não foi atribuído naquele momento.Não é um tipo de valor, porque o desempenho (espaço e tempo!) Seria terrível se fosse um tipo de valor e seu valor tivesse que ser copiado toda vez que fosse passado e retornado de métodos, etc.
Tem semântica de valor para manter o mundo são. Você pode imaginar o quão difícil seria codificar se
definido
b
para serfalse
? Imagine o quão difícil a codificação seria para qualquer aplicativo.fonte
new String("foo");
outrasnew String("foo")
podem avaliar na mesma referência, o que não é o que você espera que umnew
operador faça. (Ou você pode me dizer um caso em que eu iria querer comparar as referências?)ReferenceEquals(x, y)
é um teste rápido e você pode retornar 0 imediatamente e, quando misturado com o teste nulo, nem adiciona mais trabalho.string
poderia se comportar como uma string vazia (como era nos sistemas pré-.net), e não como uma referência nula. Na verdade, minha preferência seria ter um tipo de valorString
que contivesse um tipo de referênciaNullableString
, com o primeiro tendo um valor padrão equivalenteString.Empty
e o segundo com o padrãonull
e com regras especiais de boxe / unboxing (como valorizadoNullableString
daria uma referência aString.Empty
).A distinção entre tipos de referência e tipos de valor é basicamente uma troca de desempenho no design do idioma. Os tipos de referência têm alguma sobrecarga na construção e destruição e coleta de lixo, porque são criados no heap. Os tipos de valor, por outro lado, têm sobrecarga nas chamadas de método (se o tamanho dos dados for maior que um ponteiro), porque todo o objeto é copiado e não apenas um ponteiro. Como as strings podem ser (e geralmente são) muito maiores que o tamanho de um ponteiro, elas são projetadas como tipos de referência. Além disso, como Servy apontou, o tamanho de um tipo de valor deve ser conhecido em tempo de compilação, o que nem sempre é o caso para seqüências de caracteres.
A questão da mutabilidade é uma questão separada. Os tipos de referência e os tipos de valor podem ser mutáveis ou imutáveis. Os tipos de valor são tipicamente imutáveis, pois a semântica para tipos de valor mutável pode ser confusa.
Os tipos de referência geralmente são mutáveis, mas podem ser projetados como imutáveis, se fizer sentido. As strings são definidas como imutáveis porque possibilitam certas otimizações. Por exemplo, se a mesma string literal ocorrer várias vezes no mesmo programa (o que é bastante comum), o compilador poderá reutilizar o mesmo objeto.
Então, por que "==" está sobrecarregado para comparar seqüências de caracteres por texto? Porque é a semântica mais útil. Se duas seqüências de caracteres forem iguais ao texto, elas podem ou não ser a mesma referência de objeto devido às otimizações. Portanto, comparar referências é bastante inútil, enquanto comparar texto é quase sempre o que você deseja.
Falando de maneira mais geral, Strings tem o que é chamado de semântica de valores . Esse é um conceito mais geral que os tipos de valor, que é um detalhe de implementação específico do C #. Os tipos de valor têm semântica de valor, mas os tipos de referência também podem ter semântica de valor. Quando um tipo tem semântica de valor, você não pode realmente dizer se a implementação subjacente é um tipo de referência ou tipo de valor; portanto, você pode considerar isso um detalhe da implementação.
fonte
string
tipo precisaria ter um buffer de caracteres de algum tamanho fixo, o que seria restritivo e altamente ineficiente.Essa é uma resposta tardia a uma pergunta antiga, mas todas as outras respostas estão erradas, o .NET não tinha genéricos até o .NET 2.0 em 2005.
String
é um tipo de referência em vez de um tipo de valor, porque era de importância crucial para a Microsoft garantir que as seqüências pudessem ser armazenadas da maneira mais eficiente em coleções não genéricas , comoSystem.Collections.ArrayList
.Armazenar um tipo de valor em uma coleção não genérica requer uma conversão especial para o tipo
object
chamado boxe. Quando o CLR caixa um tipo de valor, ele agrupa o valor dentro deSystem.Object
e armazena-o no heap gerenciado.A leitura do valor da coleção requer a operação inversa, que é chamada de unboxing.
Tanto o boxe como o unboxing têm um custo não negligenciável: o boxe requer uma alocação adicional, o unboxing requer verificação de tipo.
Algumas respostas afirmam incorretamente que
string
nunca poderiam ter sido implementadas como um tipo de valor porque seu tamanho é variável. Na verdade, é fácil implementar cadeias de caracteres como uma estrutura de dados de comprimento fixo usando uma estratégia de Otimização de pequenas cadeias: as cadeias seriam armazenadas na memória diretamente como uma sequência de caracteres Unicode, exceto as cadeias grandes que seriam armazenadas como ponteiro para um buffer externo. Ambas as representações podem ser projetadas para ter o mesmo comprimento fixo, ou seja, o tamanho de um ponteiro.Se os genéricos existissem desde o primeiro dia, acho que ter uma string como um tipo de valor provavelmente teria sido uma solução melhor, com semântica mais simples, melhor uso de memória e melhor localidade de cache. Uma
List<string>
contendo apenas pequenas seqüências poderia ter sido um único bloco de memória contíguo.fonte
string
contém apenas seu tamanho e um ponteiro para achar
matriz, portanto, não seria um "tipo de valor enorme". Mas esse é um motivo simples e relevante para essa decisão de design. Obrigado!Não apenas as strings são tipos de referência imutáveis. Delegados multi-elenco também. É por isso que é seguro escrever
Suponho que as strings sejam imutáveis porque esse é o método mais seguro para trabalhar com elas e alocar memória. Por que eles não são tipos de valor? Os autores anteriores têm razão quanto ao tamanho da pilha, etc. Eu também acrescentaria que tornar as seqüências de caracteres tipos de referência permite economizar no tamanho da montagem quando você usa a mesma sequência constante no programa. Se você definir
Provavelmente, as duas instâncias da constante "minha string" serão alocadas no seu assembly apenas uma vez.
Se você deseja gerenciar seqüências de caracteres como o tipo de referência usual, coloque a sequência dentro de um novo StringBuilder (string s). Ou use MemoryStreams.
Se você deseja criar uma biblioteca, na qual espera que uma cadeia enorme seja passada em suas funções, defina um parâmetro como um StringBuilder ou como um Stream.
fonte
Além disso, a maneira como as strings são implementadas (diferentes para cada plataforma) e quando você começa a costurá-las. Como usar um
StringBuilder
. Ele aloca um buffer para você copiar; assim que chega ao fim, ele aloca ainda mais memória para você, na esperança de que, se você fizer um grande desempenho de concatenação, não seja prejudicado.Talvez Jon Skeet possa ajudar aqui em cima?
fonte
É principalmente uma questão de desempenho.
O fato de as seqüências se comportarem como o tipo de valor ajuda na escrita do código, mas, se este for um tipo de valor, o desempenho será enorme.
Para uma visão aprofundada, dê uma olhada em um bom artigo sobre strings na estrutura .net.
fonte
Em palavras muito simples, qualquer valor que tenha um tamanho definido pode ser tratado como um tipo de valor.
fonte
Como você pode dizer que
string
é um tipo de referência? Não tenho certeza de que importa como é implementado. As seqüências de caracteres em C # são imutáveis exatamente para que você não precise se preocupar com esse problema.fonte
Na verdade, as strings têm muito poucas semelhanças com os tipos de valor. Para iniciantes, nem todos os tipos de valor são imutáveis, você pode alterar o valor de um Int32 o quanto quiser e ele ainda seria o mesmo endereço na pilha.
As strings são imutáveis por um motivo muito bom, não tem nada a ver com ser um tipo de referência, mas tem muito a ver com o gerenciamento de memória. É apenas mais eficiente criar um novo objeto quando o tamanho da string mudar do que mudar as coisas na pilha gerenciada. Eu acho que você está misturando tipos de valor / referência e conceitos de objetos imutáveis.
No que diz respeito a "==": Como você disse, "==" é uma sobrecarga de operador e, novamente, foi implementada por uma boa razão para tornar a estrutura mais útil ao trabalhar com strings.
fonte
Não é tão simples quanto Strings são compostas de matrizes de caracteres. Eu vejo as strings como uma matriz de caracteres []. Portanto, eles estão na pilha porque o local da memória de referência é armazenado na pilha e aponta para o início da localização da memória da matriz na pilha. O tamanho da string não é conhecido antes de ser alocado ... perfeito para o heap.
É por isso que uma string é realmente imutável, porque quando você a altera, mesmo que seja do mesmo tamanho, o compilador não sabe disso e precisa alocar uma nova matriz e atribuir caracteres às posições na matriz. Faz sentido se você pensar em strings como uma maneira de as linguagens protegê-lo de ter que alocar memória rapidamente (leia C como programação)
fonte
Correndo o risco de obter mais um voto negativo misterioso ... o fato de muitos mencionarem a pilha e a memória em relação aos tipos de valor e tipos primitivos é porque eles devem caber em um registro no microprocessador. Você não pode enviar ou empurrar algo da / para a pilha se ela precisar de mais bits do que um registro possui .... as instruções são, por exemplo, "pop eax" - porque eax tem 32 bits de largura em um sistema de 32 bits.
Os tipos primitivos de ponto flutuante são manipulados pela FPU, com 80 bits de largura.
Tudo isso foi decidido muito antes de haver uma linguagem OOP para ofuscar a definição de tipo primitivo e presumo que tipo de valor seja um termo criado especificamente para linguagens OOP.
fonte