Os tipos por
referência ocupam espaço duas vezes. Uma na stack
e outra na heap. Na stack existe um
apontamento para o dado correspondente na heap. Se estes tipos por referência
forem destruídos, apenas seu ponteiro é destruído. O Garbage Collector percebe os dados na heap que não possuem
contra-referências na stack e o excluem na sua limpeza.
Os tipos por
referência são especialmente úteis. Eles são responsáveis por guardar tipos
mais complexos como classes. Estes tipos normalmente ocupam grande espaço em
memória. Quando um tipo por referência é utilizado como parâmetro para um
método, este parâmetro aponta para o tipo por referência. E quando um tipo por
referência é atribuído por outro, existe um apontamento interno, diferentemente
do tipo por valor. Isto é de grande valia para objetos pesados.
Os tipos por referência mais fundamentais
são:
- System.Object
- System.String
- System.Text.StringBuilder
- System.Array
- System.IO.Stream
- System.Exception
Por que utilizar StringBuilder a string?
As strings são
classes prontas do .NET para tratamento de arrays de caracteres. Enquanto um
char[] utiliza a classe System.Array, um string utiliza a classe System.String.
A classe System.String se especializa no tratamento de array de caracteres
trazendo uma série de benefícios na sua manipulação.
As strings em
.NET e em outras linguagens apresentam uma limitação: as strings são imutáveis.
Isto significa que é impossível alterar uma string em .NET. Esta afirmativa é
estranha por que é comum a alteração de string, porém internamente a CLR
realiza uma manobra para que isto funcione corretamente. Quando uma string é
definida, uma posição de memória é alocada na stack para apontar para um
endereço da heap que conterá a string. Se esta string for alterada, uma nova entrada
será feita na stack e outra cópia será feita na heap. E pelo menos o .NET apaga
o endereço de memória da stack que contém a antiga string. Desta forma na
próxima passagem do Garbage Collector, todas as strings antigas serão
excluídas. Veja o código – comentado – a seguir que mostra este problema:
public static void Main()
{
// System.Int32 ocupa 4 Bytes somente na stack
int
valor1 = 300;
int valor2 =
600;
int
valor3 = 900;
string
valor;
// System.String ocupa o equivalente a um System.IntPtr na stack.
//Este valor varia de acordo com a plataforma
/* Como
este valor não está instanciado, apenas ocupa a stack, e não aponta para a heap
*/
valor = "Esta
string é um teste";
// Um novo espaço (System.IntPtr) é ocupado na stack
/* O
espaço antigo não é anulado (embora já estivesse)*/
/* A heap
guarda o tamanho da string mais outras partes da classe System.String*/
valor += "\nO
valor 1 é de " + valor1;
// Um novo espaço (System.IntPtr) é ocupado na stack
/* O
espaço antigo não é anulado*/
/* A heap
guarda o tamanho da string mais outras partes da classe System.String*/
valor += "\nO
valor 2 é de " + valor2;
// Um novo espaço (System.IntPtr) é ocupado na stack
/* O
espaço antigo não é anulado*/
/* A heap
guarda o tamanho da string mais outras partes da classe System.String*/
valor += "\nO
valor 3 é de " + valor3;
// Um novo espaço (System.IntPtr) é ocupado na stack
/* O
espaço antigo não é anulado*/
/* A heap
guarda o tamanho da string mais outras partes da classe System.String*/
//
Imprime na tela o valor da string
Console.Write(valor);
}
A aplicação citada gasta:
- 3 System.Int32
- 4 Bytes cada
- 12 Bytes da stack
- Gastos corretamente
- 5 System.IntPrt
- O valor varia de acordo com a
plataforma
- Gasta no mínimo 5 bytes da
stack
- Os 5 System.IntPrt representam
indiretamente a mesma System.String
- 5 System.String
Agora, tendo o problema bem claro, então
qual é a solução? O .NET oferece um objeto que resolve esta questão:
System.Text.StringBuilder. Diferentemente da string, ele tem a função de montar
strings e, apenas, uma string é criada na memória após uma manipulação de
string. Veja o código que segue, ele resolve o problema apresentado no código
anterior:
public static
void Main()
{
// System.Int32 ocupa 4 Bytes somente na stack
int
valor1 = 300;
int
valor2 = 600;
int
valor3 = 900;
// System.Text.StringBuilder equivale
a um System.IntPtr na stack. Este valor varia de acordo com a plataforma
System.Text.StringBuilder sb = new
StringBuilder();
sb.Append("Esta string é um teste");
sb.Append("\nO
valor 1 é de " + valor1);
sb.Append("\nO
valor 2 é de " + valor2);
sb.Append("\nO
valor 3 é de " + valor3);
// Um novo espaço (System.IntPtr) é ocupado na stack
/* A heap
guarda o tamanho da string mais outras partes da classe System.String*/
string
valor = sb.ToString();
//
Imprime na tela o valor da string
Console.Write(valor);
}
Desta forma, o resumo dos gastos são:
- 3 System.Int32
- 4 Bytes cada
- 12 Bytes da stack
- Gastos corretamente
- 2 System.IntPtr
- O valor varia de acordo com a
plataforma
- 1 para apontar para um
System.String, criado corretamente
- 1 para apontar para um System.Text.StringBuilder,
criado corretamente
- 1 System.String
- 1 System.Text.StringBuilder