Enumerable.Select în C# (.NET)

LINQ (Language Integrated Query) .NET 3.5+ (Chunk și DistinctBy în .NET 6+)

Proiectează (transformă) fiecare element al unei secvențe într-o formă nouă și aplatizează colecțiile imbricate cu SelectMany.

Sintaxă C#
var proiectie = colectie.Select(x => new Dto(x.Id, x.Nume));

Ce face Enumerable.Select

Metoda Select este echivalentul funcției map din alte limbaje și al clauzei SELECT din SQL: primește o funcție de transformare (Func<TSource, TResult>) și produce câte un element de tip TResult pentru fiecare element TSource din secvența originală. Este folosită constant pentru a extrage o singură proprietate (de exemplu o listă de ID-uri sau de adrese de e-mail) ori pentru a converti entitățile din baza de date în obiecte DTO (Data Transfer Objects).

Atunci când fiecare element al colecției conține la rândul său o sub-colecție (de exemplu, fiecare Comanda conține un List<LinieComanda>), metoda pereche SelectMany proiectează și aplatizează (flattens) toate sub-listele într-o singură secvență continuă IEnumerable<LinieComanda>.

Parametri, metode și opțiuni

Parametru / Metodă Descriere
source.Select(x => rezultat) Transformă fiecare element x în valoarea sau obiectul returnat de expresia lambda.
source.Select((x, index) => rezultat) Include indexul elementului (începând de la 0) în funcția de transformare.
source.SelectMany(x => x.SubColectie) Proiectează fiecare element într-o secvență și concatenează toate sub-secvențele într-o singură listă plată.
source.Distinct() / DistinctBy(x => x.Cheie) Elimină elementele duplicate din secvență (sau duplicatele după o anumită proprietate în .NET 6+).
source.Chunk(int size) Împarte secvența în loturi (tablouri T[]) de cel mult size elemente (.NET 6+).

Exemple

Exemplul 1: Proiecția entităților în obiecte DTO cu Select și numerotare cu index

Transformăm o listă de angajați în rezumate pregătite pentru afișare, calculând salariul net și numărul de ordine.

var angajati = new[]
{
    new Angajat("Elena Popa", "Backend .NET", SalariuBrut: 16000m),
    new Angajat("Radu Ionescu", "DevOps", SalariuBrut: 18000m),
    new Angajat("Maria Stan", "QA Automation", SalariuBrut: 12000m)
};

var fluturasi = angajati
    .Select((a, idx) => new
    {
        Pozitie = idx + 1,
        a.Nume,
        a.Departament,
        SalariuNet = Math.Round(a.SalariuBrut * 0.585m, 2)
    })
    .ToList();

foreach (var f in fluturasi)
{
    Console.WriteLine($"#{f.Pozitie} {f.Nume} ({f.Departament}) -> Net: {f.SalariuNet:F2} RON");
}

record Angajat(string Nume, string Departament, decimal SalariuBrut);

Ieșire în consolă

#1 Elena Popa (Backend .NET) -> Net: 9360.00 RON
#2 Radu Ionescu (DevOps) -> Net: 10530.00 RON
#3 Maria Stan (QA Automation) -> Net: 7020.00 RON

Exemplul 2: Aplatizarea colecțiilor imbricate cu SelectMany și deduplicare cu Distinct

Extragem o listă unică cu toate tehnologiile folosite în mai multe proiecte folosind SelectMany urmat de Distinct.

var proiecte = new[]
{
    new { Nume = "Portal Plăți", Tech = new[] { "C#", "PostgreSQL", "Docker" } },
    new { Nume = "API Mobil",    Tech = new[] { "C#", "Redis", "Docker" } },
    new { Nume = "Worker Raport", Tech = new[] { "C#", "RabbitMQ" } }
};

string[] toateTehnologiile = proiecte
    .SelectMany(p => p.Tech)
    .Distinct()
    .ToArray();

Console.WriteLine("Tehnologii unice: " + string.Join(", ", toateTehnologiile));

Ieșire în consolă

Tehnologii unice: C#, PostgreSQL, Docker, Redis, RabbitMQ

Greșeli frecvente

  • Folosirea lui Select doar pentru a produce efecte secundare (de exemplu lista.Select(x => { Console.WriteLine(x); return x; });) — nu doar că încalcă principiul funcțional al lui Select, dar dacă nu apelezi .ToList(), codul nici măcar nu se va executa din cauza evaluării leneșe. Folosește o buclă foreach obișnuită pentru efecte secundare.
  • Folosirea lui Select în loc de SelectMany pe o proprietate care este deja o listă, obținând un IEnumerable<List<T>> (listă de liste) în loc de o listă plată IEnumerable<T>.

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).
  • OrderBy / ThenBy: Sortează secvențele crescător sau descrescător pe baza unuia sau mai multor criterii și extrage pagini cu Skip și Take.
  • GroupBy: Grupează elementele unei secvențe după o cheie comună pentru a calcula statistici, sume și agregări pe categorii.
  • record și imutabilitate: Definește într-o singură linie modele de date imutabile cu egalitate bazată pe valoare, afișare automată și clonare nedistructivă (with).