Alternativas ao AutoMapper em C#

O AutoMapper é uma biblioteca popular em C# para mapeamento de objetos, facilitando a transformação de dados entre camadas. No entanto, recentemente foi anunciado que o AutoMapper passará a ser pago. Neste artigo, vamos explorar alternativas gratuitas e de código aberto, como TinyMapper, ExpressMapper e Mapster, destacando suas funcionalidades. A necessidade de ferramentas para mapeamento de objetos No desenvolvimento de software, é comum precisar realizar a transformação de dados entre diferentes tipos de objetos. Por exemplo, ao trabalhar com camadas distintas em uma aplicação, como a camada de persistência de dados (entidades) e a camada de apresentação (DTOs), a conversão entre esses tipos de objetos é fundamental. Realizar esse mapeamento manualmente pode ser demorado e propenso a erros, além de tornar o código mais difícil de manter. Por isso, ferramentas que automatizam esse processo de mapeamento tornam-se essenciais. Elas permitem que os desenvolvedores se concentrem nas regras de negócios e na lógica da aplicação, enquanto a ferramenta cuida da transformação dos dados. TinyMapper O TinyMapper é uma das alternativas mais simples e focadas em desempenho. Sua proposta é ser leve e extremamente rápida, fornecendo uma solução sem dependências adicionais e uma API de configuração simples. Para instalar o TinyMapper, adicione o pacote pelo .NET CLI ou NuGet no seu projeto: .NET CLI: dotnet add package TinyMapper NuGet: Install-Package TinyMapper O TinyMapper não exige configurações complexas. Basta informar quais objetos serão mapeados. Isso pode ser feito no “Program.cs” ou em outro local adequado: TinyMapper.Bind(); O mapeamento é feito automaticamente com base nas propriedades que possuem o mesmo nome entre as classes de origem e destino. var enderecoInicial = new Endereco { Id = 1, Rua = “Av. Paulista”, Cidade = “São Paulo”, Estado = “São Paulo”, CEP=”64000000″ }; var enderecoFinal = TinyMapper.Map(enderecoInicial); Mapeamento personalizado Para casos mais específicos, é possível usar mapeamentos personalizados. TinyMapper.Bind(c => { c.Ignore(s => s.Id); c.Bind(s => s.Avenida, d => d.Rua); c.Bind(s => s.NumeroCEP, d => d.CEP); c.Bind(s => s.NomeCidade, d => d.Cidade); c.Bind(s => s.NomeEstado, d => d.Estado); }); No exemplo acima, temos dois objetos cujas propriedades possuem nomes diferentes. É possível omitir o mapeamento de uma propriedade, como foi feito com o “Id”, e também especificar explicitamente qual propriedade de um objeto deve ser mapeada para outra. Mapeamento objetos aninhados O TinyMapper também oferece suporte para mapear objetos aninhados. Você pode fazer isso especificando manualmente como cada propriedade do objeto interno deve ser mapeada. Para esse exemplo e os demais vamos usar esse objeto: public class Endereco { public string Rua { get; set; } public string Cidade { get; set; } } public class Pessoa { public string Nome { get; set; } public Endereco Endereco { get; set; } } Podemos mapear “Pessoa” para “PessoaDTO”, incluindo o “Endereco”, dessa forma: TinyMapper.Bind(config => { config.Bind(s => s.Nome, d => d.Nome); config.Bind(s => s.Endereco.Rua, d => d.Endereco.Rua); config.Bind(s => s.Endereco.Cidade, d => d.Endereco.Cidade); }); ou desta outra maneira: TinyMapper.Bind(config => { config.Bind(s => s.Rua, d => d.Rua); config.Bind(s => s.Cidade, d => d.Cidade); }); TinyMapper.Bind(config => { config.Bind(s => s.Nome, d => d.Nome); config.Bind(s => s.Endereco, d => d.Endereco); }); Na primeira forma, mapeamos as propriedades dentro do objeto “Endereco” diretamente para as propriedades do “PessoaDTO”. Esse tipo de mapeamento é fácil de implementar quando você lida com objetos aninhados com um número reduzido de níveis. Já na segunda forma, nós criamos um mapeamento para “Pessoa” e outro para “Endereco”. Quando “Pessoa” precisa realizar o mapeamento do objeto “Endereco”, ele já sabe como deve proceder. ExpressMapper O ExpressMapper é uma alternativa que se destaca pela configuração rápida e por oferecer suporte a expressões Lambda para personalizar mapeamentos. Ele foca na simplicidade e flexibilidade, permitindo um mapeamento rápido e eficiente. Para instalar o ExpressMapper, adicione o pacote NuGet no seu projeto: .NET CLI: dotnet add package Expressmapper NuGet: Install-Package ExpressMapper A configuração básica do ExpressMapper é rápida e direta. Basta registrar o mapeamento entre as classes de origem e destino: ExpressMapper.Mapper.Register(); var enderecoInicial = new Endereco { Id = 1, Rua = “Av. Paulista”, Cidade = “São Paulo”, Estado = “São Paulo”, CEP=”64000000″ }; O mapeamento é feito automaticamente, usando as convenções de nome de propriedades. Para realizar o mapeamento: var enderecoFinal = ExpressMapper.Mapper.Map(enderecoInicial); Mapeamento personalizado Caso precise de mapeamentos mais específicos, como quando as propriedades de origem e destino possuem nomes diferentes, você pode usar expressões Lambda para personalizar a transformação de dados: ExpressMapper.Mapper.Register() .Ignore(dest => dest.Id) .Member(dest => dest.Rua, orig => orig.Avenida) .Member(dest => dest.CEP, orig => orig.NumeroCEP) .Member(dest => dest.Cidade, orig => orig.NomeCidade) .Member(dest => dest.Estado, orig => orig.NomeEstado); No exemplo acima, o ExpressMapper permite personalizar o mapeamento, especificando que a propriedade “Id” não deve ser mapeada usando “Ignore”. Após isso usamos o método “Member” para ligar o parâmetro de destino com o de origem. Mapeamento objetos aninhados Para realizarmos o mapeamento de objetos aninhados no ExpressMapper, podemos fazer da seguinte forma: ExpressMapper.Mapper.Register() .Member(s => s.Nome, d => d.Nome) .Member(s => s.Endereco.Rua, d => d.Endereco.Rua) .Member(s => s.Endereco.Cidade, d => d.Endereco.Cidade); ou ExpressMapper.Mapper.Register(); ExpressMapper.Mapper.Register() .Member(s => s.Nome, d => d.Nome) .Member(s => s.Endereco, d => d.Endereco); Semelhante ao TinyMapper, podemos realizar o mapeamento do objeto “Endereco” dentro de “Pessoa” ou podemos realizar o seu mapeamento separadamente. Mapster O Mapster é uma das alternativas mais poderosas e flexíveis ao AutoMapper. Ele suporta mapeamento manual, via configuração ou atributos, tornando-se ideal para projetos mais complexos que exigem flexibilidade e personalização. Para instalar o Mapster, adicione o pacote NuGet no seu projeto: .NET CLI: dotnet add package Mapster NuGet: Install-Package Mapster A configuração básica do Mapster pode ser feita diretamente, registrando a configuração do mapeamento: TypeAdapterConfig.NewConfig(); O mapeamento pode ser feita da seguinte forma: var enderecoInicial = new Endereco { Id = 1, Rua = “Av. Paulista”, Cidade = “São Paulo”, Estado = “São Paulo”, CEP=”64000000″ }; var enderecoFinal = enderecoInicial.Adapt(); O Mapster utiliza o método “Adapt<T>()”, que é um método de extensão da classe “Object”. Isso significa que todos os tipos de objetos em C# podem utilizar esse método para se adaptar a
Como funcionam os desconstrutores em C#

A partir do C# 7.0, uma nova funcionalidade foi introduzida na linguagem, permitindo uma maneira simples e poderosa de desconstruir objetos: os desconstrutores. Essa funcionalidade é especialmente útil quando trabalhamos com tuplas ou classes que contêm múltiplos valores e desejamos acessar seus componentes de maneira mais direta e legível. Neste artigo, vamos explorar o conceito de desconstrutores e como podemos utilizá-los no C#. O que é um desconstrutor? Um desconstrutor é um método especial que permite “desempacotar” um objeto em seus componentes individuais. Ele facilita a extração dos valores de um objeto sem precisar acessar suas propriedades diretamente, o que pode melhorar a clareza e a concisão do código. Em termos simples, o desconstrutor é usado para extrair os membros de um objeto em variáveis separadas, como se fosse um processo de “desconstrução” do objeto. Sintaxe básica de um desconstrutor O desconstrutor é implementado através de um método especial chamado “Deconstruct”. Esse método é, essencialmente, um método de instância que “desmonta” o objeto e passa seus componentes para as variáveis de sua escolha. Aqui está um exemplo básico de como isso pode ser feito: public class Ponto { public int X { get; } public int Y { get; } public Ponto(int x, int y) { X = x; Y = y; } public void Deconstruct(out int x, out int y) { x = X; y = Y; } } No exemplo acima, a classe “Ponto” tem um desconstrutor chamado “Deconstruct”. Esse método recebe duas variáveis (x e y) e as preenche com os valores das propriedades “X” e “Y” do objeto. Como as variáveis são passadas com o modificador “out”, o método “Deconstruct” é responsável por atribuir os valores a elas. Isso permite que, ao instanciar um objeto da classe “Ponto”, possamos facilmente obter seus valores. Depois de implementado, podemos usar o desconstrutor para extrair os valores de um objeto de forma simples e direta: var ponto = new Ponto(5, 10); var (x, y) = ponto; Console.WriteLine($”X: {x}, Y: {y}”); Neste exemplo, ao utilizar a sintaxe “var (x, y) = ponto”, o C# invoca automaticamente o método “Deconstruct” da classe “Ponto”, atribuindo os valores das propriedades “X” e “Y” às variáveis “x” e “y”, respectivamente. Exemplo de desconstrução com Tuplas Tuplas são tipos de dados que permitem armazenar múltiplos valores de diferentes tipos. Elas são especialmente úteis para retornar vários resultados de uma função ou para representar estruturas simples de dados. Para mais detalhes, confira nosso artigo sobre tuplas. Aqui está um exemplo de como usar desconstrutores com tuplas: public class Pessoa { public string Nome { get; } public int Idade { get; } public Pessoa(string nome, int idade) { Nome = nome; Idade = idade; } public void Deconstruct(out string nome, out int idade) { nome = Nome; idade = Idade; } } var pessoa = new Pessoa(“João”, 30); // Desconstruindo a instância de Pessoa em uma tupla var (nome, idade) = pessoa; Console.WriteLine($”Nome: {nome}, Idade: {idade}”); Neste exemplo, a classe “Pessoa” possui um método “Deconstruct” que permite desconstruir um objeto “Pessoa” em duas variáveis: “nome” e “idade”. Vantagens de usar desconstrutores O uso de desconstrutores oferece várias vantagens em termos de clareza e concisão do código: Facilidade de acesso aos componentes: não é necessário acessar cada propriedade individualmente. Em vez disso, podemos desconstruir um objeto em suas partes e usá-las diretamente. Redução de código: o processo de desconstrução permite escrever código mais enxuto e expressivo, especialmente em casos onde temos objetos compostos, como tuplas ou classes com múltiplas propriedades. Integração com Tuplas: em C# 7.0, as tuplas foram aperfeiçoadas, e desconstrutores permitem um uso ainda mais eficaz dessas estruturas. Ao usar tuplas, podemos facilmente extrair seus valores em variáveis nomeadas. 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 Os desconstrutores proporcionam uma maneira eficiente e limpa de acessar os componentes de um objeto. Eles se tornam especialmente úteis quando trabalhamos com objetos compostos ou tuplas, permitindo um código mais legível e conciso. Ao usar desconstrutores, podemos reduzir a complexidade do código, especialmente em cenários onde a extração de múltiplos valores de um objeto é frequente. Como vimos, a sintaxe é simples e pode melhorar significativamente a clareza do código. Se você estiver desenvolvendo em C# 7.0 ou superior, vale a pena explorar o uso de desconstrutores para tornar seu código mais expressivo e eficiente!
Trabalhando com tuplas em C#

No desenvolvimento de software, frequentemente nos deparamos com a necessidade de agrupar diferentes tipos de dados em uma única unidade, sem a necessidade de criar uma estrutura ou classe complexa. Nesse cenário, as tuplas se mostram uma solução bastante eficaz, permitindo armazenar múltiplos valores em uma única estrutura sem a sobrecarga de criar tipos personalizados como classes ou structs. O que são tuplas? Tuplas são estruturas de dados que permitem armazenar múltiplos valores de diferentes tipos em uma única entidade. Elas são leves, simples e eficientes, ideais para agrupar dados temporários ou relacionados de maneira direta. Ao contrário das classes ou structs, que exigem uma definição formal para cada tipo de dado, as tuplas oferecem uma maneira rápida e flexível de organizar informações sem a necessidade de complexidade adicional. Tuplas com Tuple – antes do C# 7 Antes da introdução das tuplas modernas no C# 7, a única forma de utilizar tuplas em C# era por meio da classe “Tuple<T>”. Ela foi introduzida no .NET Framework 4.0 e permitia armazenar até oito valores em uma única tupla, utilizando o tipo genérico “Tuple<T1, T2, …, T8>”, da seguinte forma: var pessoaTupla = new Tuple(1, “João”, true); Console.WriteLine(pessoaTupla.Item1); Console.WriteLine(pessoaTupla.Item2); Console.WriteLine(pessoaTupla.Item3); Limitações do “Tuple<T>”: Nomes de itens: os valores armazenados em uma “Tuple<T>” são acessados por propriedades como “Item1”, “Item2”, e assim por diante. Embora funcionais, esses nomes não são descritivos, o que pode levar a confusões sobre qual valor está sendo acessado, especialmente em tuplas com múltiplos itens. Imutabilidade: tuplas baseadas em “Tuple<T>” são imutáveis, ou seja, você não pode alterar os valores depois de criá-los. Tuplas no C# 7+ – Uso da sintaxe moderna Com a chegada do C# 7, a linguagem introduziu uma nova forma de trabalhar com tuplas, que são mais simples do que as antigas “Tuple<T>”. Agora, podemos usar a sintaxe de tuplas diretamente com a palavra-chave “var”, o que torna o código mais legível e intuitivo: var pessoaTupla = (1, “João”, true); Console.WriteLine(pessoaTupla.Item1); Console.WriteLine(pessoaTupla.Item2); Console.WriteLine(pessoaTupla.Item3); Nomeação de elementos de tuplas Para sanar o questão da nomenclatura dos itens, no C# 7 foi introduzida a capacidade de nomear os elementos das tuplas diretamente na declaração. Isso torna o código mais expressivo e fácil de entender. var pessoaTupla= (Id: 1, Nome: “João”, Ativo: true); Console.WriteLine(pessoaTupla.Id); Console.WriteLine(pessoaTupla.Nome); Console.WriteLine(pessoaTupla.Ativo); Com isso, é possível chamar a informação desejada pelo nome que foi definido. Inferência de tipos O C# 7 também introduziu a inferência de tipos, o que significa que você pode omitir o tipo da tupla ao usá-la, e o compilador infere os tipos com base nos valores atribuídos. var pessoaTupla= (1, “João”, true); Neste caso, o compilador infere que a tupla contém um “int”, uma “string” e um “bool”, simplificando a declaração sem a necessidade de especificar os tipos manualmente. Retorno de múltiplos valores em métodos Uma das utilizações mais comuns das tuplas é o retorno de múltiplos valores de um método. Antes da introdução das tuplas, se precisássemos retornar mais de um valor de um método, a solução seria criar uma classe ou struct para agrupar os valores. Com as tuplas, esse processo se torna simples e direto. Exemplo: public static (int, string) ObterDados() { return (1, “Valor retornado”); } var resultado = ObterDados(); Console.WriteLine(resultado.Item1); Console.WriteLine(resultado.Item2); Nomeação dos valores no retorno Ao retornar tuplas de métodos, também podemos nomear os elementos, tornando o código ainda mais legível: public static (int Id, string Nome) ObterDados() { return (1, “João”); } var resultado = ObterDados(); Console.WriteLine(resultado.Id); Console.WriteLine(resultado.Nome); Decomposição de tuplas A decomposição de tuplas permite extrair seus valores diretamente para variáveis, tornando o código mais limpo e fácil de entender. var minhaTupla = (1, “João”, true); var (id, nome, ativo) = minhaTupla; Console.WriteLine(id); Console.WriteLine(nome); Console.WriteLine(ativo); Essa forma de decomposição é bastante útil quando trabalhamos com tuplas retornadas de métodos. Boas práticas Embora as tuplas sejam bastante práticas, é importante usá-las com cautela para evitar confusão e manter a clareza no código. Aqui estão algumas boas práticas: Evite o uso excessivo de tuplas: quando o número de valores a ser retornado ou agrupado é grande ou a semântica é complexa, considere usar structs ou classes. Nomeie os elementos da tupla: sempre que possível, use a nomeação de elementos em tuplas para aumentar a legibilidade do código. Use tuplas para dados temporários: tuplas são ideais para valores temporários ou resultados rápidos, mas não devem ser usadas como tipos permanentes ou complexos. Conclusão As tuplas no C# evoluíram significativamente ao longo das versões do .NET, oferecendo uma forma mais simples e eficiente de agrupar múltiplos valores sem a necessidade de classes ou structs. Elas são ideais para cenários que exigem um agrupamento rápido e simples de dados temporários. Se usadas corretamente, as tuplas podem melhorar significativamente a legibilidade e performance do seu código, especialmente quando se trata de retornos múltiplos ou manipulação de dados temporários.
Paginação de dados em .NET: um guia completo

Quando precisamos lidar com um grande volume de dados a paginação se torna essencial, principalmente quando esses dados precisam ser apresentados ao usuário de forma eficiente e escalável. Sem uma implementação adequada de paginação, os sistemas podem enfrentar sérios problemas de desempenho e de experiência do usuário, especialmente ao carregar listas de milhares ou milhões de registros. Importância da paginação A paginação é fundamental para: Otimizar o desempenho: carregar um grande volume de dados de uma vez pode causar lentidão na aplicação e sobrecarga do servidor. A paginação permite carregar apenas uma parte dos dados por vez, o que melhora o desempenho e reduz a latência. Melhorar a experiência do usuário: a navegação por páginas proporciona uma interação mais fluida, sem necessidade de esperar por um grande conjunto de dados para ser carregado. Os usuários podem acessar os dados de forma mais rápida e sem esperar muito tempo. Reduzir o uso de recursos: ao carregar somente uma parte dos dados de cada vez, o uso de memória e processamento é significativamente reduzido, o que pode ser crítico em ambientes com muitos usuários simultâneos. Abordagens de paginação em .NET Neste artigo vamos ver três abordagens principais para implementar a paginação em uma aplicação .NET: paginação em banco de dados, paginação com Entity Framework e paginação manual em listas. Paginação em banco de dados com SQL A primeira abordagem de paginação é realizada diretamente no banco de dados. Utilizando comandos SQL, é possível carregar uma parte dos dados com base em parâmetros como “OFFSET” e “FETCH NEXT”, que permitem dividir os dados em páginas e evitar a sobrecarga de memória. public async Task GetPagedProductsAsync(int pageNumber, int pageSize) { var sql = @” SELECT * FROM Products ORDER BY ProductID OFFSET @Offset ROWS FETCH NEXT @PageSize ROWS ONLY; “; var offset = (pageNumber – 1) * pageSize; var products = await _dbConnection.QueryAsync(sql, new { Offset = offset, PageSize = pageSize }); } Aqui, o parâmetro “@PageNumber” representa o número da página que você deseja consultar e “@PageSize” representa o número de registros que serão retornados por página. A cláusula “OFFSET” é usada para determinar quantos registros serão ignorados antes de começar a retornar os resultados da consulta, com base no número da página e no tamanho da página. Usado um exemplo repassando a query com o Dapper, se você receber o “pageNumber = 1” e o “pageSize = 20”, a consulta trará os primeiros 20 registros. Para “pageNumber = 2”, a consulta irá pular os primeiros 20 registros e trará os 20 seguintes, ou seja, do 21º ao 40º registro. A cláusula “FETCH NEXT” é responsável por limitar o número de registros que serão retornados na consulta, baseando-se em “@PageSize”. Paginação com Entity Framework Quando utilizamos o Entity Framework (EF) para implementar paginação, a abordagem é mais abstrata e orientada a objetos em comparação com a paginação enviando diretamente o SQL para o banco de dados. Em vez de usar comandos SQL como “OFFSET” e “FETCH NEXT”, o EF fornece os métodos “Skip()” e “Take()” para realizar a paginação de maneira mais simples e intuitiva. public async Task GetPagedProductsAsync() { var pageNumber = 1; var pageSize = 15; var query = _context.Products.AsQueryable(); var items = await query .Skip((pageNumber – 1) * pageSize ) .Take(pageSize) .ToListAsync(); return items; } No exemplo apresentado, a consulta começa criando um “IQueryable” a partir da coleção de produtos (_context.Products), permitindo que você construa e manipule a consulta de maneira eficiente, sem realizar a execução imediata no banco de dados. O método “Skip()” é utilizado para ignorar os registros anteriores à página solicitada, ou seja, ele “pula” os registros de páginas anteriores. A fórmula usada para calcular o número de registros a serem ignorados é “(pageNumber – 1) * pageSize”, onde “pageNumber” é o número da página e “pageSize” é o número de registros por página. Já o método “Take()” limita a quantidade de registros retornados para o número de itens da página atual (definido por “pageSize”). Paginação manual em listas Quando os dados já estão carregados em memória, pode ser necessário implementar a paginação manual em listas, sem o uso de banco de dados. Isso ocorre frequentemente quando você tem uma coleção de dados já disponível, como uma lista em memória, e deseja dividi-la em páginas para melhorar a apresentação ou a navegação. Esse método é útil quando os dados não são tão grandes e podem ser manipulados diretamente na memória, evitando a necessidade de consultas adicionais ao banco de dados. public Products GetPagedProducts(List products, int pageNumber, int pageSize) { var totalItems = products.Count; var items = products.Skip((pageNumber – 1) * pageSize) .Take(pageSize) .ToList(); var totalPages = (int)Math.Ceiling((double)totalItems / pageSize); return new Products { Items = items, CurrentPage = pageNumber }; } No exemplo fornecido, a função “GetPagedProducts” recebe uma lista de produtos (List<Product> products), o número da página (pageNumber) e o tamanho da página (pageSize). Primeiro, o total de itens na lista é calculado usando o método “Count()”. Vamos usar esse valor para calcular a quantidade de páginas mais à frente. Em seguida, “Skip()” é usado para pular os itens das páginas anteriores, calculando a quantidade de itens a serem ignorados com base no número da página e no tamanho da página: “(pageNumber – 1) * pageSize”. Após isso, o método “Take()” é utilizado para retornar os itens correspondentes à página atual, ou seja, os registros que serão exibidos naquela página. O número total de páginas é calculado dividindo o total de itens (totalItems) pela quantidade de itens por página (pageSize) e arredondando para cima com “Math.Ceiling()”, para garantir que uma página extra seja retornada quando o total de registros não for divisível perfeitamente pelo tamanho da página. Por fim, o método retorna uma lista de “Product”, que contém os itens da página atual, e o número da página atual + 1 para que na próxima requisição seja enviada a próxima página. Isso proporciona uma estrutura organizada para a paginação. Criando uma API paginada em ASP.NET Core Agora vamos demonstrar como criar uma API paginada utilizando
Extension Methods em C#: como estender classes e otimizar seu código

Extension Methods são uma funcionalidade do C# que permite expandir o comportamento de classes já existentes sem modificá-las diretamente ou usar herança. Essa técnica se mostra especialmente útil quando o código-fonte original não está disponível ou quando se busca preservar uma arquitetura de código limpa e desacoplada. Neste artigo você aprenderá o que são extension methods, como criá-los, utilizá-los de maneira eficaz e aplicá-los em casos práticos. O que são extension methods? Extension Methods são métodos estáticos que permitem “estender” a funcionalidade de tipos existentes. Eles podem ser aplicados tanto a tipos definidos pelo usuário quanto a tipos internos do .NET, como “string”, “List<T>”, “DateTime”, entre outros. A grande vantagem dos extension methods é que eles são invocados como se fossem métodos de instância, melhorando a legibilidade do código. Como criar um extension method Regras básicas Para criar um extension method, você precisa seguir algumas regras: Ele deve estar em uma classe estática. O próprio método também deve ser estático. O primeiro parâmetro do método deve usar a palavra-chave “this”, seguida do tipo que você deseja estender. Estrutura básica Vamos adicionar o método “InverterString” para inverter uma string: public static class ExtensoesDeString { public static string InverterString(this string valor) { return new string(valor.ToCharArray().Reverse().ToArray()); } } Aqui, o “this string valor” indica que estamos estendendo a classe “string”. Com isso, qualquer instância de “string” pode chamar esse método diretamente. Utilizando o método criado Depois de criar um extension method, você pode utilizá-lo de forma muito simples: string texto = “NextWaveEducation”; Console.WriteLine(texto.InverterString()); Note que o método “InverterString()” aparece no IntelliSense (Ctrl + Espaço) como se fosse um método nativo da classe string, tornando o código mais limpo e intuitivo. Boas práticas com extension methods Quando usar Quando você precisa adicionar funcionalidades sem modificar a classe original. Para encapsular códigos repetitivos e melhorar a legibilidade. Para fornecer funções auxiliares que fazem sentido dentro do contexto de um tipo. Quando evitar Evite criar extension methods para funcionalidades triviais que poderiam ser implementadas diretamente na classe original. Evite extension methods que alterem o estado interno do objeto estendido, pois isso pode tornar o comportamento inesperado. Nomeação clara Use nomes intuitivos que sigam as convenções do C#. Certifique-se de que o nome do método indica claramente sua funcionalidade. Não crie métodos com nomes que possam causar confusão com métodos nativos. Caso de uso real Aqui está um exemplo que verifica se uma data é um fim de semana: public static class ExtensoesDeData { public static bool EhFimDeSemana(this DateTime data) => data.DayOfWeek is DayOfWeek.Saturday or DayOfWeek.Sunday; } 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 Extension Methods são uma ferramenta valiosa para melhorar a produtividade e a organização do código em C#. Eles permitem que você adicione funcionalidades de maneira elegante e eficiente, tornando o código mais limpo e legível. Pratique criando seus próprios extension methods e aplique-os em seus projetos para ver os benefícios em ação.
Utilizando indexadores em classes C#

Em C#, indexadores são recursos que permitem que objetos de classes sejam acessados de maneira semelhante a arrays ou coleções, utilizando a sintaxe de índices (os colchetes []). Essa funcionalidade torna o código mais intuitivo e legível, oferecendo uma forma concisa de manipular dados armazenados em objetos de classes personalizadas. Neste artigo vamos explorar o conceito de indexadores, como eles são declarados e implementados, e como podem ser usados para criar coleções personalizadas ou facilitar o acesso a dados. O que são indexadores? Indexadores são membros de uma classe ou struct que permitem acessar elementos de um objeto usando a sintaxe de índice. Embora a sintaxe de índices seja comumente associada a arrays e coleções, indexadores permitem que você ofereça esse comportamento em suas próprias classes. A declaração de um indexador em C# envolve o uso da palavra-chave “this”, seguida de um ou mais parâmetros entre colchetes, representando o índice. Esses parâmetros determinam como o acesso aos elementos é feito. Como declarar e implementar indexadores A sintaxe básica para declarar um indexador é a seguinte: public tipo this[tipos índices] { get { … } set { … } } Aqui, tipo é o “tipo” de dado que o indexador irá acessar (ou seja, o tipo do valor que será retornado ou atribuído), e “tipos índices” são os parâmetros que o indexador aceita (frequentemente usados para representar um índice ou chave). Para entendermos melhor, vamos supor que você queira criar uma classe “MinhaColecao” que armazena uma lista de inteiros. Se fizermos da forma tradicional para acessar as informações de dados, poderíamos implementar algo assim: public class MinhaColecao { private int[] dados; public MinhaColecao(int tamanho) { dados = new int[tamanho]; } public int GetValor(int index) { return dados[index]; } public void SetValor(int index, int valor) { dados[index] = valor; } } O acesso aos elementos seria feito chamando métodos como “GetValor” e “SetValor”: var colecao = new MinhaColecao(5); colecao.SetValor(0, 10); colecao.SetValor(1, 20); Console.WriteLine(colecao.GetValor(0)); Console.WriteLine(colecao.GetValor(1)); Embora funcional, essa abordagem exige a utilização explícita de métodos para acessar e modificar os dados, o que pode tornar o código mais verboso. Mas usando um indexador, podemos fazer da seguinte forma: public class MinhaColecao { private int[] dados; public MinhaColecao(int tamanho) { dados = new int[tamanho]; } public int this[int index] { get { return dados[index]; } set { dados[index] = value; } } } Com o indexador, o acesso aos elementos fica mais intuitivo e direto: var colecao = new MinhaColecao(5); colecao[0] = 10; colecao[1] = 20; Console.WriteLine(colecao[0]); Console.WriteLine(colecao[1]); Perceba que a sintaxe de índices (colecao[0]) torna o código mais limpo e fácil de entender, eliminando a necessidade de métodos adicionais para acesso aos dados. O indexador age como uma ponte que simplifica a interação com os elementos da classe. Boas práticas de design para indexadores Evitar Ambiguidade: Se o seu indexador tiver múltiplos parâmetros ou tipos de índice diferentes, tome cuidado para não criar ambiguidade. Isso pode tornar o código confuso e difícil de manter. public string this[int index, string chave] { get; set; } Esse tipo de indexador pode causar confusão se você não tiver uma necessidade clara para dois parâmetros diferentes. Em vez disso, considere usar métodos ou propriedades para separar as responsabilidades. Validação: Sempre que possível, valide os índices ou as chaves antes de acessar os dados. Isso ajuda a evitar exceções inesperadas no seu código. public int this[int index] { get { if (index < 0 || index >= dados.Length) { throw new IndexOutOfRangeException(“Índice fora dos limites da coleção.”); } return dados[index]; } set { if (index < 0 || index >= dados.Length) { throw new IndexOutOfRangeException(“Índice fora dos limites da coleção.”); } if (value < 0) { throw new ArgumentException("O valor não pode ser negativo."); } dados[index] = value; } } Consistência: Certifique-se de que as operações de “get” e “set” sigam a mesma lógica de acesso. Isso garante que o comportamento do indexador seja consistente e previsível. Desempenho: Em casos em que o acesso aos dados é frequentíssimo, tome cuidado com o impacto de validações ou transformações dentro do indexador. Tente manter o código de “get” e “set” o mais simples possível para não impactar a performance. Imutabilidade: Se o seu objeto deve ser imutável, lembre-se de que um indexador de leitura (get) sem set pode ser uma boa escolha. Isso garante que os dados não sejam alterados após a criação do objeto. Exemplo prático: usando indexadores com dicionário personalizado Agora, vamos ver um exemplo prático de como usar um indexador para criar um dicionário personalizado, onde as chaves são strings e os valores são inteiros. public class MeuDicionario { private Dictionary dados = new Dictionary(); public int this[string chave] { get { if (!dados.ContainsKey(chave)) throw new KeyNotFoundException("Chave não encontrada."); return dados[chave]; } set { dados[chave] = value; } } } Usando o “MeuDicionario” var dicionario = new MeuDicionario(); dicionario["um"] = 1; dicionario["dois"] = 2; Console.WriteLine(dicionario["um"]); Console.WriteLine(dicionario["dois"]); Neste exemplo, a classe “MeuDicionario” oferece acesso a dados por meio de chaves de tipo string, permitindo armazenar e recuperar valores de maneira simples e eficiente. 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 Os indexadores em C# oferecem uma maneira prática e intuitiva de acessar dados em objetos, similar ao acesso a elementos de arrays ou coleções. Eles permitem criar classes que encapsulam dados de maneira eficiente, mantendo o código limpo e legível. Ao utilizar indexadores, é importante aplicar boas práticas, como validação de índices e evitar ambiguidades, para garantir que o design do seu código seja fácil de manter.
Índices e intervalos em arrays no C#

Os arrays são estruturas fundamentais em C#, permitindo armazenar e manipular coleções de elementos do mesmo tipo. A partir do C# 8.0, novas funcionalidades foram introduzidas para tornar o trabalho com arrays ainda mais eficiente. Neste artigo vamos explorar desde os índices tradicionais até as últimas novidades como índices negativos com o operador “^” e intervalos “(..)” para criar subarrays. Índices tradicionais em arrays Nos arrays, os índices são utilizados para acessar elementos individuais. Eles começam do zero, ou seja, o primeiro elemento está no índice 0, o segundo no índice 1, e assim por diante. int[] numeros = { 10, 20, 30, 40, 50 }; Console.WriteLine(numeros[0]); Console.WriteLine(numeros[2]); Se você tentar acessar um índice fora dos limites do array (numeros[5]), uma exceção “IndexOutOfRangeException” será lançada. Índices negativos com o operador ^ Com o C# 8.0, foi introduzido o operador “^” para trabalhar com índices relativos ao final do array. Isso simplifica o acesso ao último elemento ou a elementos próximos ao fim. int[] numeros = { 10, 20, 30, 40, 50 }; Console.WriteLine(numeros[^1]); Console.WriteLine(numeros[^2]); Aqui, o número após o operador “^” indica a posição a ser acessada de trás para frente no array. No exemplo acima, o console exibirá os valores 50 (último elemento) e 40 (penúltimo elemento). Intervalos com o operador .. O operador “..” permite criar subarrays (fatias ou slices) de maneira concisa. Ele utiliza dois índices, um inicial e um final, separados por “..”. Regras para usar o operador .. Ao definir um intervalo como [inicial..final], você especifica onde o intervalo começa e termina. int[] numeros = { 10, 20, 30, 40, 50 }; int[] fatia1 = numeros[1..3]; Console.WriteLine(string.Join(“, “, fatia1)); Nesse exemplo, a fatia inclui os elementos “numeros[1]” (20) e “numeros[2]” (30), mas exclui o elemento “numeros[3]”. Se você omitir o índice inicial (..final), o intervalo começa no primeiro elemento do array e vai até o índice especificado (não incluído). int[] fatia2 = numeros[..3]; Console.WriteLine(string.Join(“, “, fatia2)); O intervalo “[..3]” inclui os elementos “numeros[0]”, “numeros[1]” e “numeros[2]”. Se você omitir o índice final (inicial..), o intervalo vai do índice inicial especificado até o último elemento do array. int[] fatia3 = numeros[2..]; Console.WriteLine(string.Join(“, “, fatia3)); Nesse caso, o intervalo “[2..]” inclui todos os elementos a partir de “numeros[2]” até o final do array. Se você omitir tanto o índice inicial quanto o final (..), o intervalo abrange o array inteiro. Essa sintaxe equivale a criar uma cópia completa do array. int[] fatiaCompleta = numeros[..]; Console.WriteLine(string.Join(“, “, fatiaCompleta)); Combinação com o operador ^ O operador “^” pode ser combinado com “..” para criar intervalos relativos ao final do array. Isso é útil para trabalhar com os últimos elementos de uma coleção. int[] ultimos = numeros[^3..]; Console.WriteLine(string.Join(“, “, ultimos)); O [ ^3.. ] começa no terceiro elemento a partir do final e vai até o final do array. A saída será 30, 40, 50. int[] semOsUltimos = numeros[..^2]; Console.WriteLine(string.Join(“, “, semOsUltimos)); O [ ..^2 ] vai do início do array até dois elementos antes do final. O resultado será 10, 20, 30. Combinando intervalos com métodos de extensão Uma outra possibilidade do C# é a de combinar intervalos com métodos de extensão como “Where”, “Select”, e outros disponíveis no LINQ. Isso permite criar filtros e transformações dinâmicas diretamente sobre fatias de arrays. Você pode usar o operador “..” para criar um subarray e aplicar um filtro dinâmico com o método “Where”: int[] numeros = { 10, 15, 20, 25, 30 }; var pares = numeros[1..4].Where(n => n % 2 == 0); Console.WriteLine(string.Join(“, “, pares)); O intervalo “[1..4]” seleciona os elementos do índice 1 ao índice 3 (15, 20, 25). Após isso, o método “Where” filtra os elementos que são pares. A saída resulta em 20. Além de filtrar, você pode transformar os valores de um intervalo utilizando o método “Select”: int[] numeros = { 10, 15, 20, 25, 30 }; var dobrados = numeros[2..].Select(n => n * 2); Console.WriteLine(string.Join(“, “, dobrados)); Neste exemplo, o intervalo “[2..]” seleciona todos os elementos a partir do índice 2 (20, 25, 30). O método “Select” aplica a transformação “n * 2” a cada elemento do intervalo. O resultado será uma nova lista com os números 40, 50, 60 armazenados na variável “dobrados”. 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 Índices e intervalos negativos em C# oferecem novas possibilidades para manipular arrays e coleções de forma simples e eficiente. Esses recursos tornam o código mais expressivo, eliminando a necessidade de cálculos manuais de índice. Além disso, intervalos e índices negativos podem ser combinados com outros recursos modernos de C# para criar soluções robustas e claras. Explore essas funcionalidades em seus projetos e veja como elas podem simplificar suas implementações!
Integre suas aplicações .NET com o DeepSeek

Recentemente, a start-up chinesa DeepSeek tem ganhado destaque no campo da inteligência artificial com seu revolucionário chatbot de IA generativa. O modelo da DeepSeek alcançou um nível de desempenho comparável aos de gigantes como OpenAI e Anthropic, mas com um custo significativamente mais baixo. Neste artigo, iremos explorar como integrar a API DeepSeek em uma aplicação .NET, mostrando passo a passo como configurar, enviar e processar mensagens para criar uma experiência de bate-papo inteligente e fluída. Se você deseja adicionar funcionalidades de conversação inteligente ao seu projeto, este guia prático é o ponto de partida perfeito. Criando uma conta no DeepSeek Acesse o site oficial do DeepSeek. Preencha o formulário com seu e-mail e senha. Após criar sua conta, faça login e vá até a seção API Keys no painel de controle e clique em “Create new API Key”, copie e guarde a chave gerada. Configurando a API Key Agora, no código que vamos escrever, você deve inserir sua chave de API gerada no DeepSeek. No exemplo abaixo, a chave de API é armazenada diretamente na variável apiKey. Em aplicações reais, evite armazenar a chave diretamente no código fonte. Em vez disso, você pode armazená-la em uma variável de ambiente ou em um arquivo de configuração. public class Program { private static readonly string apiKey = “Sua-chave”; private static readonly string baseUrl = “https://api.deepseek.com/chat/completions”; public static async Task Main(string[] args) { //nosso código } } Função principal para envio de mensagens Na “Main”, começamos inicializando a lista de mensagens e configuramos o sistema para enviar as mensagens ao modelo de chat da API DeepSeek. A API requer um formato específico de dados para a comunicação. Aqui, o código define um ponto de entrada para a conversa, onde o sistema envia uma mensagem de saudação inicial ao DeepSeek: Console.WriteLine(“Iniciando conversa com o DeepSeek…”); var messages = new List(); messages.Add(new { role = “system”, content = “Você é um assistente inteligente e amigável.” } Esse código simula uma conversa onde o “role” define quem está enviando a mensagem. Já o “content” armazena qual o comportamento do assistente. Leitura da entrada do usuário e envio de solicitações Agora vamos criar um “while” que vai continuar pedindo mensagens ao usuário e as envia para o DeepSeek. Caso o usuário queira parar o “while” ele teve enviar uma mensagem vazia: while (true) { //…código do processamento aqui } Os próximos blocos de código devem ser inseridos dentro do while(true). Console.Write(“Você: “); string userMessage = Console.ReadLine(); if (string.IsNullOrEmpty(userMessage)) break; Após escrever a mensagem, nós vamos adicionar ela na lista de mensagens: messages.Add(new { role = “user”, content = userMessage }); Em seguida, vamos montar o corpo da nossa requisição para enviar a solicitação ao DeepSeek. Essa requisição vai conter todas as mensagens trocadas até aquele momento (usuário e assistente): var requestData = new { model = “deepseek-chat”, messages = messages.ToArray(), stream = false }; Enviando a solicitação HTTP para a API Agora vamos usar a classe “HttpClient” para enviar uma requisição POST: using var httpClient = new HttpClient(); string jsonRequest = JsonSerializer.Serialize(requestData); var content = new StringContent(jsonRequest, Encoding.UTF8, “application/json”); httpClient.DefaultRequestHeaders.Add(“Authorization”, $”Bearer {apiKey}”); var response = await httpClient.PostAsync(baseUrl, content); Aqui, a chave de API é passada no cabeçalho da requisição através do cabeçalho “Authorization”. Tratando a resposta da API Após isso, é necessário analisar a resposta da API e extrair a resposta do modelo. A API DeepSeek retorna a resposta em um formato JSON, e usamos “JsonDocument” para processá-la: var responseBody = await response.Content.ReadAsStringAsync(); using JsonDocument jsonDoc = JsonDocument.Parse(responseBody); string contentText = jsonDoc .RootElement .GetProperty(“choices”)[0] .GetProperty(“message”) .GetProperty(“content”) .GetString() O resultado será apenas a resposta do modelo sem os demais dados. Apresentando as informações e armazenando a nova mensagem Por fim, podemos apresentar a resposta do modelo na tela e adicionar a mensagem recebida na lista de mensagens e continuando no ‘while’: Console.WriteLine($”DeepSeek: {contentText }”); messages.Add(new { role = “assistant”, content = contentText }); Na imagem abaixo podemos ver uma demonstração da aplicação em funcionamento: 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 Neste artigo, nós vimos como integrar com a API DeepSeek em um aplicativo .NET para criar uma aplicação de bate-papo simples. Com base nesse código, você pode expandir a funcionalidade para criar interfaces gráficas, armazenar o histórico de mensagens em um banco de dados, ou até mesmo integrar com outras APIs ou sistemas.
Crie um assistente virtual em .NET com a API da OpenAI

Com a crescente demanda por soluções inteligentes, integrar assistentes de IA em aplicações tornou-se uma oportunidade valiosa para desenvolvedores. Para isso, a OpenAI ampliou suas capacidades com a API Assistants, permitindo a criação de assistentes personalizados com funcionalidades avançadas. Neste artigo, exploraremos como utilizar essa API, desde a configuração inicial até exemplos práticos. Quais as diferenças entre a API do ChatGPT e a API Assistants? A API Assistants e a API tradicional do ChatGPT compartilham a base tecnológica dos modelos avançados da OpenAI, mas possuem abordagens distintas quanto à funcionalidade e ao uso: Personalização e instruções específicas: ChatGPT: possui um foco mais direto em responder perguntas e participar de conversas com base nas mensagens enviadas. Assistants: permite configurar assistentes com instruções específicas para definir a personalidade e os limites do assistente, ajustando-o para diferentes casos de uso. Threads persistentes: ChatGPT: não armazena o histórico de conversas diretamente. Assistants: trabalha com Threads que armazenam o histórico de mensagens, possibilitando a continuidade das interações mesmo após múltiplas consultas. Manipulação de arquivos: ChatGPT: não interage diretamente com arquivos. Assistants: suporta o acesso e manipulação de arquivos em diversos formatos, além de citar arquivos e criar novos durante a execução de tarefas. Configuração inicial Para fins didáticos, utilizaremos aqui uma aplicação do tipo Console. Então, para começar, vamos organizar a estrutura básica da nossa aplicação de forma clara: public class Program { private static readonly string apiKey = “”; // Substitua com sua chave de API private static readonly string apiUrl = “https://api.openai.com/v1″; private static string assistantId; private static string threadId; public static async Task Main(string[] args) { try { //Implementação do nosso código. } catch (Exception ex) { Console.WriteLine($”Erro geral: {ex.Message}”); } } private static async Task CriarAssistente() { } private static async Task CriarThread() { } private static async Task EnviarMensagem(string assistantId, string threadId, string Mensagem) { } private static async Task BuscarMensagens(string threadId) { } } Dentro da classe “Program” nós criamos quatro variáveis. Na variável “apiKey” deve ser armazenada a chave de API da OpenAI. Caso tenha dúvidas de como conseguir essa chave, você pode ver o passo a passo neste tutorial. A URL base (apiUrl) é fixa e aponta para o endpoint de interações com assistentes. A variável “assistantId” vai armazenar o id da nossa assistente e a “threadId” vai armazenar o id da thead que vamos criar. O método principal será a “Main”, responsável por iniciar a execução do sistema e controlar o fluxo principal da interação com a API da OpenAI. Além dela, contaremos com quatro métodos que desempenham funções específicas: CriarAssistente: Este método será responsável por enviar uma requisição para a criação de um assistente virtual. Ele configurará parâmetros como instruções iniciais e o modelo que será utilizado. CriarThread: Após a criação do assistente, será necessário criar uma thread associada a ele, onde as mensagens e respostas serão organizadas. Este método realiza essa tarefa. EnviarMensagem: Uma vez que a thread estiver configurada, este método permitirá enviar mensagens para o assistente virtual, incluindo instruções e dados adicionais conforme necessário. BuscarMensagens: Este método será responsável por recuperar as mensagens da thread, exibindo as respostas geradas pelo assistente. Criando o assistente virtual A primeira etapa para interagir com a API do OpenAI é criar um assistente. Vamos definir algumas configurações iniciais no método “CriarAssistente”: private static async Task CriarAssistente() { using var client = new HttpClient(); var data = new { instructions = “Você é um professor de matemática que auxilia seus alunos.”, name = “Professor”, tools = new List { new { type = “file_search” }, new { type = “code_interpreter” } }, model = “gpt-4-turbo” }; //…continua } Aqui nós criamos uma instância de “HttpClient” para realizarmos as requisições. Após isso, montamos o objeto da nossa requisição que vai conter a instrução do que nossa assistente deve fazer, o seu nome, uma lista de ferramentas que o assistente pode usar como pesquisa de arquivos e interpretação de código. E por fim, o modelo que usaremos. Ainda dentro do método “CriarAssistente”, vamos montar a nossa requisição e envia-la: var request = new HttpRequestMessage(HttpMethod.Post, $”{apiUrl}/assistants”) { Content = new StringContent(JsonSerializer.Serialize(data), Encoding.UTF8, “application/json”) }; request.Headers.Add(“Authorization”, $”Bearer {apiKey}”); request.Headers.Add(“OpenAI-Beta”, “assistants=v2”); var response = await client.SendAsync(request); Após realizar o envio da requisição, vamos ler a informação que foi retornada: var result = await response.Content.ReadAsStringAsync(); using var doc = JsonDocument.Parse(result); return doc.RootElement.GetProperty(“id”).GetString(); No código acima, após realizarmos a leitura da nossa resposta, vamos armazená-la em “result”. Após isso, vamos transformar o valor recebido em um JSON para permitir o acesso estruturado aos seus dados. O código então vai navegar até a raiz do documento (RootElement) e extrair o valor associado à propriedade “id”, convertendo-o em uma string com “GetString()”. Com isso, conseguimos capturar o id da nossa assistente. Criando a thread de conversa Após criar o assistente, precisamos de uma “thread” (ou conversa) onde as interações acontecerão. private static async Task CriarThread() { using var client = new HttpClient(); var data = new { metadata = new { project = “AulasMatematica” } }; //Aqui o objeto “data” vai conter as informações do projeto que você está criando. var request = new HttpRequestMessage(HttpMethod.Post, $”{apiUrl}/threads”) { Content = new StringContent(JsonSerializer.Serialize(data), Encoding.UTF8, “application/json”) }; request.Headers.Add(“Authorization”, $”Bearer {apiKey}”); request.Headers.Add(“OpenAI-Beta”, “assistants=v2”); var response = await client.SendAsync(request); var result = await response.Content.ReadAsStringAsync(); using var doc = JsonDocument.Parse(result); return doc.RootElement.GetProperty(“id”).GetString(); } O código acima vai seguir o mesmo fluxo da criação da assistente, retornando o id da thread que foi criada. Enviando mensagens para o assistente Agora que temos o assistente e a thread criados, podemos enviar mensagens para o assistente da seguinte forma: private static async Task SendMessageAsync(string assistantId, string threadId, string mensagem) { using var client = new HttpClient(); var data = new { assistant_id = assistantId, instructions = “Responda apenas às perguntas referentes a matemática. Caso seja falado sobre outros assuntos, responda educadamente para voltar para assuntos relacionados a matemática.”, additional_messages = new[] { new { role = “user”, content = mensagem} } }; //…continua } Aqui, a variável “data” guarda o “assistantId” do assistente que
Domine o yield em C#: iteração sob demanda e eficiência de memória

Em aplicações que lidam com grandes volumes de dados, pode ser necessário começar a retornar resultados antes que todo o processamento seja concluído. Isso é especialmente útil para evitar o consumo excessivo de memória e melhorar o desempenho da aplicação. No C#, a palavra-chave “yield” é uma ferramenta bastante útil para lidar com essa necessidade, permitindo a criação de iteradores que geram valores sob demanda. Neste artigo exploraremos como “yield return” e “yield break” funcionam, sua aplicação prática e os benefícios no gerenciamento de coleções grandes. O que é yield? Em C#, “yield” é usado dentro de métodos iteradores para retornar elementos um por um, sem precisar armazenar todos os itens na memória. A palavra-chave “yield return” permite que um método produza uma sequência de valores sob demanda, enquanto “yield break” encerra a iteração antecipadamente. Como yield funciona internamente em métodos iteradores Quando um método contém “yield return”, o compilador gera automaticamente um iterador que preserva o estado entre as chamadas subsequentes. Isso significa que os valores são calculados sob demanda, permitindo uma iteração eficiente em memória. yield return Para entendermos melhor vamos ver um exemplo prático: foreach (var numero in GeradorNumeros(5)) { Console.WriteLine(numero); } IEnumerable GeradorNumeros(int quantidade) { for (int i = 1; i