Olá, tudo bem? Eu sou o Daniel Artine e serei seu instrutor neste curso de Gerenciamento de Memória com .NET da Alura!
Audiodescrição: Daniel se descreve como um homem de barba e cabelos pretos e olhos castanho-escuros. Ele veste uma camisa branca e está sentado em uma cadeira preta em frente a uma parede clara iluminada em azul.
Vamos entender o que abordaremos neste curso e quais tópicos serão discutidos. Para começar, falaremos sobre algumas questões importantes, como o funcionamento de classes, o que são classes de fato e como elas são armazenadas na memória do computador.
Iremos explorar também como listas e LinkedList
funcionam, o que é uma LinkedList
, as diferenças entre elas, e os benefícios e desvantagens de cada uma. Analisaremos vários cenários e questionaremos: métodos sempre possuem retorno ou nem sempre? Como lidamos com isso? O que é struct
? Quais são os benefícios de utilizá-la? E o que é record
? O que ganhamos ao usá-las e por que existem essas possibilidades?
No final, vamos entender o que é record struct
, basicamente uma combinação entre record
e struct
. Ao longo do curso, analisaremos diversos tópicos e cenários para entender por que poderíamos ou não utilizar determinadas ferramentas que o .NET oferece.
Esperamos que você aproveite o curso. Te encontramos na primeira aula!
Qual será o ponto de partida do nosso curso? A ideia é, no caso em que trabalhamos agora, lidar com um projeto que já foi inicializado, mas em um contexto onde lidaremos com classes específicas, como se estivéssemos desenvolvendo uma biblioteca para um projeto de escopo maior.
Qual é o projeto em que estamos trabalhando nesse momento? Temos, basicamente, três classes: a classe Coordenada.cs
, a classe Usuario.cs
, e também a classe UsuarioDto.cs
.
Caso já tenha feito os cursos de API com .NET da Alura, que não são necessariamente pré-requisitos para este curso, você vai entender o conceito do DTO do usuário. Porém, para este curso, não vamos aplicar esses conceitos específicos, então não precisa se preocupar caso não tenha feito os cursos. É interessante fazer para expandir o seu conhecimento.
Temos um projeto com essas três classes, mais a classe Program.cs
que está vazia, e a ideia do projeto é, a partir do que já temos, desenvolver eventuais modificações para que ele tenha melhor utilização de memória e que possamos entender, por meio deste projeto, como torná-lo mais bem voltado para o consumo de memória e como lidamos com a memória no escopo desse projeto que futuramente seriam as libs, um projeto de escopo maior.
Um ponto de partida foi dado. Vamos começar, então, a sessão da classe Usuario
para entendermos algumas coisas. Por exemplo: vamos analisar com mais detalhes o que temos nessa classe. Temos um construtor, a public class Usuario
, onde temos um nome
, um email
e uma lista de strings de telefone
.
Temos também a definição das propriedades. Primeiro, temos um Id
que não definimos no construtor, mas temos as outras três propriedades de Nome
, Email
e Telefones
que estão sendo definidas.
Program.cs
O que queremos fazer agora, antes de começar a entender um pouco da teoria? Vamos inicializar a classe Program.cs
, criando um Usuario usuario
igual a new Usuario()
. Feito isso, vamos inicializar esse usuário, cujo nome será "Daniel", o e-mail será "daniel@email.com". Por fim, temos uma lista de telefones, então digitamos new List<string>()
, que será inicializada com os valores da lista eventualmente, por exemplo, "12345678".
Para termos uma boa utilização de espaço, vamos quebrar as linhas.
Program.cs
:
using UsuarioLib;
Usuario usuario =
new Usuario(
"Daniel",
"daniel@email.com",
new List<string>() {"12345678"});
Outro ponto que vamos abordar agora é a questão de como cada propriedade da classe Usuario
é armazenada na memória, a nível de, quando instanciamos essa classe em Program.cs
, o que acontece efetivamente? Vamos desenvolver um cenário para que possamos partir mais para a teoria de como o gerenciamento de memória do .NET funciona.
No momento em que instanciarmos um Usuario()
, algo útil que precisamos nos preocupar é o seguinte: se temos alguma lista de telefones, isto é, se a pessoa usuária tem algum telefone na lista com oito dígitos, o que vamos querer fazer será converter para nove dígitos, para ficarmos no padrão atual, ou seja, adicionar um nove na frente.
PadronizaTelefones()
Então, vamos criar no arquivo Usuario.cs
um método chamado public void PadronizaTelefones()
. A ideia será que, para cada telefone da lista, vamos adicionar, caso ele tenha oito dígitos, um 9 na frente.
Podemos fazer isso com um link, então digitamos Telefones.Select()
, indicando que para cada telefone
que tivermos na lista, será verificado se o tamanho dele é igual a 8. Se for, vamos falar que o telefone será igual a "9" mais telefone
. Se não for igual a 8, o telefone
será ele mesmo.
Usuario.cs
:
public void PadronizaTelefones()
{
Telefones.Select(telefone =>
telefone.Length == 8 ?
telefone = "9" + telefone :
telefone
);
}
Utilizamos o link para indicar que, para cada telefone da lista de telefones, se o tamanho desse telefone (telefone.Lenght
) em específico for 8, deve ser colocado o 9 na frente; se não, deve ser mantido o valor atual.
ExibeTelefones()
Além desse método, teremos um public void ExibeTelefones()
. No escopo do método, faremos basicamente um Telefones.ForEach()
, e para cada telefone que tivermos da pessoa usuária, daremos um Console.WriteLine()
para telefone
.
Usuario.cs
:
public void ExibeTelefones()
{
Telefones.ForEach(telefone => Console.WriteLine(telefone));
}
De volta à classe Program.cs
, vamos fazer um teste para entender o que está acontecendo efetivamente. Primeiro, ao final do código, vamos chamar usuario.ExibeTelefones()
, e em seguida, vamos chamar usuario.PadronizaTelefones()
. Por fim, vamos fazer usuario.ExibeTelefones()
novamente.
Program.cs
:
usuario.ExibeTelefones();
usuario.PadronizaTelefones();
usuario.ExibeTelefones();
O que esperamos por padrão que aconteça? No momento em que chamarmos ExibeTelefones()
pela primeira vez, vamos exibir o telefone cadastrado, que é "12345678". No momento em que chamamos PadronizaTelefones()
, efetuamos a padronização, e por fim, ao exibir novamente, esperamos que esteja no padrão "912345678".
O que você imagina que vai acontecer? A depender do que você estiver pensando, já teremos uma quebra de expectativa boa para justificar tudo o que devemos abordar nesse conceito de gerenciamento de memória.
Ao executar, é exibido duas vezes "12345678". Ou seja, ele não alterou o telefone. Por quê?
Talvez você já esteja pensando em argumentos, como "strings são imutáveis", ou "foi feito algo errado". Vamos analisar o método que fizemos de PadronizaTelefones()
com o Select()
. O Select()
projeta cada elemento da sequência em uma nova forma. Então, se avaliarmos bem o que acontece, para cada telefone, fazemos essa devida validação.
Mas por que não alterou? Porque o retorno do Select()
é um IEnumerable<string>
, pois Telefones
é uma lista de strings. Então, o que deveríamos fazer, de certa forma, seria falar que a lista de Telefones
, a partir desse momento, será igual ao Select()
que fizemos seguido de .ToList()
.
Usuario.cs
:
public void PadronizaTelefones()
{
Telefones = Telefones.Select(telefone =>
telefone.Length == 8 ?
telefone = "9" + telefone :
telefone
).ToList();
}
A partir desse momento, se reexecutarmos a aplicação, teremos a exibição antiga e a nova no terminal.
Você pode pensar que esse conteúdo é muito simples e se perguntar: qual é a utilidade prática disso? Isso que acabou de acontecer é uma situação clássica onde, caso já tenhamos o conhecimento de como toda a memória é armazenada, como os conceitos são armazenados, as classes, os tipos primitivos e afins são armazenados no .NET, não correríamos o risco de ter, por exemplo, esse bug em um ambiente produtivo.
Já imaginou isso sendo levado à frente e gerando esse problema? Seria uma dor de cabeça. Por isso é importante, além de começar a avançar nesse projeto com diversas outras questões de gerenciamento de memória, como falamos anteriormente na apresentação do curso, também entender o que devemos fazer para armazenar as nossas classes, os nossos objetos e tipos primitivos.
Vamos entender isso melhor no próximo vídeo. Te encontramos lá e até mais!
Vamos começar entendendo o que efetivamente aconteceu no vídeo anterior. Entenderemos por que a lista não foi alterada, por que depois ela foi e como aquilo tudo está sendo armazenado na memória.
O que precisamos entender é que existem três principais estruturas que armazenam os dados, não os dados no sentido de banco, por exemplo, mas os dados no sentido de valores, de referências, instâncias e afins.
O primeiro tipo que vamos abordar é a stack, onde quando temos algum valor armazenado, por exemplo, um int
, um bool
, um double
, um byte
e afins, como var a = 0
, int b = 5
, ou double valor = 2.253
, qualquer tipo desses valores primitivos que temos armazenado, os valores serão armazenados na stack, que é essa estrutura cuja tradução literal é uma pilha de dados que temos para armazenar esses tipos de valores.
A outra estrutura que temos é a heap, onde vamos armazenar tipos mais complexos, os valores dos objetos que temos efetivamente. Então, no momento em que fizermos a instância, correspondente ao momento no código em que fazemos Usuario usuario = new Usuario()
, criamos na heap os valores, nesse caso com Id
, que não definimos, o Nome
Daniel e o Email
"daniel@email.com".
Se tivéssemos, por exemplo, outra pessoa usuária criada com alguma outra informação definida, esses valores também ficariam na heap de dados.
Um último tipo aqui que temos é o LOH (Large Object Heap). O que isso quer dizer? Para que ele serve? Ele é basicamente uma heap, mas para objetos muito grandes, ou seja, 85 kb ou mais.
Mas como tudo isso que falamos agora se relaciona com o problema que tivemos anteriormente, de alterar cada elemento da lista, mas, aparentemente, não ocorrer essa mudança como esperado e precisar de uma atribuição manual para resolver?
O que acontece é que, por exemplo, no momento em que criamos uma pessoa usuária no código, na stack, apontamos para 1. Quando fazemos Usuario u = new Usuario()
, a referência, isto é, o ponteiro para onde esse valor está na memória vai ficar na stack, e o valor, efetivamente, vai ficar na heap.
Então, se fizermos outro tipo de instância, como, por exemplo, outra pessoa usuária, quando fizéssemos a execução do código, teríamos dois ponteiros diferentes apontando para diferentes valores na heap.
Mas por que o que aconteceu anteriormente aconteceu efetivamente? Porque quando criamos a lista de telefones, que está dentro do nosso usuário, mas temos uma lista apontando, ela não era um tipo primitivo. Vimos que o que é armazenado na stack são as referências e tipos primitivos, mas a lista de telefones é uma lista de strings.
O que acontece nesse momento é que, quando fizemos a inserção do valor, apontamos para um espaço específico na memória. Quando fizemos Telefones.Select()
, o Select()
projetou cada elemento da sequência em uma nova forma. Então, o que ele fez efetivamente foi apontar para um novo lugar na memória. Assim, o Telefones.Select()
apontou para outro espaço em memória, e esse outro espaço em memória tem o valor que queremos de fato.
É como se tivéssemos {"12345678"}
e, ao fazer a operação, obtivéssemos {"912345678"}
. Basicamente, foi isso que aconteceu. Temos a lista com esse valor, fizemos o Telefones.Select()
, que retorna uma nova sequência, e essa nova sequência estará na stack, apontando para um espaço diferente.
No momento em que executamos essa atribuição, paramos de apontar, a partir dessa referência, para o valor que está na heap, e apontamos para o novo valor. Então, todo o espaço que era apontado pela lista de telefones foi perdido na memória e, posteriormente, será coletado pelo garbage collector, para não ficarmos com muito lixo na memória.
Por isso, devemos entender os conceitos de stack e heap, principalmente, além de ter a noção da existência do LOH.
Em resumo, a partir da stack, deixamos de apontar para a lista antiga e passamos a apontar para uma nova. Então, o valor que tínhamos foi perdido e, a partir desse momento, apontamos para um novo local dentro da memória, mas com o mesmo ponteiro que tínhamos, ou seja, a lista de telefones.
A partir de agora, vamos seguir otimizando o nosso projeto a nível de memória, entendendo quais outros tipos de estrutura podemos utilizar. Te encontramos no próximo vídeo e até mais!
O curso .NET: gerenciamento de memória para otimização de performance possui 122 minutos de vídeos, em um total de 44 atividades. Gostou? Conheça nossos outros cursos de .NET em Programação, ou leia nossos artigos de Programação.
Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:
Impulsione a sua carreira com os melhores cursos e faça parte da maior comunidade tech.
1 ano de Alura
Assine o PLUS e garanta:
Formações com mais de 1500 cursos atualizados e novos lançamentos semanais, em Programação, Inteligência Artificial, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.
A cada curso ou formação concluído, um novo certificado para turbinar seu currículo e LinkedIn.
No Discord, você tem acesso a eventos exclusivos, grupos de estudos e mentorias com especialistas de diferentes áreas.
Faça parte da maior comunidade Dev do país e crie conexões com mais de 120 mil pessoas no Discord.
Acesso ilimitado ao catálogo de Imersões da Alura para praticar conhecimentos em diferentes áreas.
Explore um universo de possibilidades na palma da sua mão. Baixe as aulas para assistir offline, onde e quando quiser.
Acelere o seu aprendizado com a IA da Alura e prepare-se para o mercado internacional.
1 ano de Alura
Todos os benefícios do PLUS e mais vantagens exclusivas:
Luri é nossa inteligência artificial que tira dúvidas, dá exemplos práticos, corrige exercícios e ajuda a mergulhar ainda mais durante as aulas. Você pode conversar com a Luri até 100 mensagens por semana.
Aprenda um novo idioma e expanda seus horizontes profissionais. Cursos de Inglês, Espanhol e Inglês para Devs, 100% focado em tecnologia.
Transforme a sua jornada com benefícios exclusivos e evolua ainda mais na sua carreira.
1 ano de Alura
Todos os benefícios do PRO e mais vantagens exclusivas:
Mensagens ilimitadas para estudar com a Luri, a IA da Alura, disponível 24hs para tirar suas dúvidas, dar exemplos práticos, corrigir exercícios e impulsionar seus estudos.
Envie imagens para a Luri e ela te ajuda a solucionar problemas, identificar erros, esclarecer gráficos, analisar design e muito mais.
Escolha os ebooks da Casa do Código, a editora da Alura, que apoiarão a sua jornada de aprendizado para sempre.