Ce face FirstOrDefault / SingleOrDefault
În LINQ există patru metode principale pentru extragerea unui element individual dintr-o secvență: First, FirstOrDefault, Single și SingleOrDefault. Înțelegerea diferenței dintre ele este crucială pentru a evita excepțiile neașteptate în producție. Toate patru sunt metode de execuție imediată (terminală): parcurg secvența pe loc și returnează un singur obiect.
Metodele First și FirstOrDefault se opresc imediat ce găsesc primul element care respectă condiția (fără a mai verifica restul colecției); dacă niciun element nu se potrivește, First aruncă InvalidOperationException, în timp ce FirstOrDefault returnează default(T) (null pentru clase/înregistrări, 0 pentru numere) sau valoarea implicită pasată ca parametru în .NET 6+. În schimb, Single și SingleOrDefault impun o regulă mai strictă: trebuie să existe cel mult un singur element care respectă condiția — dacă găsesc două sau mai multe potriviri, ambele aruncă InvalidOperationException: Sequence contains more than one matching element.
Parametri, metode și opțiuni
| Parametru / Metodă | Descriere |
|---|---|
FirstOrDefault(predicat) |
Returnează primul element care respectă predicatul sau default(T) (null / 0) dacă nu se găsește nimic. |
FirstOrDefault(predicat, defaultValue) |
Returnează primul element potrivit sau defaultValue dacă secvența nu conține nicio potrivire (.NET 6+). |
First(predicat) |
Returnează primul element potrivit; aruncă InvalidOperationException dacă nu există niciunul. |
SingleOrDefault(predicat) |
Returnează unicul element potrivit, default(T) dacă nu există niciunul, sau aruncă excepție dacă există două sau mai multe potriviri. |
Single(predicat) |
Returnează unicul element potrivit; aruncă excepție atât când sunt 0 potriviri, cât și când sunt două sau mai multe potriviri. |
LastOrDefault(predicat) |
Returnează ultimul element din secvență care respectă condiția sau default(T). |
Exemple
Exemplul 1: Căutare sigură cu FirstOrDefault și valoare implicită personalizată (.NET 6+)
Căutăm un utilizator după ID și oferim fie verificarea cu is not null, fie un obiect fallback direct ca al doilea parametru al metodei FirstOrDefault.
var utilizatori = new List<Utilizator>
{
new(1, "andrei@dexblog.ro", "Admin"),
new(2, "elena@dexblog.ro", "Editor"),
new(3, "mihai@dexblog.ro", "Editor")
};
Utilizator? gasit = utilizatori.FirstOrDefault(u => u.Id == 2);
Console.WriteLine($"Căutare ID 2: {gasit?.Email ?? "Inexistent"}");
// Cu valoare implicită explicită (.NET 6+):
Utilizator vizitator = utilizatori.FirstOrDefault(
u => u.Id == 99,
defaultValue: new Utilizator(0, "guest@dexblog.ro", "Guest")
);
Console.WriteLine($"Căutare ID 99: {vizitator.Email} (rol: {vizitator.Rol})");
record Utilizator(int Id, string Email, string Rol);
Ieșire în consolă
Căutare ID 2: elena@dexblog.ro
Căutare ID 99: guest@dexblog.ro (rol: Guest)
Exemplul 2: Diferența critică dintre FirstOrDefault și SingleOrDefault la duplicate
SingleOrDefault validează unicitatea: este ideal când cauți după o cheie unică (CNP, e-mail, SKU) și vrei să detectezi imediat o eventuală corupere a datelor.
string[] coduri = ["RO-01", "RO-02", "RO-01"];
string? primul = coduri.FirstOrDefault(c => c == "RO-01");
Console.WriteLine($"FirstOrDefault a returnat: {primul}");
try
{
string? unicul = coduri.SingleOrDefault(c => c == "RO-01");
}
catch (InvalidOperationException ex)
{
Console.WriteLine($"SingleOrDefault a aruncat excepție: {ex.Message}");
}
Ieșire în consolă
FirstOrDefault a returnat: RO-01
SingleOrDefault a aruncat excepție: Sequence contains more than one matching element
Greșeli frecvente
- Folosirea lui
FirstOrDefaultpe o colecție de tipuri valoare (precumList<int>) unde0este o valoare validă: dacăFirstOrDefaultreturnează0, nu poți ști dacă a găsit numărul0în listă sau dacă nu a găsit nimic. În acest caz proiectează la nullable (lista.Cast<int?>().FirstOrDefault(...)) sau specifică o valoare santinelă precumFirstOrDefault(x => ..., -1). - Folosirea lui
SingleOrDefaultpe colecții mari în memorie când nu ai nevoie să validezi absența duplicatelor — spre deosebire deFirstOrDefault(care se oprește la primul element găsit),SingleOrDefaulteste obligat să scaneze restul listei până la capăt pentru a se asigura că nu mai există un al doilea element potrivit.
Funcții și concepte înrudite
Enumerable.Where: Filtrează declarativ o secvență de elemente pe baza unui predicat lambda, cu execuție amânată (deferred execution).Tipuri Nullable (?, ??, ?.): Reprezintă explicit absența unei valori și previne excepțiile NullReferenceException prin operatori siguri de navigare și coalescență.Any, All, Count și Sum: Evaluează condiții booleene rapide (cu scurtcircuitare) și calculează agregări numerice (Count, Sum, Average, Min, Max) peste colecții.