CodeGym /Kursy /C# SELF /Wprowadzenie do LINQ i jego zalety

Wprowadzenie do LINQ i jego zalety

C# SELF
Poziom 31 , Lekcja 0
Dostępny

1. Wprowadzenie

Wyobraź sobie, że wchodzisz do biblioteki i musisz znaleźć wszystkie książki o programowaniu wydane po 2020 roku, posortowane według nazwiska autora. Raczej nie będziesz biegać od półki do półki i sprawdzać każdą książkę ręcznie, prawda? Poprosiłbyś bibliotekarza, który wie, jak szybko znaleźć to, czego potrzebujesz.

LINQ (Language Integrated Query, czyli "Zapytania zintegrowane z językiem") – to nasz "sprytny bibliotekarz", "SQL-silnik" (czyli narzędzie do pisania zapytań do danych) prosto w C#!

Myśl o LINQ jak o języku, który pozwala Ci opisać, co chcesz dostać z danych, a nie jak to zrobić. Zamiast wymagać: "Weź pierwszy element, sprawdź warunek, jeśli pasuje, dodaj do nowej listy, potem przejdź do drugiego...", po prostu mówisz: "Daj mi wszystkie produkty, których cena jest większa niż 1000". A C# sam ogarnie, jak to zrobić najwydajniej.

Kluczowa idea LINQ: dać standardową składnię do zapytań do dowolnych źródeł danych, które implementują interfejs IEnumerable<T>. I to jest super, bo List<T>, tablice, HashSet<T> – wszystkie one implementują IEnumerable<T>. A jeśli Twoje dane są w bazie, to specjalne biblioteki (np. Entity Framework) zamienią Twoje LINQ-zapytania na prawdziwy SQL!

LINQ pojawił się w C# ponad 10 lat temu. To było przełomowe wydarzenie, które zmieniło podejście do pracy z danymi w .NET. Przed LINQ programiści musieli pisać mnóstwo szablonowego kodu do filtrowania, sortowania i transformacji kolekcji. Albo używać stringów z SQL-zapytaniami, które nie były sprawdzane przez kompilator i były podatne na błędy w czasie działania. LINQ przyniósł koncepcję "zapytań" prosto do języka programowania, czyniąc je typ-bezpiecznymi i bardziej czytelnymi.

Wielu uważa, że LINQ — to jedno z najważniejszych ulepszeń w C# od czasu pojawienia się generyków (Generics).

2. Zalety LINQ: Dlaczego wszyscy go kochają?

Zwięzłość i czytelność kodu: Żeby przefiltrować np. produkty z ceną powyżej 1000 w dużej bazie, trzeba było pisać kilka linijek kodu. Z LINQ można to wyrazić w jednej. Mniej kodu — mniej potencjalnych bugów, większa czytelność i zrozumiałość. Kod staje się bliższy naturalnemu językowi.
Programistyczny żart: "Im mniej kodu piszę, tym mniej bugów mogę tam wrzucić." LINQ w tym pomaga!

Moc i elastyczność: LINQ daje bogaty zestaw operacji: filtrowanie (Where), projekcja (Select), sortowanie (OrderBy), grupowanie (GroupBy), agregacja (Sum, Average), łączenie (Join) i wiele więcej. Pozwala rozwiązywać złożone zadania przetwarzania danych, łącząc te operacje.

Typ-bezpieczeństwo: To bardzo ważne! Gdy piszesz SQL-zapytanie jako string, kompilator nic o nim nie wie. Jeśli pomylisz się w nazwie kolumny, dowiesz się o tym dopiero w czasie działania programu, gdy się wywali z błędem. Z LINQ kompilator C# sprawdza Twoje zapytanie na błędy już podczas kompilacji. Jeśli spróbujesz zapytać o nieistniejące pole w obiekcie Product, kompilator od razu pokaże błąd. To jak mieć osobistego korektora, który łapie Twoje literówki zanim zobaczą je inni.

Integracja z językiem: Zapytania LINQ – to nie jakieś "magiczne" stringi. To pełnoprawne konstrukcje C#, które używają znanych Ci wyrażeń lambda i działają na już znanych typach danych. Dzięki temu przejście do LINQ jest bardzo płynne.

Odroczone wykonanie (Deferred Execution): To jedna z najfajniejszych i chyba najtrudniejszych koncepcji LINQ, ale bardzo ważna. Chodzi o to, że LINQ-zapytanie nie wykonuje się od razu, jak tylko je napiszesz. Ono się "składa" i czeka, aż naprawdę poprosisz o wynik (np. zaczniesz je iterować w foreach). To pozwala optymalizować zapytania, zwłaszcza przy pracy z dużą ilością danych. O tym pogadamy szerzej w Wykładzie 166. Na razie po prostu zapamiętaj: LINQ jest sprytny, nie robi niepotrzebnej roboty.

Uniwersalność i rozszerzalność: LINQ — to nie tylko dla list w pamięci. Istnieją różne "provider'y" LINQ:

  • LINQ to Objects: dla kolekcji w pamięci (List<T>, tablice itd.).
  • LINQ to SQL / Entity Framework Core: dla baz danych SQL (Twoje LINQ-zapytania zamieniają się w SQL-zapytania).
  • LINQ to XML: do pracy z dokumentami XML.
  • I wiele innych.
    To znaczy, że jak ogarniesz LINQ, możesz pracować z danymi z różnych źródeł, używając tej samej logiki zapytań.

3. Problem pracy z kolekcjami "po staremu"

Przed LINQ kod obsługi kolekcji w C# wyglądał mniej więcej tak:


var products = new List<Product> { /* ... */ };

var expensive = new List<Product>();
foreach (var prod in products)
{
    if (prod.Price > 100)
        expensive.Add(prod);
}

expensive.Sort((a, b) => a.Price.CompareTo(b.Price));

foreach (var item in expensive)
    Console.WriteLine(item.Name);

Taki sposób jest typowy: najpierw ręcznie przefiltrować, potem posortować — koniecznie stworzyć pośrednią listę. Im więcej logiki, tym więcej kodu, błędów i zmiennych. To wszystko przypomina warsztat składania mebli: części są, ale składanie — ręczne i męczące.

Alicja w krainie pętli

Wyobraź sobie, że masz ogromną listę użytkowników i chcesz dostać tylko imiona tych, którzy mają więcej niż 18 lat i mieszkają w mieście Neonville, posortować je alfabetycznie i wypisać trzy pierwsze. Musiałbyś pisać zagnieżdżone pętle, warunki, sortowania, pomocnicze listy... albo po prostu użyć LINQ.

4. Jak wygląda LINQ-zapytanie? Proste przykłady

LINQ daje dwa główne style składni:

  • Method Syntax (łańcuch metod)
  • Query Syntax (język podobny do SQL)

Najczęściej używa się Method Syntax, zwłaszcza w nowoczesnych projektach. Oto przykład na bazie naszej aplikacji:


var expensive = products
    .Where(p => p.Price > 100)
    .OrderBy(p => p.Price)
    .Select(p => p.Name)
    .Take(3);

foreach (var name in expensive)
    Console.WriteLine(name);

Wszystko czytelne: filtr, sortowanie, wybór konkretnego pola, ograniczenie ilości. Minimum kodu — maksimum sensu.

To samo przez Query Syntax


var expensive = from p in products
                where p.Price > 100
                orderby p.Price
                select p.Name;

foreach (var name in expensive.Take(3))
    Console.WriteLine(name);

Oba warianty dają ten sam wynik — wybierz ten, który bardziej Ci pasuje (ale method-syntax i tak jest częściej używany).

5. Architektura LINQ: co pod maską?

LINQ działa z dowolnymi kolekcjami, które implementują interfejs IEnumerable<T> (albo IQueryable<T>, o nim pogadamy później).

Główne komponenty LINQ

Komponent Krótkie wyjaśnienie
Klasy rozszerzające LINQ Statyczne metody (
Where
,
Select
,
OrderBy
itd.) w klasie
System.Linq.Enumerable
Delegaty Najczęściej używane
Func<T, TResult>
i
Predicate<T>
Odroczone wykonanie Zapytanie jest wyliczane dopiero przy pierwszej iteracji (np. przez
foreach
)
Query Provider (dla LINQ to SQL, LINQ to Entities) — zamienia łańcuch metod na SQL-zapytania itd.

Wizualny schemat


Kolekcja (List<Product>, T[], ...) 
      │
      ▼
LINQ-metody (Where, OrderBy, Select...)
      │
      ▼
Zapytanie (IEnumerable<T>) 
      │
      ▼
Rzeczywiste wykonanie (foreach, ToList, Count, ...)

6. Przykład: krok po kroku analiza LINQ-łańcucha w naszej aplikacji

Wracamy do naszego mini-aplikacji z klasą Product:


// Klasa produktu
class Product
{
    public string Name { get; set; }
    public double Price { get; set; }
}

Oto zestaw produktów:


var products = new List<Product>
{
    new Product { Name = "Ser", Price = 250.5 },
    new Product { Name = "Chleb", Price = 30 },
    new Product { Name = "Mleko", Price = 80 },
    new Product { Name = "Kawa", Price = 330 },
    new Product { Name = "Masło", Price = 140 }
};

Załóżmy, że naszym zadaniem jest: wypisać na ekran nazwy wszystkich towarów droższych niż 100 euro, posortować po cenie.

Stary sposób


var filtered = new List<Product>();
foreach (var p in products)
{
    if (p.Price > 100)
        filtered.Add(p);
}

filtered.Sort((a, b) => a.Price.CompareTo(b.Price));

foreach (var p in filtered)
    Console.WriteLine(p.Name);

Sposób LINQ


var expensive = products
    .Where(p => p.Price > 100)
    .OrderBy(p => p.Price)
    .Select(p => p.Name);

foreach (var name in expensive)
    Console.WriteLine(name);

W pierwszej linijce opisujemy samą istotę zadania: "Przefiltruj tych, co mają Price > 100, posortuj po cenie, wybierz nazwy".

7. Częste operacje LINQ i ich analogi "po staremu"

Operacja Logika "po staremu" LINQ
Filtrowanie foreach + if + Add
Where
Projekcja (wybór pola) foreach + Add(field)
Select
Sortowanie Sort(comparer)
OrderBy
,
OrderByDescending
Unikalne wartości foreach + contains + Add
Distinct
Zliczanie foreach + counter++
Count
,
Count(predicate)
Sprawdzenie warunku foreach + if
Any
,
All
Szukanie pierwszego/ostatniego foreach + if + break
First
,
Last
,
FirstOrDefault
Ograniczenie ilości foreach + licznik + break
Take
,
Skip

8. Praktyka: dodajemy LINQ do aplikacji

Weźmy bazowy kod naszej aplikacji (Product-lista) i spróbujmy zrobić kilka przydatnych operacji z LINQ.

Filtrowanie i sortowanie


// Wybierz produkty tańsze niż 200 euro i posortuj po nazwie
var cheapProducts = products
    .Where(p => p.Price < 200)
    .OrderBy(p => p.Name);

foreach (var p in cheapProducts)
    Console.WriteLine($"{p.Name}: {p.Price} euro");

Transformacja (projekcja) kolekcji


// Pobierz listę cen (double)
var prices = products.Select(p => p.Price);

foreach (var price in prices)
    Console.WriteLine(price);

Szukanie pierwszego pasującego elementu


// Pierwszy produkt, którego nazwa zaczyna się na "K"
var firstK = products.FirstOrDefault(p => p.Name.StartsWith("K"));
Console.WriteLine(firstK?.Name ?? "Nie znaleziono");

Zliczanie produktów droższych niż 100 euro


int count = products.Count(p => p.Price > 100);
Console.WriteLine($"Takich produktów: {count} szt.");

Od tego wykładu będziemy aktywnie używać LINQ do obsługi kolekcji, filtrowania, wyboru potrzebnych danych, agregacji i wielu innych rzeczy. Nowe metody i możliwości LINQ (w tym nowości z .NET 9!) omówimy w najbliższych wykładach, a na razie — nie bój się eksperymentować z prostymi zapytaniami, żeby poczuć całą moc tego narzędzia!

Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION