O LINQ transformou o desenvolvimento em C#, proporcionando uma sintaxe poderosa para consulta e manipulação de dados. Neste artigo exploraremos os métodos do LINQ que retornam um único objeto, ideais para simplificar e melhorar a legibilidade de consultas que exigem a recuperação de um elemento específico de uma coleção. Para métodos que retornam mais de um elemento, recomendamos nosso artigo sobre “3 métodos essenciais para um desenvolvedor .NET“.
Para exemplificar a aplicação dos métodos, vamos utilizar como modelo de dados uma classe chamada “Produto”, cujo código é o seguinte:
public class Produto
{
public int Id { get; set; }
public string Nome { get; set; }
public decimal Preco { get; set; }
}
Em seguida, vamos criar uma lista chamada “produtos”, que funcionará como nossa base de dados, e adicionar cinco objetos:
List produtos = new List
{
new Produto { Id = 1, Nome = "Notebook", Preco = 1200 },
new Produto { Id = 2, Nome = "Tablet", Preco = 400 },
new Produto { Id = 3, Nome = "Smartphone", Preco = 800 },
new Produto { Id = 4, Nome = "Monitor", Preco = 300 },
new Produto { Id = 5, Nome = "Teclado", Preco = 50 }
};
O método “First” do LINQ é utilizado para retornar o primeiro elemento de uma sequência que satisfaz uma condição especificada. Caso nenhuma condição seja passada, ele retorna o primeiro elemento da sequência. Abaixo vemos as duas possibilidades de uso:
Produto primeiroElemento = produtos.First();
Produto primeiroElementoCorrespondente = produtos.First(p => p.Preco < 500);
Nesse exemplo, a variável “primeiroElemento” receberá o produto “Notebook” que é o primeiro elemento da lista “produtos”. Já a variável “primeiroElementoCorrespondente” receberá o item “Tablet”, que é o primeiro item que possui um valor abaixo de R$ 500. Note que os itens “Monitor” e “Teclado” também possuem valor abaixo de R$ 500, mas como o método “First” encontrou um item que satisfaz a condição, ele imediatamente retorna esse item e interrompe a busca, não sendo necessário verificar os demais.
É importante notar que, se nenhum elemento da lista satisfizer a condição ou se a lista estiver vazia, o método “First” lançará uma exceção do tipo “InvalidOperationException”. Portanto, é uma boa prática usar tratamento de exceções para lidar com esses casos e evitar falhas inesperadas no programa. Isso também vale para os métodos “Single” e “Last” que vamos falar mais a frente.
Podemos implementar o seguinte código para tratar as exceções:
try
{
Produto primeiroElementoCorrespondente = produtos.First(p => p.Preco > 2000);
}
catch (InvalidOperationException ex)
{
Console.WriteLine("Nenhum elemento encontrado: " + ex.Message);
}
Nesse exemplo, a lista não contém nenhum item com o preço acima de R$ 2.000. Ao tentar recuperar o primeiro elemento que corresponde a condição com “First”, uma exceção “InvalidOperationException” é lançada, que é então capturada e tratada no bloco “catch”, exibindo uma mensagem apropriada.
Uma alternativa ao “First” é o método “FirstOrDefault”, que se a sequência estiver vazia ou nenhum elemento satisfizer a condição, ele retorna para tipos de referência como “string” ou objetos, o valor padrão “null” e para tipos de valor como “int”, “bool”, o valor padrão é “0” para “int” e “false” para “bool”. Isso pode ser útil para evitar a necessidade de tratamento de exceções em alguns casos:
Produto primeiroElementoCorrespondente = produtos.FirstOrDefault(p => p.Preco > 2000);
Nesse exemplo, ao invés de retornar uma exceção, será retornado um valor “null”.
A partir do .NET 6, é possível definir um valor padrão diretamente no método “FirstOrDefault”, o que facilita o tratamento do retorno:
Produto primeiroElementoCorrespondente = produtos.FirstOrDefault(p => p.Preco > 2000, new Produto { Id = 0, Nome = "Produto não encontrado", Preco = 0 });
Nesse exemplo, se não for encontrado um item que possua o valor acima de R$ 2.000, a variável “primeiroElementoCorrenspondente” ao invés de receber um valor “null”, ela receberá um item com “Id” e “Preco” iguais a “0” e com o “Nome” igual a “Produto não encontrado”.
O método “Single” é bastante semelhante ao “First”. Ele também retorna um único elemento de uma sequência que satisfaz uma condição especificada. Se não for encontrado nenhum elemento que satisfaça a condição, será lançada uma exceção do tipo “InvalidOperationException”, com a mensagem “A sequência não contém nenhum elemento correspondente”. Porém, diferente do “First”, que retorna apenas o primeiro elemento caso existam múltiplos elementos que satisfaçam a condição, o “Single” vai retornar uma exceção também do tipo: “InvalidOperationException”, mas com a mensagem “A sequência contém mais de um elemento correspondente”. Ou seja, esse método deve ser usado quando se espera que exista apenas um único elemento que satisfaz a condição especificada.
Produto produtoId1 = produtos.Single(p => p.Id == 1);
Produto primeiroElementoCorrespondente = produtos.Single(p => p.Preco > 500);
No exemplo acima, a variável “produtoId1” receberá o produto “Notebook”, que o único que corresponde à condição de Id == 1. Já no segundo caso, o método “Single” vai buscar um elemento que possua o valor acima de R$ 500 na lista de “produtos” e vai encontrar os itens “Notebook” e “Smartphone”. Por conta disso, uma exceção será lançada.
O método “SingleOrDefault” funciona de forma semelhante ao “Single”, mas, em vez de lançar uma exceção quando nenhum elemento satisfaz a condição, ele retorna o valor padrão para o tipo especificado.
Produto primeiroElementoCorrespondente = produtos.SingleOrDefault(p => p.Preco > 2000);
No exemplo acima, “primeiroElementoCorrespondente ” será “null”, pois não há nenhum elemento com o valor acima de R$ 2.000 na lista. O método “SingleOrDefault” também permite a adição de um valor padrão caso o elemento não exista na lista.
Produto primeiroElementoCorrespondente = produtos.SingleOrDefault(p => p.Preco > 2000, new Produto { Id = 0, Nome = "Produto não encontrado", Preco = 0 });
Isso é útil para retornar um valor padrão para indicar a ausência de resultados. Isso evita exceções ou comportamentos inesperados na aplicação.
O método “Last” do LINQ é utilizado para retornar o último elemento de uma sequência que satisfaz uma condição especificada. Se nenhuma condição for passada, ele retorna o último elemento da sequência. Caso nenhum elemento cumpra a condição, uma exceção do tipo “InvalidOperationException” será lançada.
Este método é útil quando você precisa obter o elemento final de uma coleção, seja ela ordenada ou não
Produto ultimoElemento = produtos.Last(p => p.Preco > 500);
Neste exemplo, a variável “ultimoElemento” receberá o valor “Smartphone”, que é o último elemento da lista que tem o valor acima de R$ 500.
Uma alternativa ao “Last” é o método “LastOrDefault”, que retorna o valor padrão do tipo de elemento da sequência se a sequência estiver vazia ou se nenhum elemento satisfizer a condição especificada.
Produto ultimoElementoCorrespondente = produtos.LastOrDefault(p => p.Preco > 2000);
Nesse exemplo, “ultimoElementoCorrespondente” será “null”, pois não há nenhum produto na lista que tem o valor acima de R$ 2.000.
Também é possível adicionar um valor padrão a ser retornado se a condição não for atendida:
Produto primeiroElementoCorrespondente = produtos.LastOrDefault(p => p.Preco > 2000, new Produto { Id = 0, Nome = "Produto não encontrado", Preco = 0 });
Nesse exemplo, se um item com o valor acima de R$ 2.000 não for encontrado, “ultimoElementoCorrespondente” será um produto com o “Nome” “Produto não encontrado”.
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
Em suma, os métodos do LINQ para recuperação de registros únicos oferecem flexibilidade e robustez para desenvolvedores C#. O uso de métodos como “First”, “FirstOrDefault”, “Single”, “SingleOrDefault”, “Last” e “LastOrDefault” simplifica a obtenção precisa de elementos de coleções, promovendo legibilidade e eficiência no código. Contudo, é crucial entender suas diferenças e aplicabilidades específicas para evitar exceções e garantir a integridade dos dados.