Métodos LINQ avançados: Distinct, Except, Intersect e Union

Neste artigo abordaremos quatro métodos muito importantes do LINQ (Language Integrated Query): Distinct, Except, Intersect e Union. Além disso, exploraremos suas versões aprimoradas (com o sufixo “By”), que permitem a definição de uma chave específica para comparação. Vamos examinar como esses métodos funcionam e em que situações as variações com o “By” são mais úteis, com exemplos práticos em C#. Distinct e DistinctBy O método “Distinct” é utilizado para remover duplicatas de uma coleção, retornando apenas os elementos únicos. Ele é útil quando queremos evitar valores repetidos. Suponha que temos uma lista de números e queremos obter apenas os valores únicos: List numeros = new List { 1, 2, 3, 2, 4, 4, 5 }; var numerosUnicos = numeros.Distinct().ToList(); Console.WriteLine(string.Join(“, “, numerosUnicos)); O resultado será uma lista sem repetições de elementos, ou seja, “1, 2, 3, 4, 5”. Já o “DistinctBy” é uma versão mais flexível que permite definir uma chave específica para determinar a unicidade dos elementos. Assim, ele é útil para comparar apenas um campo ou propriedade, mantendo elementos únicos com base na chave escolhida. Por exemplo, considere uma lista de objetos “Pessoa”, em que queremos apenas uma instância de cada pessoa com base na” Cidade”. List pessoas = new List { new Pessoa { Nome = “João”, Cidade = “São Paulo” }, new Pessoa { Nome = “Maria”, Cidade = “Rio de Janeiro” }, new Pessoa { Nome = “Pedro”, Cidade = “São Paulo” } }; var pessoasDistintasPorCidade = pessoas.DistinctBy(p => p.Cidade).ToList(); foreach (var pessoa in pessoasDistintasPorCidade) { Console.WriteLine($”{pessoa.Nome} – {pessoa.Cidade}”); } O resultado será: “João – São Paulo, Maria – Rio de Janeiro”. Aqui, o “DistinctBy” elimina duplicatas com base no campo “Cidade”, retornando uma lista onde cada cidade aparece apenas uma vez. Except e ExceptBy O método “Except” retorna os elementos de uma coleção que não estão presentes em outra, funcionando como uma operação de diferença. Ele é útil para excluir elementos comuns entre duas listas. Considere duas listas de números. Queremos saber quais números estão na primeira lista, mas não na segunda. List lista1 = new List { 1, 2, 3, 4, 5 }; List lista2 = new List { 3, 4, 5, 6, 7 }; var diferenca = lista1.Except(lista2).ToList(); Console.WriteLine(string.Join(“, “, diferenca)); O resultado final será uma lista com os elementos 1 e 2, pois eles são os únicos elementos que não estão na “lista2”. Já o “ExceptBy” permite que você defina uma chave específica para realizar a comparação, sendo especialmente útil quando queremos comparar apenas um campo entre duas coleções. Agora, suponha duas listas de objetos “Produto”. Queremos saber quais produtos na primeira lista não estão na segunda, mas comparando apenas pelo campo “Codigo”. List produtosLoja1 = new List { new Produto { Codigo = 1, Nome = “Camiseta” }, new Produto { Codigo = 2, Nome = “Calça” }, new Produto { Codigo = 3, Nome = “Tênis” } }; List produtosLoja2 = new List { new Produto { Codigo = 3, Nome = “Tênis” }, new Produto { Codigo = 4, Nome = “Boné” } }; var produtosExclusivosLoja1 = produtosLoja1.ExceptBy(produtosLoja2.Select(p => p.Codigo), p => p.Codigo).ToList(); foreach (var produto in produtosExclusivosLoja1) { Console.WriteLine($”{produto.Nome} – {produto.Codigo}”); // Saída: Camiseta – 1, Calça – 2 } O resultado será a lista “produtosExclusivosLoja1” contendo apenas os itens camiseta e calça. Intersect e IntersectBy O método “Intersect” vai realizar a ação inversa ao método “Except”. Ele vai retornar apenas os elementos que estão presentes em ambas as coleções, funcionando como uma operação de interseção. É útil quando queremos saber quais itens duas coleções compartilham. Vamos supor que temos duas listas de números e queremos saber quais números estão presentes em ambas. List numeros1 = new List { 1, 2, 3, 4 }; List numeros2 = new List { 3, 4, 5, 6 }; var intersecao = numeros1.Intersect(numeros2).ToList(); Console.WriteLine(string.Join(“, “, intersecao)); Com isso, o nosso resultado será armazenado na lista “intersecao ” contendo apenas os valores 3 e 4. Já o “IntersectBy” permite fazer essa interseção que seja baseada em uma chave específica, que é ideal para cenários onde queremos comparar apenas um campo ou propriedade entre duas listas. Agora, considere duas listas de objetos “Aluno”. Queremos saber quais alunos estão matriculados em ambas as turmas, comparando apenas pelo “Id”. List turmaA = new List { new Aluno { Id = 1, Nome = “Carlos” }, new Aluno { Id = 2, Nome = “Ana” }, }; List turmaB = new List { new Aluno { Id = 2, Nome = “Ana” }, new Aluno { Id = 3, Nome = “Mariana” } }; var alunosEmAmbasTurmas = turmaA.IntersectBy(turmaB.Select(a => a.Id), a => a.Id).ToList(); foreach (var aluno in alunosEmAmbasTurmas) { Console.WriteLine($”{aluno.Nome} – {aluno.Id}”); } Neste exemplo, o “IntersectBy” retorna apenas a aluna “Ana” que está em ambas as turmas. Union e UnionBy Por fim, o método “Union” combina duas coleções, retornando uma única lista que contém todos os elementos, sem duplicatas. Ele é útil para unir listas de mesmo tipo enquanto remove repetições. Suponha que temos duas listas de números e queremos combiná-las, mas mantendo apenas valores únicos. List numerosA = new List { 1, 2, 3 }; List numerosB = new List { 3, 4, 5 }; var uniao = numerosA.Union(numerosB).ToList(); Console.WriteLine(string.Join(“, “, uniao)); O código acima nos retornará uma lista com os elementos 1,2,3,4,5. A sua variação, o “UnionBy”, permite definir uma chave específica para determinar a unicidade dos elementos combinados, sendo prático para garantir que objetos com valores duplicados em uma propriedade específica sejam removidos. Agora, imagine duas listas de objetos “Carro” onde queremos combinar as listas e manter apenas uma instância de cada carro com base na “Placa”. List carrosGaragem1 = new List { new Carro { Placa = “ABC1234”, Modelo = “Sedan” }, new Carro { Placa = “DEF5678”, Modelo = “SUV” } }; List carrosGaragem2 = new List { new Carro { Placa = “DEF5678”, Modelo = “SUV” }, new Carro { Placa = “GHI9101”, Modelo = “Hatch” } }; var todosCarros = carrosGaragem1.UnionBy(carrosGaragem2, c => c.Placa).ToList(); foreach (var carro

Tratamento de exceções em C#

O tratamento de exceções é uma parte fundamental do desenvolvimento de software, garantindo que erros inesperados sejam tratados de maneira controlada e que a aplicação continue funcionando de forma estável. No C#, as exceções podem ser tratadas utilizando as estruturas “try”, “catch”, “finally” e “throw”. Neste artigo vamos explorar como utilizar essas estruturas para capturar, tratar e limpar recursos e como tratar exceções de forma global. O que são exceções? Exceções são condições anormais ou erros que ocorrem durante a execução de um programa. Elas podem ser causadas por uma variedade de razões, como falha em uma operação de leitura de arquivos, tentativa de divisão por zero, entre outros. O tratamento adequado dessas exceções é crucial para garantir que a aplicação não falhe silenciosamente e que o usuário receba uma resposta apropriada. Bloco try O bloco “try” é utilizado para encapsular código que pode gerar uma exceção. Se uma exceção ocorrer, a execução será desviada para o bloco “catch” correspondente (caso exista). Aqui está um exemplo de como usar o “try-catch” para tratar exceções: try { var caminho = “D:\Arquivo\Inexistente\arquivo.txt”; var conteudo = File.ReadAllText(caminho); Console.WriteLine(conteudo); } catch (FileNotFoundException ex) { Console.WriteLine($”Erro: Arquivo não encontrado – {ex.Message}”); } Neste exemplo, qualquer tentativa de leitura de um arquivo inexistente gerará uma exceção que será capturada pelo bloco catch. Sem o uso de “try-catch”, o programa falharia abruptamente se ocorresse uma exceção. Por exemplo, tente executar o código abaixo sem tratamento de exceção: var caminho = “D:\Arquivo\Inexistente\arquivo.txt”; string conteudo = File.ReadAllText(caminho); Console.WriteLine(conteudo); Uma exceção de difícil compreensão será lançada, resultando em uma mensagem genérica de erro que pode não fornecer informações claras sobre a causa do problema. Isso dificulta o diagnóstico e a resolução do erro. Por outro lado, ao usar o “try-catch”, podemos capturar e tratar a exceção de forma controlada, personalizando a mensagem de erro para que ela seja mais informativa e fácil de compreender. Bloco catch Como vimos acima, o “catch” captura e trata exceções que foram lançadas em um bloco “try”. Você pode definir múltiplos blocos “catch” para tratar tipos específicos de exceções. try { int divisor = 0; int resultado = 100 / divisor; Console.WriteLine($”Resultado: {resultado}”); } catch (DivideByZeroException) { Console.WriteLine(“Erro: Não é possível dividir por zero.”); } catch (Exception) { Console.WriteLine(“Ocorreu um erro”); } Neste exemplo, se “divisor” for zero, a exceção “DivideByZeroException” será capturada. Outros erros serão tratados pelo bloco genérico “catch”. Aqui a ordem de definição dos blocos catch é importante: exceções mais específicas devem ser tratadas primeiro, enquanto as mais genéricas, como a classe “Exception”, deve vir depois.  Ainda no bloco “catch” é importante notar que você pode ou não declarar uma variável para receber os detalhes da exceção. Note que no exemplo acima especificamos apenas o nome da classe de exceção que está sendo tratada. Dessa forma, capturamos a exceção, mas não temos acesso aos seus dados. Se desejarmos acessar maiores detalhes sobre o erro, podemos fazer da seguinte forma: catch (Exception ex) { Console.WriteLine($”Erro genérico: {ex.Message}”); } Agora a variável “ex” recebe a instância da exceção lançada e, a partir dela, podemos acessar os detalhes do erro. Aqui cabe destacar as propriedades Message e StackTrace das classes de exceção. A primeira traz uma mensagem descritiva sobre o problema, enquanto a segunda traz o detalhamento da pilha de chamadas no código que gerou a exceção. Essa informação é crucial para identificarmos qual trecho do código causou o erro. Também é possível adicionar filtros no bloco “catch” para realizar tratamentos mais personalizados para cada cenário. Por exemplo: public int GetItem(int index) { List items = [1, 2, 3, 4, 5]; try { return items[index]; } catch(ArgumentOutOfRangeException) when (index < 0) { Console.WriteLine("Índice não pode ser negativo"); throw; } catch(ArgumentOutOfRangeException) { Console.WriteLine($"Índice incorreto. A lista só contém {items.Count} itens."); throw; } } No código acima estamos tratando o mesmo tipo de exceção duas vezes. Porém, no primeiro caso adicionamos um filtro para que o bloco “catch” só seja utilizado quando o parâmetro “index” for menor que zero. Para quaisquer outros valores inválidos de “index”, o segundo bloco “catch” será acionado. Bloco finally O bloco “finally” é utilizado para garantir que o código de limpeza será executado, independentemente de uma exceção ter sido lançada ou não. Ele pode ser usado com ou sem o bloco “catch” e é muito comum para realizar o descarte de recursos após algum processamento. public void AbrirConexao() { SqlConnection conexao = null; try { conexao = new SqlConnection("sua_conexao"); conexao.Open(); } catch (SqlException ex) { Console.WriteLine($"Erro de SQL: {ex.Message}"); } finally { if (conexao != null) { conexao.Close(); Console.WriteLine("Conexão fechada."); } } } No código acima o bloco “finally” fecha a conexão com o banco de dados, mesmo que ocorra uma exceção durante a execução. Instrução throw A instrução “throw” é utilizada para lançar uma exceção, seja criando uma nova ou re-lançando uma exceção já capturada. Isso permite que a exceção seja tratada em um nível superior ou forneça informações mais detalhadas sobre o erro. Por exemplo, no código abaixo, se a idade for menor que 18, uma exceção “ArgumentException” é lançada: public void ValidarIdade(int idade) { if (idade < 18) { throw new ArgumentException("A idade deve ser maior ou igual a 18 anos."); } } Além disso, o “throw” pode ser usado dentro de um bloco “catch” para re-lançar uma exceção capturada, permitindo que ela seja tratada em outro nível, como mostrado abaixo: try { if (idade < 18) { throw new ArgumentException("A idade deve ser maior ou igual a 18 anos."); } } catch (Exception ex) { Console.WriteLine($"Log de erro: {ex.Message}"); throw; } Esse uso do “throw” ajuda a criar um fluxo de controle eficiente para exceções, possibilitando um tratamento mais flexível e organizado. Acelere a sua carreira conosco! Se você é Desenvolvedor .NET Júnior e quer acelerar sua carreira até nível Pleno com salário de R$7k+, ou mesmo busca a primeira vaga, conheça a Mentoria .NET Start: Clique aqui Se é Desenvolvedor .NET Pleno ou Sênior e quer virar referência técnica em sua equipe e mercado, com salário de R$10k+, conheça

Introdução aos Worker Services no .NET

Um Worker Service no .NET é uma aplicação que roda em segundo plano, projetada para executar tarefas contínuas ou agendadas, sem a necessidade de interação direta com o usuário. Isso torna o Worker Service uma excelente opção para tarefas como processamento de dados, envio de e-mails, ou integração com filas de mensagens, especialmente em projetos que já possuem uma API existente. Uma das grandes vantagens é que você pode implementar um Worker Service dentro de um projeto de API já existente, sem a necessidade de criar um novo projeto separado. Isso facilita o gerenciamento e a manutenção do código, além de permitir a reutilização de serviços e configurações da API. Implementando um Worker Service em um projeto de API existente O Worker Service pode ser implementado dentro da estrutura do projeto existente, por exemplo, em uma nova pasta chamada “Workers”. Essa abordagem centraliza o código e simplifica a manutenção. Após criar uma pasta no local desejado, crie a classe. Para o nosso exemplo, eu criei uma classe chamada  “ProcesamentoPedidosWorker.cs” dentro dessa pasta, simulando um serviço que realiza o processamento de pedidos de um e-commerce: public class ProcesamentoPedidosWorker : BackgroundService { private readonly ILogger _logger; public ProcesamentoPedidosWorker(ILogger logger) { _logger = logger; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { _logger.LogInformation(“Processando pedidos”); await Task.Delay(5000, stoppingToken); } } } Em nosso exemplo acima, a classe “ProcesamentoPedidosWorker”, herda da classe “BackgroundService”, uma abstração fornecida pelo .NET para facilitar a criação de serviços de segundo plano. No construtor de “ProcesamentoPedidosWorker”, o serviço de log (ILogger) é injetado para registrar informações, erros ou outras mensagens durante a execução do Worker. A injeção de dependência permite que o Worker use os serviços registrados no container da aplicação (como repositórios, serviços de e-mail, etc.). Neste caso, estamos injetando um logger para registrar logs da execução. O método mais importante em um Worker Service é o “ExecuteAsync”, que contém a lógica principal que será executada em segundo plano. Nele criamos um loop “while” que  garante que o Worker continue executando enquanto o serviço não for cancelado. O “stoppingToken” é um token de cancelamento que sinaliza quando o serviço deve parar (como ao desligar a aplicação).  Após isso, usamos o “ILogger” para registrar uma mensagem a cada execução. Por fim, o  método “Task.Delay(5000)” cria uma pausa de 5 segundos entre cada ciclo de execução, simulando a execução de uma tarefa periódica. O “stoppingToken” é passado para permitir que a tarefa seja interrompida caso o Worker seja cancelado. Registre o Worker no Program.cs Para que o Worker Service seja executado em conjunto com a sua API, você precisa registrá-lo no “Program.cs”. No arquivo “Program.cs”, adicione o registro do serviço: builder.Services.AddHostedService(); No exemplo acima, utilizamos o método “AddHostedService” para registrar o Worker Service na coleção de serviços da aplicação. A classe “ProcesamentoPedidosWorker” é passada como o tipo do serviço, indicando ao .NET que essa classe será executada em segundo plano enquanto a API estiver ativa. Isso permite que o “ProcesamentoPedidosWorker” execute suas tarefas paralelamente, sem bloquear as operações da API, facilitando a implementação de processos contínuos ou agendados no seu projeto. Injeção de Dependências no Worker Service Assim como acontece nas APIs, o Worker Service pode aproveitar o ServiceProvider para a injeção de dependências. Isso permite o uso de serviços e repositórios que já existem no seu projeto, facilitando a integração. No exemplo abaixo, vamos supor que você tenha um serviço “IEmailService” que envia e-mails, e deseja utilizá-lo no Worker: public interface IEmailService { Task SendEmailAsync(string to, string subject, string body); } public class EmailService : IEmailService { public Task SendEmailAsync(string to, string subject, string body) { Console.WriteLine($”Enviando e-mail para {to}”); //Implementação do envio de e-mail. return Task.CompletedTask; } } Agora, no seu Worker, você pode injetar esse serviço e utilizá-lo para enviar e-mails: public class ProcesamentoPedidosWorker: BackgroundService { private readonly ILogger _logger; private readonly IEmailService _emailService; public ProcesamentoPedidosWorker(ILogger logger, IEmailService emailService) { _logger = logger; _emailService = emailService; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { _logger.LogInformation(“Processando pedidos”); await _emailService.SendEmailAsync(“cliente@exemplo.com”, “Pedido aprovado”, “Seu pedido foi aprovado!”); await Task.Delay(5000, stoppingToken); } } } E não se esqueça de registrar o serviço “IEmailService” no “Program.cs”: builder.Services.AddSingleton(); Dessa forma, o serviço de e-mail será usado pelo Worker sempre que ele for executado. Acelere a sua carreira conosco! Se você é Desenvolvedor .NET Júnior e quer acelerar sua carreira até nível Pleno com salário de R$7k+, ou mesmo busca a primeira vaga, conheça a Mentoria .NET Start: Clique aqui Se é Desenvolvedor .NET Pleno ou Sênior e quer virar referência técnica em sua equipe e mercado, com salário de R$10k+, conheça a Mentoria .NET Expert: Clique aqui Conclusão A integração de um Worker Service em um projeto de API existente no .NET oferece uma solução eficiente e centralizada para tarefas em segundo plano. O Worker Service pode ser facilmente adicionado em uma pasta do projeto, aproveitando toda a infraestrutura existente, como injeção de dependências e configurações. Além disso, ele é ideal para cenários como tarefas agendadas, monitoramento de serviços e processamento de dados. Esse recurso permite criar uma arquitetura mais robusta, escalável e modular, facilitando o gerenciamento de processos de segundo plano na aplicação e proporcionando uma implementação eficiente e organizada para atender as necessidades específicas do sistema.

Trabalhando com tipos genéricos em C#

Os tipos genéricos são um dos pilares da programação em .NET. Eles permitem criar código reutilizável, flexível e seguro, resolvendo um dos grandes desafios do desenvolvimento de software: a criação de componentes que podem trabalhar com diferentes tipos de dados. Neste artigo, vamos explorar o que são tipos genéricos, como e por que utilizá-los, e como implementá-los em C#. O que são tipos genéricos? Tipos genéricos permitem que você defina classes, métodos, interfaces e estruturas sem especificar o tipo exato de dado com o qual eles vão trabalhar. Em vez disso, você usa parâmetros de tipo, que serão substituídos por tipos reais no momento da utilização. Por exemplo, em vez de criar uma classe “ListaDeInteiros” para armazenar inteiros e outra “ListaDeStrings” para armazenar strings, você pode criar uma classe genérica “Lista<T>”, em que  “T” representa o tipo de dado. Esse conceito é extremamente poderoso, pois oferece: Reutilização de código: ao invés de duplicar código para diferentes tipos, você cria uma única versão genérica; Segurança em tempo de compilação: o compilador verifica o tipo utilizado, prevenindo erros em tempo de execução; Manutenção mais fácil: com menos código duplicado, a manutenção torna-se mais simples e menos sujeita a erros. Exemplo de classe genérica em C# Agora vamos ver um exemplo simples da utilização de genéricos. O primeiro passo é criar uma classe genérica chamada “Caixa”, na qual vamos processar um tipo que ainda não conhecemos. public class Caixa { private T _conteudo; public Caixa(T conteudo) { _conteudo = conteudo; } public void MostrarConteudo() { Console.WriteLine($”Conteúdo: {_conteudo}”); } } Acima, criamos uma classe que vai receber um tipo quando ela for instanciada. O T entre os sinais de menor e maior (<T>) indica que a classe é genérica e pode trabalhar com qualquer tipo de dado. Dentro dela, temos o método construtor “Caixa(T conteudo)” que recebe o valor do tipo “T” e armazena em uma variável privada “_conteudo” e o método “MostrarConteudo()” que exibe o conteúdo no console. Agora, vamos criar uma variável do tipo “Caixa” e informar que ela será do tipo “int”. Isso permite que nossa classe processe números inteiros: Caixa caixaInt = new Caixa(123); Aqui, estamos dizendo que a classe “Caixa” vai trabalhar com o tipo “int” e estamos passando o valor “123” para o conteúdo. Depois disso, podemos chamar o método “MostrarConteudo”, que vai exibir no console o valor “Conteúdo: 123”: caixaInt.MostrarConteudo(); Agora, suponha que eu queira utilizar a mesma classe “Caixa”, mas desta vez passando um tipo diferente, como uma “string”. Podemos criar outra instância de “Caixa” para processar texto: Caixa caixaString = new Caixa(“Texto”); Aqui, a classe “Caixa” vai trabalhar com “string”, e o valor “Texto” será armazenado. Podemos chamar o método “MostrarConteudo” novamente, e ele vai exibir o texto: Interfaces genéricas Interfaces genéricas nos permitem criar contratos que funcionam de forma flexível com diferentes tipos de dados. Isso é útil, especialmente em cenários nos quais o comportamento é o mesmo para diferentes tipos, mas os dados podem variar. Um exemplo comum de utilização de interfaces genéricas é na criação de repositórios para operações CRUD (Create, Read, Update, Delete), que é utilizado para manipular entidades em uma aplicação. Vamos começar criando uma interface genérica que define as operações básicas de um repositório, como adicionar, obter e remover itens: public interface IRepositorio { void Adicionar(T item); T Obter(int id); void Remover(int id); } Aqui, o “T” representa o tipo de entidade com o qual o repositório vai trabalhar. Isso significa que podemos criar implementações dessa interface para qualquer tipo de entidade que nossa aplicação precise gerenciar. Os métodos “Adicionar”, “Obter” e “Remover” são genéricos e vão funcionar com qualquer tipo de dado que seja passado quando a interface for implementada. Agora vamos criar uma classe “Produto” que funcionará como entidade a ser manipulada pelo repositório: public class Produto { public int Id { get; set; } public string Nome { get; set; } } Em seguida podemos implementar o repositório para “Produto” utilizando a interface genérica “IRepositorio<T>”: public class RepositorioDeProdutos : IRepositorio { private List _produtos = new List(); public void Adicionar(Produto produto) { _produtos.Add(produto); } public Produto Obter(int id) { return _produtos.FirstOrDefault(p => p.Id == id); } public void Remover(int id) { var produto = _produtos.FirstOrDefault(p => p.Id == id); if (produto != null) { _produtos.Remove(produto); } } } Essa implementação usa uma lista de “Produto” para armazenar os itens, simulando um banco de dados em memória. O método “Adicionar” insere um novo produto na lista, o “Obter” retorna um produto com base no id, e o “Remover” exclui o produto da lista se ele for encontrado. Uma das principais vantagens de interfaces genéricas é que podemos reutilizar a mesma lógica para diferentes tipos de entidades, sem duplicar o código. Por exemplo, podemos criar um “RepositorioDeClientes” da mesma forma que criamos o “RepositorioDeProdutos”, simplesmente mudando o tipo genérico de “Produto” para “Cliente”. Restrições de tipos (constraints) em genéricos Em C#, os tipos genéricos oferecem muita flexibilidade ao permitir que classes, métodos e interfaces possam trabalhar com diferentes tipos de dados. No entanto, em alguns cenários, pode ser necessário impor restrições sobre quais tipos podem ser utilizados com genéricos. Essas restrições são chamadas de constraints. Elas garantem que o tipo passado ao genérico atenda a certos critérios, como herdar de uma classe específica, implementar uma interface, ou possuir um construtor sem parâmetros. Por exemplo, você pode querer garantir que o tipo genérico seja uma classe, e não uma struct (um tipo de valor). Isso pode ser útil quando você está lidando com referências de objetos e precisa evitar que o genérico seja aplicado a tipos de valor. public class IRepositorio where T : class { //Implementações } Quando adicionamos o “where T : class” estamos colocando uma restrição que garante que o tipo “T” seja uma classe, não permitindo o uso de outros tipos. A mesma estrutura pode ser usada para aplicar outras restrições. Por exemplo, no caso do repositório, podemos querer que apenas classes que herdam de um tipo base “Entidade” sejam usadas como parâmetro: public

Convenções de nomenclatura em C#

As convenções de nomenclatura são essenciais para garantir que o código seja legível, consistente e de fácil manutenção. Em C# e .NET, seguir as convenções de nomenclatura recomendadas melhora a clareza, facilita a colaboração entre desenvolvedores e evita ambiguidades no código. Este artigo explora as principais convenções de nomenclatura para classes, métodos, propriedades, variáveis, interfaces e enumerações, oferecendo exemplos práticos e discutindo a importância de boas práticas. A importância de seguir convenções Seguir padrões de nomenclatura é uma prática essencial em qualquer linguagem de programação, mas no contexto de C# e .NET, essas convenções são mais que recomendadas — elas são quase um padrão da comunidade. As principais razões para seguir essas convenções incluem: Legibilidade: código bem nomeado é mais fácil de ler e entender, mesmo por pessoas que não participaram do desenvolvimento inicial; Manutenção: manter e atualizar o código se torna mais simples, pois o nome das variáveis e funções já indicam sua finalidade; Consistência: seguir um padrão consistente em todo o projeto melhora a qualidade do código e facilita a revisão por outros desenvolvedores; Reuso e extensibilidade: código bem estruturado e nomeado corretamente tende a ser mais modular e fácil de expandir. Agora, vamos examinar as principais convenções de nomenclatura em C# e .NET. Nomenclatura de classes Começando pelas classes, em C# elas seguem a convenção de serem nomeadas sem PascalCase, em que cada palavra no nome começa com uma letra maiúscula. O mesmo se aplica às propriedades públicas. O nome de uma classe e suas propriedades deve ser claro e descritivo quanto ao seu propósito: public class Cliente { public string Nome { get; set; } public string Sobrenome { get; set; } public DateTime DataNascimento { get; set; } } Caso alguma propriedade da classe não esteja em PascalCase, um aviso será emitido, indicando que é sugerido corrigir a nomenclatura. Exemplos: Correto: Cliente, FormaPagamento, Pedido. Incorreto: cliente, forma_pagamento, pedido. Nomenclatura de métodos Os métodos também utilizam PascalCase. O nome do método deve indicar o que ele faz, muitas vezes iniciando com um verbo no infinitivo. Métodos devem ser nomeados de forma a refletir claramente sua função. public class Calculadora { public int SomarValores(int primeiroValor, int segundoValor) { return primeiroValor + segundoValor; } } Exemplos: Correto: CalcularDesconto, AdicionarItem, InativarCliente. Incorreto: somaValores, somar_Valores, SOMARVALORES. Nomenclatura de variáveis locais e parâmetros Para variáveis locais e parâmetros de métodos, a convenção é usar camelCase. Ou seja, a primeira letra será minúscula e, em seguida, a primeira letra de cada palavra será maiúscula. O nome deve ser descritivo. public int CalcularDesconto(int valorTotal, int desconto) { int valorFinal= valorTotal – desconto; return valorFinal; } Exemplos: Correto: AdicionarItem(ItemPedido item) ConsultarClientes(string nome, string cpf) string primeiroNome DateTime ultimaDataCompra; Incorreto: CalcularDesconto(int valor_total, int desconto) CalcularDesconto(int VALORTOTAL, int DESCONTO) int valor_final int VALORFINAL Nomenclatura de interfaces As interfaces em C# também usam PascalCase, e adicionalmente podem conter uma letra “I” no início do nome para indicar que se trata de uma interface. O nome da interface deve descrever uma ação ou comportamento. public interface ICommand { void Executar(); } Nomenclatura de enums As enumerações também seguem a convenção de PascalCase, em que o nome da enum e cada um de seus valores começa com letra maiúscula. A Microsoft recomenda que os valores sejam claros e descritivos, representando estados ou categorias de forma precisa, com substantivos ou adjetivos. Na comunidade .NET, é comum o uso do prefixo “E” antes do nome da enumeração (como EStatus), para identificar visualmente que se trata de um tipo enum. Essa prática ajuda especialmente em projetos com muitas entidades e classes. public enum Status { Pendente, Enviado, Entregue, Cancelado } Exemplos: Correto: public enum TipoPagamento public enum EStatusPagamento Incorreto: public enum tipoPagamento public enum TIPO_PAGAMENTO Boas práticas em nomenclatura Evite abreviações e nomes ambíguos Evitar abreviações e nomes que não explicam o propósito é fundamental. Ainda que nomes mais curtos possam parecer convenientes, é melhor priorizar a clareza. Nomes explícitos facilitam a compreensão do código, mesmo que sejam um pouco mais longos. Correto: BuscarUsuarioPorId, ProcessarCodigoBarras Incorreto: Buscar, BusUsuPId Nomes claros ajudam a identificar rapidamente a função ou objetivo do método ou variável, tornando o código mais intuitivo e acessível para desenvolvedores que trabalham no mesmo projeto. Evite o uso de identificadores que conflitam com palavras-chave É recomendável evitar o uso de identificadores que entrem em conflito com palavras-chave de linguagens de programação amplamente usadas. De acordo com a Regra 4 da CLS (Common Language Specification), todas as linguagens compatíveis devem fornecer um mecanismo que permita o acesso a itens nomeados que usam uma palavra-chave dessa linguagem como identificador. O C#, por exemplo, utiliza o sinal “@” como um mecanismo de escape nesse caso. string @string; ou public class Example { public void @class() { // Código } } Embora seja possível utilizar “@” para evitar conflitos com palavras-chave, é uma boa prática evitar ao máximo o uso de palavras reservadas como identificadores. Métodos ou variáveis com escape “@” podem ser confusos e complicados de manter, além de dificultar a legibilidade do código. Acelere a sua carreira conosco! Se você é Desenvolvedor .NET Júnior e quer acelerar sua carreira até nível Pleno com salário de R$7k+, ou mesmo busca a primeira vaga, conheça a Mentoria .NET Start: Clique aqui Se é Desenvolvedor .NET Pleno ou Sênior e quer virar referência técnica em sua equipe e mercado, com salário de R$10k+, conheça a Mentoria .NET Expert: Clique aqui Conclusão Seguir as convenções de nomenclatura em C# e .NET é uma prática essencial para a criação de código limpo, legível e fácil de manter. O uso correto de PascalCase e camelCase, evitar abreviações, garantir que nomes sejam claros e descritivos, e evitar conflitos com palavras-chave são práticas que devem ser seguidas rigorosamente. Manter essas boas práticas resultará em um código mais profissional e, o mais importante, mais fácil de colaborar e expandir em longo prazo.

Trabalhando com flags em .NET

No desenvolvimento de software, frequentemente nos deparamos com situações em que precisamos trabalhar com múltiplas opções ou estados simultaneamente. Para essas situações, o uso de flags em pode ser muito útil. As flags permitem combinar e manipular valores de forma eficiente, usando operações de bit a bit. O que são flags? Flags são comumente usadas em enums (enumerações) para representar combinações de valores usando operações bitwise (bit a bit). Imagine que você precisa representar diferentes permissões ou estados de um objeto, e essas permissões podem ser combinadas. Flags são ideais para isso, pois elas permitem representar múltiplos estados ao mesmo tempo usando um único valor. Enum e flags: a diferença Um enum tradicional permite escolher apenas um valor de cada vez. Por exemplo, um enum que representa os dias da semana permite que uma variável armazene apenas um dia por vez: public enum DiasDaSemana { Domingo = 1, Segunda = 2, Terça = 3, Quarta = 4, Quinta = 5, Sexta = 6, Sábado = 7 } Mas, e se você quiser representar mais de um dia ao mesmo tempo, como por exemplo “Segunda e Terça”? Para isso, o C# oferece enums com flags, que permitem representar combinações de valores. Usando enum com flags Para usar enum com flags, é necessário entender que os valores definidos no enum permitem combinar múltiplas opções, representando, por exemplo, uma seleção de dias da semana. Com o atributo [Flags], podemos tratar os valores do enum como uma combinação de flags. Para exemplificar isso, vamos criar um enum com flags: [Flags] public enum DiasDaSemana { Nenhum = 0, Domingo = 1, Segunda = 2, Terça = 4, Quarta = 8, Quinta = 16, Sexta = 32, Sábado = 64 } Dessa forma podemos combinar múltiplos valores desse enum usando o operador  “|”. Por exemplo: DiasDaSemana diasSelecionados = DiasDaSemana.Segunda | DiasDaSemana.Terça; Console.WriteLine(diasSelecionados); Nesse exemplo, combinamos os dias “Segunda” e “Terça”, e o enum resultante mantém ambos os valores e imprimindo  na tela o texto “Segunda, Terça”. Por que potências de 2? Se observarmos bem, quando criamos o enum com flags acima, utilizamos potências de 2 ao invés de sequência comum dos enumeradores (1,2,3,4 …). Isso acontece porque quando utilizamos potências de 2, cada valor ocupa uma posição distinta em seu mapa de bits. Por exemplo, considere as seguintes potências de 2 representadas em binário: 1 (2^0) = 0001 2 (2^1) = 0010 4 (2^2) = 0100 8 (2^3) = 1000 Cada valor tem apenas um bit ativo e em uma posição única. Isso garante que, ao combinar esses valores utilizando a operação OR bitwise (|), os bits ativos se somem, permitindo que várias flags sejam combinadas sem causar sobreposição. Para entendermos melhor, vamos criar uma flag de permissões: [Flags] public enum Permissoes { Nenhuma = 0, // 0000 Ler = 1, // 0001 Escrever = 2, // 0010 Executar = 4, // 0100 Deletar = 8 // 1000 } Se quisermos combinar “Ler” e “Escrever”, a operação Ler | Escrever resultará em: 0001 (Ler = 1) 0010 (Escrever = 2) ——- 0011 (Ler, Escrever = 3) O valor resultante (0011 em binário, ou 3 em decimal) representa a combinação de ambas as permissões sem que haja conflito ou sobreposição de bits. Operações comuns com flags Verificando valores Para verificar se uma flag específica está definida em uma combinação, utilizamos o operador “&”: bool contemSegunda = (diasSelecionados & DiasDaSemana.Segunda) == DiasDaSemana.Segunda; Console.WriteLine(contemSegunda); Aqui, estamos verificando se “Segunda” faz parte de “diasSelecionados”. Adicionando valores Para adicionar um valor a um enum flag, usamos o operador “|”: diasSelecionados |= DiasDaSemana.Quarta; Console.WriteLine(diasSelecionados); Nesse exemplo, adicionamos “Quarta” à combinação de dias já existente Removendo valores Para remover uma flag de uma combinação, usamos o operador “&” com o complemento bitwise “~”: diasSelecionados &= ~DiasDaSemana.Terça; Console.WriteLine(diasSelecionados); Aqui, removemos “Terça” da combinação. Quando usar flags? Flags são extremamente úteis em diversas situações, como: Permissões de acesso: quando você quer representar várias permissões (ler, escrever, executar, etc.) para um arquivo ou recurso; Configurações: ao armazenar múltiplas opções de configuração em um único campo; Estados de um sistema: quando um objeto pode estar em vários estados simultâneos (por exemplo, conectado e autenticado). Boas práticas Algumas boas práticas ao utilizar as flags são: Use potências de 2: sempre defina os valores do enum como potências de 2 para garantir que as combinações funcionem corretamente; Crie um valor “Nenhum”: incluir um valor Nenhum = 0 é uma boa prática, pois representa a ausência de qualquer flag; Facilite a leitura: combine o uso de enums com métodos descritivos para tornar o código mais legível e fácil de entender. Exemplo prático: sistema de permissões Vamos agora aplicar flags para um sistema de permissões de usuários. Após criar uma aplicação de console, vamos utilizar o flags de permissões que criamos acima e criar uma entidade chamada “Usuario”: public class Usuario { public string Nome { get; set; } public Permissoes PermissoesDeUsuario { get; set; } } Em seguida, vamos implementar o nosso código no “program.cs” do nosso projeto: public class Program { public static void Main(string[] args) { //Nosso código; } } Vamos adicionar um novo ao qual onde vamos dar as permissões de “Ler” e “Escrever”: Usuario usuario = new Usuario { Nome = “João”, PermissoesDeUsuario = Permissoes.Ler | Permissoes.Escrever }; Console.WriteLine($”{usuario.Nome} tem as seguintes permissões: {usuario.PermissoesDeUsuario}”); Depois, vamos adicionar mais uma permissão para ao usuário, que é a de “Executar”: usuario.PermissoesDeUsuario |= Permissoes.Executar; Console.WriteLine($”Novas permissões: {usuario.PermissoesDeUsuario}”); E por fim, vamos verificar se ele tem permissão para poder realizar deleções: bool podeDeletar = (usuario.PermissoesDeUsuario & Permissoes.Deletar) == Permissoes.Deletar; Console.WriteLine($”Pode deletar? {podeDeletar}”); Com isso, nós conseguimos criar um sistema simples de gerenciamento de permissões de usuários. Acelere a sua carreira conosco! Se você é Desenvolvedor .NET Júnior e quer acelerar sua carreira até nível Pleno com salário de R$7k+, ou mesmo busca a primeira vaga, conheça a Mentoria .NET Start: Clique aqui Se é Desenvolvedor .NET Pleno ou Sênior e quer virar referência técnica em sua equipe e mercado, com salário de R$10k+, conheça a Mentoria .NET Expert: Clique aqui Conclusão Flags no

Carregando configurações do AppSettings em .NET

No desenvolvimento de aplicações .NET, a configuração é um aspecto crucial para garantir que parâmetros importantes sejam acessados de forma eficiente e flexível. O arquivo “appsettings.json” é um dos métodos mais comuns e utilizados para armazenar configurações em projetos .NET, fornecendo uma maneira fácil de manter informações sensíveis e variáveis configuráveis fora do código-fonte. Estrutura do appsettings.json Primeiro, vamos começar com um exemplo básico de um arquivo “appsettings.json”. Ele é um arquivo de configuração baseado em JSON que pode armazenar informações como strings de conexão, variáveis de ambiente, e outros parâmetros importantes. Abaixo temos um exemplo: { “Logging”: { “LogLevel”: { “Default”: “Information”, “System”: “Warning”, “Microsoft”: “Warning” } }, “ConnectionStrings”: { “DefaultConnection”: “Server=myServerAddress;Database=myDataBase;User Id=myUsername;Password=myPassword;” }, “AppSettings”: { “ApplicationName”: “Minha Aplicação”, “Version”: “1.0.0”, “SupportedCultures”: [ “en-US”, “pt-BR” ] } } Nesse exemplo, temos três seções principais: Logging: Configurações de nível de log. ConnectionStrings: Strings de conexão com o banco de dados. AppSettings: Informações customizadas da aplicação. Adicionando o serviço de configuração Antes de começar a ler as configurações, é importante garantir que o serviço de configuração seja adicionado ao pipeline da sua aplicação. Para aplicações como ASP.NET Core, isso já é configurado automaticamente. Entretanto, para outros tipos de projetos, como aplicações de console, você precisa configurar manualmente o carregamento do “appsettings.json”. Em uma aplicação de console, você precisa configurar manualmente o carregamento do “appsettings.json” no arquivo “Program.cs”. Isso é feito adicionando o pacote “Microsoft.Extensions.Configuration.Json” e configurando o builder manualmente. Veja como fazer isso: Install-Package Microsoft.Extensions.Configuration.Json ou dotnet add package Microsoft.Extensions.Configuration.Json Adicione a configuração no “Program.cs”: using Microsoft.Extensions.Configuration; class Program { static void Main(string[] args) { var builder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile(“appSettings.json”, optional: true, reloadOnChange: true); IConfiguration configuration = builder.Build(); var appName = configuration[“AppSettings:ApplicationName”]; Console.WriteLine($”Application Name: {appName}”); } } Nesse exemplo, estamos configurando manualmente o “ConfigurationBuilder” para carregar as configurações do arquivo “appSettings.json”. Em seguida, usamos a interface “IConfiguration” para acessar os valores. Neste exemplo é necessário criar o arquivo “appSettings.json” na pasta de execução da aplicação. Lendo configurações com IConfiguration A forma mais básica de acessar as configurações no .NET é utilizando a interface “IConfiguration”. Ela fornece uma maneira simples de acessar os valores diretamente, seja qual for a estrutura ou tipo do dado. Para o nosso exemplo, vamos  supor que queremos acessar a string de conexão com o banco de dados. Podemos fazer isso injetando “IConfiguration” em um controlador da nossa API. [ApiController] [Route(“api/[controller]”)] public class ConfigController : ControllerBase { private readonly IConfiguration _configuration; public ConfigController(IConfiguration configuration) { _configuration = configuration; } [HttpGet(“GetDatabaseConnection”)] public IActionResult GetDatabaseConnection() { var connectionString = _configuration.GetConnectionString(“DefaultConnection”); return Ok(connectionString); } } No código acima, utilizamos o método “GetConnectionString” para obter a string de conexão configurada no “appsettings.json”. A interface “IConfiguration” é injetada no construtor da classe e usada para acessar a configuração. Usando o método GetSection O método “GetSection” permite acessar uma parte do arquivo “appsettings.json” como uma seção distinta. Isso é útil quando você deseja trabalhar com uma subseção completa, como uma estrutura mais complexa. O valor retornado por “GetSection” ainda é um objeto de configuração, de modo que você pode acessar subseções e valores dentro dela. var applicationName = _configuration.GetSection(“AppSettings”)[“ApplicationName”]; Aqui, “GetSection(“AppSettings”)” isola a seção “AppSettings” do “appsettings.json”, e então “[“ApplicationName”]” acessa o valor dessa chave dentro da seção. Acessando diretamente via indexação de chave Uma segunda abordagem é utilizar a notação de chave com : (dois pontos), permite acessar valores de forma direta sem ter que chamar “GetSection” explicitamente. Essa notação representa a hierarquia no arquivo JSON. var applicationName = _configuration[“AppSettings:ApplicationName”]; Nesse caso, estamos utilizando a chave “AppSettings:ApplicationName” diretamente, o que evita a necessidade de chamar o “GetSection”. Quando usamos o “IConfiguration” para acessar diretamente as configurações no “appsettings.json” estamos acessando valores manualmente usando strings de chaves. Embora isso funcione bem em aplicações simples, pode se tornar problemático em sistemas maiores, pois como as chaves são passadas como strings, erros de digitação podem ocorrer facilmente. Se o nome de uma chave mudar no “appsettings.json”, seria necessário atualizar manualmente todas as referências ao longo da aplicação, o que aumenta o risco de inconsistências e erros difíceis de detectar. Mapeando configurações com IOptions Para resolver esses problemas, o “IOptions<T>” oferece uma abordagem mais robusta e segura. Ao usar “IOptions<T>”, mapeamos as configurações do “appsettings.json” para uma classe fortemente tipada. Isso reduz a chance de erros, pois passamos a usar propriedades fortemente tipadas em vez de strings, e se o nome de uma chave mudar, só precisamos ajustar o mapeamento uma vez na classe de configuração, em vez de em todo o código. Além disso, usando “IOptions<T>”, o próprio .NET valida a configuração durante o carregamento, o que evita que valores ausentes ou incorretos causem erros silenciosos de tempo de execução. Mapeamento com IOptions Primeiro, criamos uma classe que corresponde às seções do arquivo “appsettings.json”. No nosso exemplo, vamos mapear a seção “AppSettings”: public class AppSettings { public string ApplicationName { get; set; } public string Version { get; set; } public List SupportedCultures { get; set; } } Configurando o IOptions Agora, vamos configurar o “IOptions<T>” dentro do arquivo “Program.cs” para mapear as configurações para a classe “AppSettings”. var builder = WebApplication.CreateBuilder(args); builder.Services.Configure(builder.Configuration.GetSection(“AppSettings”)); O comando “builder.Configuration.GetSection(“AppSettings”)” acessa a seção “AppSettings” do arquivo “appsettings.json” e mapeia suas configurações para a classe “AppSettings” que criamos, tornando essas informações disponíveis de forma estruturada. Em seguida, o “builder.Services.Configure<AppSettings>” registra essa classe no contêiner de dependências da aplicação, permitindo que ela seja injetada em qualquer serviço ou controlador que precise acessar essas configurações. Acessando as configurações em um controller Agora que configuramos o “IOptions<T>”, podemos injetá-lo em qualquer lugar da aplicação, como em um controlador de API, para acessar as configurações de forma tipada. [ApiController] [Route(“api/[controller]”)] public class ConfigController : ControllerBase { private readonly IOptions _appSettings; public ConfigController(IOptions appSettings) { _appSettings = appSettings; } [HttpGet(“GetAppSettings”)] public IActionResult GetAppSettings() { var appSettings = _appSettings.Value; return Ok(appSettings); } } O “_appSettings.Value” permite acessar a instância da classe “AppSettings” que foi configurada por meio de “IOptions<AppSettings>”. Ao utilizar essa abordagem, as configurações armazenadas no arquivo “appsettings.json” são carregadas de forma estruturada

Introdução aos testes de unidade com XUnit no .NET

Os testes de unidade são uma técnica fundamental para garantir que o comportamento de pequenas partes de um sistema funcione conforme esperado. No .NET, o XUnit é um framework popular para escrever testes de unidade, devido à sua simplicidade e flexibilidade. Boas práticas na criação de testes de unidade Antes de começarmos com a implementação do nosso exemplo, é importante destacar algumas boas práticas para escrever testes de unidade eficazes: Testes independentes: cada teste deve ser independente dos outros. Isso significa que a ordem de execução dos testes não deve afetar seus resultados. Nomeação clara: O nome do teste deve descrever claramente o que ele está testando. Isso facilita o entendimento de qual comportamento está sendo validado. Arrange-Act-Assert (AAA): a estrutura padrão para testes de unidade, que facilita a leitura e compreensão do código de teste: Arrange: configura o cenário inicial. Act: executa a ação a ser testada. Assert: verifica se o resultado é o esperado. Manter o teste simples e específico: testes devem ser simples, focando em apenas uma funcionalidade ou cenário por vez. Isso facilita a identificação do que falhou em caso de erro. Evite dependências externas: testes de unidade não devem depender de serviços externos, como bancos de dados ou APIs. Utilize mocking ou stubs para simular dependências. Teste cenários positivos e negativos: teste tanto os caminhos em que tudo ocorre como esperado (cenários positivos) quanto os erros esperados e exceções (cenários negativos). Implementando um sistema de estoque Antes de começarmos a escrever nossos testes, precisamos de um código que será testado. Para isso, vamos desenvolver um sistema de gerenciamento de estoque. O primeiro passo é criar um projeto de console que conterá uma entidade chamada “Produto”. public class Produto { public string Nome { get; set; } public decimal Preco { get; set; } public int Quantidade { get; set; } public decimal ValorTotal { get; set; } public Produto(string nome, decimal preco, int quantidade) { Nome = nome; Preco = preco; Quantidade = quantidade; ValorTotal = quantidade * preco; } } Após isso, vamos criar uma classe chamada “Estoque” em que vamos implementar o gerenciamento: private readonly List _produtos = new List(); // Adicionar produto ao estoque public void AdicionarProduto(Produto produto) { var produtoExistente = _produtos.FirstOrDefault(p => p.Nome == produto.Nome); if (produtoExistente != null) { produtoExistente.Quantidade += produto.Quantidade; } else { _produtos.Add(produto); } } // Remover produto do estoque public void RemoverProduto(string nome, int quantidade) { var produto = _produtos.FirstOrDefault(p => p.Nome == nome); if (produto == null || produto.Quantidade < quantidade) { throw new InvalidOperationException("Produto indisponível ou quantidade insuficiente."); } produto.Quantidade -= quantidade; if (produto.Quantidade == 0) { _produtos.Remove(produto); } } // Retorna a lista de produtos public List ListarProdutos() { return _produtos; } Implementando testes com XUnit Agora que a lógica do sistema foi implementada, vamos garantir que tudo funcione corretamente através de testes de unidade. O primeiro passo é criar um projeto de teste do tipo XUnit dentro da solução onde está o projeto principal. O resultado da estrutura da solução deve ser algo semelhante a isso: Com o projeto de testes criado, clique com o botão direito do mouse sobre o projeto “GestaoEstoqueTest” e a seguir clique em “Adicionar” e selecione a opção “Referência de Projeto…”.  Na janela que abriu, marque o projeto “GestaoEstoque” e clique em OK: Agora, vamos criar um teste para verificar se a adição de produtos ao estoque está funcionando corretamente, seguindo a abordagem padrão de Arrange, Act e Assert: [Fact] public void AdicionarProduto_DeveAdicionarProdutoAoEstoque() { // Arrange var estoque = new Estoque(); var produto = new Produto("Laptop", 5000m, 10); decimal valorTotalEsperado = 50000m; // Preço esperado após a adição decimal valorTotalDiferenteDoEsperado = 20500m; // Um preço incorreto // Act estoque.AdicionarProduto(produto); // Assert var resposta = estoque.ListarProdutos(); Assert.Single(resposta); Assert.Equal(valorTotalEsperado, resposta[0].ValorTotal); Assert.NotEqual(valorTotalDiferenteDoEsperado, resposta[0].ValorTotal); Assert.NotNull(resposta); Assert.NotEmpty(resposta); Assert.InRange(resposta[0].Preco, 1000m, 15000m); } Arrange: Nesta fase, preparamos tudo o que é necessário para o teste. Aqui criamos um novo estoque (Estoque), e definimos um produto (Produto) com o nome “Laptop”, preço de 5000m, e quantidade de 10 unidades. Além disso, definimos dois valores esperados para o teste:   valorTotalEsperado: 50000m, que representa o valor total correto do produto após a adição. valorTotalDiferenteDoEsperado: 20500m, um valor total incorreto que usamos para garantir que o sistema não atribua preços errados.   Act: No Act, chamamos a ação que queremos testar. Nesse caso, é o método AdicionarProduto(produto), que adiciona o produto “Laptop” ao estoque. Este é o comportamento central que estamos verificando neste teste: a adição correta de produtos ao estoque.   Single A primeira verificação é se a lista de produtos, após a adição, contém exatamente um item. Isso é importante porque garante que o método “AdicionarProduto” inseriu apenas um produto no estoque e que não houve duplicação ou falha. Se houver mais ou menos de um item na lista, o teste falha.   Equal Aqui, validamos que o preço do produto adicionado ao estoque corresponde ao valor esperado. Isso confirma que o preço foi corretamente calculado e armazenado no produto. Se o valor armazenado no estoque não corresponder ao esperado, o teste falhará, indicando um possível problema na lógica de adição de produtos.   NotEqual Este assert serve para garantir que o preço do produto não é igual a um valor incorreto, como o que foi definido em “valorTotalDiferenteDoEsperado”. Ele atua como uma camada extra de segurança, confirmando que o sistema não atribuiu valores errados ao produto, prevenindo erros que poderiam passar despercebidos.   NotNull Este assert assegura que a lista de produtos retornada pelo método “ListarProdutos” não é nula, ou seja, que o método retornou uma lista válida. Esse assert serve para garantir que o sistema esteja manipulando os dados corretamente e que a coleção de produtos foi inicializada corretamente.   NotEmpty Além de verificar que a lista não é nula, também garantimos que ela não está vazia. Isso confirma que o produto foi de fato adicionado ao estoque, proporcionando maior confiança de que o processo de adição foi bem-sucedido e que o estoque está funcionando conforme o esperado.   InRange Por

Reconhecimento facial em .NET com Kairos

A validação biométrica está se tornando uma ferramenta indispensável em um mundo cada vez mais digital, desempenhando um papel crucial em áreas como segurança e autenticação de usuários. Com o crescimento das preocupações relacionadas à segurança e à privacidade, soluções que utilizam características únicas do ser humano, como impressões digitais, reconhecimento facial e íris, têm se destacado pela eficácia e precisão. O que é a API Kairos? A Kairos é uma API de reconhecimento facial que utiliza inteligência artificial para identificar e verificar pessoas com base em características faciais. A API oferece a funcionalidade de verificação biométrica, que compara duas imagens para confirmar a identidade do usuário. Implementação da validação biométrica Para ilustrar o nosso exemplo, vamos considerar um cenário em que queremos validar a identidade de um funcionário usando uma selfie e uma imagem que foi armazenada no momento do seu cadastro. O primeiro passo que devemos tomar é definir as variáveis que serão utilizadas ao longo do código: string apiUrl = “https://idv-eu.kairos.com/v0.1/biometric-verification”; string appId = “IdentificadorAPP”; string appKey = “ChaveDaSuaAPI”; string selfieFilePath = @”C:CaminhodaImagemSelfie.jpg”; string imageFilePath = @”C:CaminhodaImagemArmazenada.png”; A variável “apiUrl” contém a URL da API Kairos para verificação biométrica.  As variáveis “appId” e “appKey” são as credenciais necessárias para autenticar a requisição na API, e é importante que sejam substituídas pelos valores correspondentes de sua conta na Kairos. “selfieFilePath” e “imageFilePath” armazenam os caminhos para os arquivos de imagem que serão utilizados no processo de validação. É crucial garantir que esses caminhos estejam corretos e que as imagens existam para evitar erros durante a execução. if (!File.Exists(selfieFilePath) || !File.Exists(imageFilePath)) { Console.WriteLine(“Erro: Um ou mais arquivos de imagem não foram encontrados.”); return; } Aqui, realizamos uma verificação para garantir que os arquivos de imagem especificados existam. Se qualquer um dos arquivos não for encontrado, vamos exibir uma mensagem de erro, e o programa será encerrado. Essa etapa é importante para evitar que a aplicação falhe mais adiante ao tentar acessar arquivos inexistentes. using (var client = new HttpClient()) { var request = new HttpRequestMessage { Method = HttpMethod.Post, RequestUri = new Uri(apiUrl), Headers = { { “app_id”, appId }, { “app_key”, appKey }, }, Content = new MultipartFormDataContent { // Adiciona a selfie do usuário new StreamContent(File.OpenRead(selfieFilePath)) { Headers = { ContentDisposition = new ContentDispositionHeaderValue(“form-data”) { Name = “selfie”, FileName = Path.GetFileName(selfieFilePath), } } }, // Adiciona a imagem armazenada new StreamContent(File.OpenRead(imageFilePath)) { Headers = { ContentDisposition = new ContentDispositionHeaderValue(“form-data”) { Name = “image”, FileName = Path.GetFileName(imageFilePath), } } }, }, }; Neste bloco criamos uma instância do “HttpClient”, que será utilizada para enviar a requisição.  Em seguida, configuramos um “HttpRequestMessage”, definindo o método HTTP como POST e especificando a URL da API.  Os cabeçalhos de autenticação são adicionados utilizando “app_id” e “app_key”.  O conteúdo da requisição é um “MultipartFormDataContent”, que permite enviar múltiplos arquivos como parte da requisição. Aqui, utilizamos “StreamContent” para ler os arquivos de imagem diretamente do sistema de arquivos. Os cabeçalhos “ContentDisposition” são configurados para cada arquivo, especificando o nome do campo e o nome do arquivo, o que é necessário para a API entender quais dados estão sendo enviados. using (var response = await client.SendAsync(request)) { response.EnsureSuccessStatusCode(); var body = await response.Content.ReadAsStringAsync(); Console.WriteLine(body); } Por fim, vamos enviar a requisição utilizando o método “SendAsync” do “HttpClient”. Após enviar a requisição, usamos “EnsureSuccessStatusCode” para garantir que a resposta da API foi bem-sucedida. Caso contrário, uma exceção será lançada. Em seguida, lemos o corpo da resposta e o exibimos no console.  A resposta da API será algo semelhante a isso. Vamos entender os principais campos deste json: { “api_req_uid”: “2f960394-96c9-4403-b34d-92b1e33f7b7c”, “processed_at”: “2024-10-02 02:50:30”, “requested_at”: “2024-10-02 02:50:30”, “response_code”: 0, “response_data”: { “biometric”: { “image”: { “faces_match_confidences”: [ 1.0 ], “faces_predicted”: [ { “confidence”: 0.9357, “delta”: { “x”: 199.7969, “y”: 233.3345 }, “top_left”: { “x”: 233.5239, “y”: 162.5957 } } ], “img”: { “height”: 480, “width”: 640 } }, “max_facematch_confidence”: 1.0, “selfie”: { “faces_liveness”: 1.0, “faces_predicted”: [ { “confidence”: 1.0, “delta”: { “x”: 161.1886, “y”: 219.9249 }, “top_left”: { “x”: 297.8376, “y”: 196.7119 } } ], “img”: { “height”: 799, “width”: 735 } } }, “decision”: { “details”: [], “reject_score”: 0.0, “review_score”: 0.0, “warning_score”: 0.0 } } } response_code: Indica o status da resposta. Um código 0 geralmente significa que a solicitação foi processada com sucesso, sem erros. faces_match_confidences: Um array que armazena as pontuações de confiança da correspondência entre os rostos detectados na imagem submetida e na selfie. Neste exemplo, a confiança máxima foi de 1.0, indicando uma correspondência precisa. faces_liveness: Indica a verificação de vivacidade da selfie, ou seja, se a imagem foi tirada de uma pessoa viva. Um valor de 1.0 significa que a vivacidade foi confirmada com sucesso. reject_score: Indica a pontuação de rejeição da verificação. Um valor 0.0 significa que a verificação foi bem-sucedida. review_score e warning_score: Pontuações que indicam se há necessidade de revisão manual ou algum aviso relacionado ao processo de verificação. No exemplo, ambas estão em 0.0, sugerindo que não há problemas ou necessidade de revisão. Em casos onde a verificação não é bem-sucedida, o sistema pode gerar uma decisão negativa, como no exemplo a seguir: “decision”:{ “details”:[ { “code”:”FAILED_FACE_MATCH”, “confidence”:1.0, “decision”:”reject”, “description”:”Faces do not match; threshold failed: 0.2285; x < 0.4" } ], "reject_score":1.0, "review_score":0.0, "warning_score":0.0 } Neste exemplo, o sistema retorna um “reject_score” de 1.0, indicando que a verificação foi rejeitada. A descrição informa que “Os rostos não correspondem; limite falhou: 0.2285; x < 0.4”, o que significa que a pontuação de semelhança foi inferior ao limite necessário (0.4). Isso resultou em uma decisão negativa, pois semelhanças abaixo de 0.4 são consideradas insuficientes para validar a correspondência entre os rostos. Acelere a sua carreira conosco! Se você é Desenvolvedor .NET Júnior e quer acelerar sua carreira até nível Pleno com salário de R$7k+, ou mesmo busca a primeira vaga, conheça a Mentoria .NET Start: Clique aqui Se é Desenvolvedor .NET Pleno ou Sênior e quer virar referência técnica em sua equipe e mercado, com salário de R$10k+, conheça a Mentoria .NET Expert: Clique aqui Conclusão A validação biométrica está se

Utilizando Include e ThenInclude no Entity Framework

Quando estamos lidando com consultas que envolvem relações entre entidades, o EF oferece métodos eficientes para incluir dados relacionados, como “Include”, “ThenInclude”. Este artigo irá explorar esses métodos, explicando suas funcionalidades e apresentando exemplos práticos de uso. Lazy Loading No Entity Framework (EF), o Lazy Loading, ou carregamento tardio, é um comportamento padrão em que as entidades relacionadas não são carregadas automaticamente junto com a entidade principal. Esse comportamento é vantajoso, pois nem sempre precisamos de todos os dados relacionados à entidade, evitando assim o carregamento de informações desnecessárias, melhorando o desempenho em algumas situações ao reduzir a quantidade de dados inicialmente buscados. Eager Loading No entanto, há casos em que é necessário carregar as entidades relacionadas junto com a entidade principal. Isso é especialmente importante quando sabemos antecipadamente que precisaremos dessas informações. Nesses cenários, o Eager Loading, ou carregamento antecipado se torna uma abordagem mais apropriada. Usando os métodos “Include” e “ThenInclude”, o EF permite carregarmos as entidades relacionadas de uma só vez, em uma única consulta, garantindo que todas as informações necessárias estejam disponíveis imediatamente. Consulta sem Include Para ilustrar o funcionamento dessas técnicas no Entity Framework, vamos tomar como exemplo um cenário em que temos a entidade “Pedido”, a qual contém um “Cliente” que, por sua vez, contém um “Endereco”. Ou seja, temos dois níveis de relacionamento entre as entidades: Ao realizarmos uma consulta no banco de dados, apenas os dados da entidade principal, “Pedido”, são carregados. Os dados relacionados, como o “Cliente” e seu “Endereco”, não são carregados imediatamente. Por exemplo: var pedidos = _context.Pedidos.ToList(); No resultado da consulta acima teríamos apenas os dados da entidade principal, “Pedido”, enquanto a propriedade de navegação “Cliente” viria nula, pois ela não foi explicitamente requisitada. Nesse caso os dados retornados seriam os seguintes: [ { “pedidoId”: 1, “descricao”: “Pedido 001”, “clienteId”: 101, “cliente”: null }, { “pedidoId”: 2, “descricao”: “Pedido 002”, “clienteId”: 102, “cliente”: null } ] Utilizando Include Conforme foi comentado anteriormente, o Lazy Loading pode ser bastante útil para evitar o carregamento antecipado de dados que não serão necessários no primeiro momento. Porém, desta vez desejamos carregar também os dados do cliente vinculado ao pedido, para isso utilizaremos o método “Include”, da seguinte forma: var pedidos = _context.Pedidos .Include(p => p.Cliente) .ToList(); Neste exemplo estamos passando como parâmetro para o método “Include” uma expressão lambda que define qual propriedade de navegação desejamos carregar. Ao fazer isso, o Entity Framework irá resolver o relacionamento que foi configurado e realizar a consulta adequada para trazer os dados dependentes. Dessa vez os dados retornados seriam os seguintes: [ { “pedidoId”: 1, “descricao”: “Pedido 001”, “clienteId”: 101, “cliente”: { “clienteId”: 101, “nome”: “João Silva”, “endereco”: null } }, { “pedidoId”: 2, “descricao”: “Pedido 002”, “clienteId”: 102, “cliente”: { “clienteId”: 102, “nome”: “Maria Souza”, “endereco”: null } } ] Utilizando ThenInclude No exemplo anterior, carregamos uma propriedade de navegação a partir da entidade principal da consulta. Porém, note que a classe “Cliente” possui uma propriedade “Endereco” que veio nula na consulta acima. Isso ocorre porque como temos mais um relacionamento, é preciso carregá-lo explicitamente, assim como foi feito com o primeiro. Porém, dessa vez, como desejamos incluir uma entidade que está relacionada a uma outra que já foi incluída, precisamos utilizar o método “ThenInclude”, realizando um tipo de carregamento “em cascata”: var pedidos = _context.Pedidos .Include(p => p.Cliente) .ThenInclude(c => c.Endereco) .ToList(); Agora, os resultados retornados incluem também os dados da entidade “Endereco”, relacionada ao cliente de cada pedido: [ { “pedidoId”: 1, “descricao”: “Pedido 001”, “clienteId”: 101, “cliente”: { “clienteId”: 101, “nome”: “João Silva”, “endereco”: { “enderecoId”: 501, “rua”: “Rua das Flores”, “cidade”: “São Paulo” } } }, { “pedidoId”: 2, “descricao”: “Pedido 002”, “clienteId”: 102, “cliente”: { “clienteId”: 102, “nome”: “Maria Souza”, “endereco”: { “enderecoId”: 502, “rua”: “Avenida Paulista”, “cidade”: “São Paulo” } } } ] Utilizando múltiplos Include e ThenInclude Caso você precise carregar múltiplas entidade relacionadas, também é possível. Por exemplo, o código abaixo inclui as propriedades “Pagamento” e “Itens” do pedido: var pedidos = _context.Pedidos .Include(p => p.Cliente) .ThenInclude(c => c.Endereco) .Include(p => p.Pagamento) .Include(p => p.Itens) .ToList(); Da mesma forma, se cada uma dessas propriedades de navegação possuir entidades relacionadas, basta utilizar o “ThenInclude” novamente: var pedidos = _context.Pedidos .Include(p => p.Cliente) .ThenInclude(c => c.Endereco) .Include(p => p.Pagamento) .ThenInclude(p => p.Parcelas) .Include(p => p.Itens) .ThenInclude(p => p.Produto) .ToList(); Para incluir vários subníveis de relacionamento, basta usar o “ThenInclude” em cascata. Por exemplo, supondo que a classe “Endereco” possua uma propriedade “Pais”, ela pode ser incluída na consulta da seguinte forma: var pedidos = _context.Pedidos .Include(p => p.Cliente) .ThenInclude(c => c.Endereco) .ThenInclude(e => e.Pais) .ToList(); Já para o caso de uma mesma propriedade de navegação possuir mais de uma entidade relacionada, é necessário usar a sequência “Include+ThenInclude” repetidas vezes. Por exemplo, se o cliente possui uma propriedade “Contatos”, representando seus e-mails e telefones, ela pode ser incluída na consulta assim: var pedidos = _context.Pedidos .Include(p => p.Cliente) .ThenInclude(c => c.Endereco) .Include(p => p.Cliente) .ThenInclude(c => c.Contatos) .ToList(); Acelere a sua carreira conosco! Se você é Desenvolvedor .NET Júnior e quer acelerar sua carreira até nível Pleno com salário de R$7k+, ou mesmo busca a primeira vaga, conheça a Mentoria .NET Start: Clique aqui Se é Desenvolvedor .NET Pleno ou Sênior e quer virar referência técnica em sua equipe e mercado, com salário de R$10k+, conheça a Mentoria .NET Expert: Clique aqui Conclusão Conhecer o funcionamento dos métodos “Include” e “ThenInclude” é fundamental para a manipulação eficaz de dados relacionados no Entity Framework. Partindo dos exemplos ilustrados neste artigo você pode aplicar essa técnica em seus projetos e construir consultas completas.