
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
intvalor1 = 300;
int valor2 = 600;
intvalor3 = 900;
stringvalor;
// 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 staticvoid Main()
{
// System.Int32 ocupa 4 Bytes somente na stack
intvalor1 = 300;
intvalor2 = 600;
intvalor3 = 900;
// System.Text.StringBuilder equivale a um System.IntPtr na stack. Este valor varia de acordo com a plataforma
System.Text.StringBuilder sb = newStringBuilder();
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*/
stringvalor = 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
Thiago Anselme - Gerente de TI - Arquiteto de Soluções
Ele atua/atuou como Dev Full Stack C# .NET / Angular / Kubernetes e afins. Ele possui certificações Microsoft MCTS (6x), MCPD em Web, ITIL v3 e CKAD (Kubernetes) . Thiago é apaixonado por tecnologia, entusiasta de TI desde a infância bem como amante de aprendizado contínuo.