Теория
LINQ (Language-Integrated Query) е функционалност на C#, която позволява да филтрираме, сортираме и търсим в колекции (масиви, списъци) с много малко код — подобно на заявки към база данни, но направо в езика. Изисква using System.Linq;.
LINQ разчита на ламбда изрази: x => x > 5 се чете "вземи елемент x, такъв че x да е по-голямо от 5". Лявата страна на => е параметърът (текущият елемент), дясната — изразът, който се изчислява за него. Ламбдата е малка анонимна функция, която подаваме на LINQ метода.
Методите се навързват във верига (method chaining): products.Where(...).OrderBy(...).Select(...) — резултатът от единия е вход за следващия. Така сложна обработка се чете отгоре надолу като изречение.
Отложено изпълнение (Deferred Execution): повечето LINQ заявки не се изпълняват на момента, а чак когато обходим резултата (с foreach) или извикаме .ToList() / .ToArray(). Заявката е "рецепта", не "готово ястие" — изпълнява се при поискване.
Златни правила
Какво е LINQ?
Функционалност на C#, която ви позволява да филтрирате, сортирате и търсите в колекции с много малко код, подобно на заявки към база данни. Не забравяйте using System.Linq;!
Ламбда изрази (=>)
LINQ разчита силно на ламбда изрази. => се чете "отива в" или "такова че". Например: x => x > 5 означава "вземи елемент x, такъв че x да е по-голямо от 5".
Отложено изпълнение
Повечето LINQ заявки не се изпълняват на момента, а чак когато обходите резултата (например с foreach) или изрично извикате .ToList() / .ToArray().
Where филтрира, Select трансформира
Where отговаря на въпроса "кои елементи?" (връща по-малко елементи от същия тип), Select — на "какво от всеки елемент?" (връща същия брой, но трансформирани).
Бърз справочник
Основни LINQ методи
| LINQ метод | За какво служи? | Пример |
|---|---|---|
.Where() | Филтрира колекцията по зададено условие. | list.Where(x => x % 2 == 0) |
.Select() | Трансформира / извлича конкретна част от данните. | students.Select(s => s.Name) |
.OrderBy() | Сортира във възходящ ред. | list.OrderBy(x => x) |
.OrderByDescending() | Сортира в низходящ ред. | list.OrderByDescending(x => x) |
.FirstOrDefault() | Връща първия елемент, отговарящ на условие, или null/0. | list.FirstOrDefault(x => x == 100) |
.Count() | Връща броя на елементите, отговарящи на дадено условие. | list.Count(x => x > 50) |
.Sum() / .Average() | Сума / средна стойност на елементите. | grades.Average() |
.Min() / .Max() | Най-малък / най-голям елемент. | prices.Max() |
.ToList() | Изпълнява заявката и връща резултата като List<T>. | var result = query.ToList(); |
Още полезни LINQ методи
| LINQ метод | За какво служи? | Пример |
|---|---|---|
.Any() | Има ли поне един елемент, отговарящ на условието (bool). | numbers.Any(n => n > 20) |
.All() | Всички ли елементи отговарят на условието (bool). | numbers.All(n => n > 0) |
.Distinct() | Премахва дубликатите. | numbers.Distinct() |
.Take(n) | Взима първите n елемента. | sorted.Take(3) — топ 3 |
.Skip(n) | Прескача първите n елемента. | sorted.Skip(3) — всичко без топ 3 |
.ThenBy() | Второ ниво на сортиране след OrderBy. | people.OrderBy(p => p.Age).ThenBy(p => p.Name) |
Код за анализ и пренаписване
Where, OrderByDescending и Select върху списък с продукти
using System;
using System.Collections.Generic;
using System.Linq;
namespace ModuleEight
{
class Product
{
public string Name { get; set; }
public double Price { get; set; }
}
class Program
{
static void Main(string[] args)
{
List<Product> products = new List<Product>
{
new Product { Name = "Лаптоп", Price = 1500.00 },
new Product { Name = "Мишка", Price = 45.50 },
new Product { Name = "Монитор", Price = 450.00 }
};
// 1. Where: Намиране на продукти с цена под 200 лв.
var cheapProducts = products.Where(p => p.Price < 200).ToList();
Console.WriteLine("--- Евтини продукти ---");
foreach (var p in cheapProducts)
{
Console.WriteLine($"{p.Name} - {p.Price} лв.");
}
// 2. OrderBy & Select: Сортиране по цена низходящо и взимане само на имената
var sortedNames = products
.OrderByDescending(p => p.Price)
.Select(p => p.Name)
.ToList();
Console.WriteLine("\n--- Продукти от най-скъп към най-евтин ---");
foreach (var name in sortedNames)
{
Console.WriteLine(name);
}
}
}
}Какво се случва тук?
- Списъкът е създаден с инициализатор на колекция — обектите се описват направо в
{ }, с инициализатор на обект (new Product { Name = ..., Price = ... }) вместо конструктор. -
products.Where(p => p.Price < 200)— ламбдата се изпълнява за всеки продуктp; остават само тези, за които изразът еtrue(тук — само Мишката). -
.ToList()"материализира" заявката веднага — без негоcheapProductsби бил само рецепта, изпълнявана при всяко обхождане (отложено изпълнение). - Във втората заявка методите са навързани: първо
OrderByDescending(p => p.Price)сортира по цена от най-скъп към най-евтин, послеSelect(p => p.Name)взима само името на всеки продукт — резултатът еList<string>, неList<Product>. - Записването на веригата на отделни редове (по един метод на ред) е стандартният четим стил за LINQ.
Статистика върху числа: Any, All, Distinct, Take
using System;
using System.Collections.Generic;
using System.Linq;
namespace ModuleEightExtra
{
class Program
{
static void Main(string[] args)
{
List<int> numbers = new List<int> { 12, 5, 8, 21, 5, 16, 3, 8 };
// Филтриране + string.Join за красив печат
var evens = numbers.Where(n => n % 2 == 0).ToList();
Console.WriteLine($"Четни: {string.Join(", ", evens)}");
// Any и All - въпроси с да/не отговор
Console.WriteLine($"Има ли число над 20? {numbers.Any(n => n > 20)}");
Console.WriteLine($"Всички ли са положителни? {numbers.All(n => n > 0)}");
// Distinct - без дубликати
Console.WriteLine($"Уникални стойности: {numbers.Distinct().Count()}");
// Верига: сортирай, махни дубликатите, вземи топ 3
var top3 = numbers
.OrderByDescending(n => n)
.Distinct()
.Take(3)
.ToList();
Console.WriteLine($"Топ 3: {string.Join(", ", top3)}");
// Агрегации
Console.WriteLine($"Сума: {numbers.Sum()}, Средно: {numbers.Average():F2}");
}
}
}Какво се случва тук?
-
string.Join(", ", evens)слепва списъка в красив низ "12, 8, 16, 8" — двойката Join + LINQ е стандартът за отпечатване на резултати. -
Anyпита "има ли поне един?",All— "всички ли?". И двата връщатboolи спират да проверяват веднага щом отговорът стане ясен. -
Distinct()маха повторенията (тук двете петици и двете осмици се броят по веднъж). - Веригата за топ 3 се чете отгоре надолу като рецепта: сортирай низходящо → премахни дубликати → вземи първите 3 → материализирай. Всеки метод подава резултата си на следващия.
-
Average()връщаdoubleдори за списък отint— LINQ е помислил за капана с целочисленото делене вместо вас.
Внимавай! Чести грешки
Грешките, които почти всеки прави в тази тема — виж разликата между грешния и правилния код.
Забравен using System.Linq
Грешно
using System;
using System.Collections.Generic;
// CS1061: 'List<int>' does not contain
// a definition for 'Where'Правилно
using System;
using System.Collections.Generic;
using System.Linq;LINQ методите са разширения и "се появяват" върху колекциите само с using System.Linq;. Грешката CS1061 за Where/Select/OrderBy почти винаги значи точно това.
>= вместо =>
Грешно
var big = numbers.Where(n >= 5);
// объркан ламбда операторПравилно
var big = numbers.Where(n => n >= 5);=> е ламбда операторът ("такова че"), >= е сравнение "по-голямо или равно". В ламбдата първо идва параметърът, после =>, после условието — което може да съдържа >=.
First върху празен резултат
Грешно
var found = products.First(p => p.Price > 9999);
// InvalidOperationException, ако няма такъвПравилно
var found = products.FirstOrDefault(p => p.Price > 9999);
if (found != null)
{
Console.WriteLine(found.Name);
}First гърми, когато няма съвпадение. FirstOrDefault връща null (или 0 за числа) — но тогава задължително проверете резултата, преди да го ползвате, иначе ви чака NullReferenceException.
Заявка без ToList, ползвана многократно
Грешно
var expensive = products.Where(p => p.Price > 100);
Console.WriteLine(expensive.Count()); // изпълнява заявката
foreach (var p in expensive) { ... } // изпълнява я ПАКПравилно
var expensive = products.Where(p => p.Price > 100).ToList();
Console.WriteLine(expensive.Count);
foreach (var p in expensive) { ... }Без .ToList() заявката е "рецепта" и се изпълнява наново при всяко обхождане (отложено изпълнение). Ще ползвате резултата повече от веднъж? Материализирайте го веднъж с ToList().
Задачи за самостоятелна работа
Опитай първо сам — подсказката е там само ако наистина закъсаш.
Задача 1: Филтриране на числа
Създайте списък с числата от 1 до 20. Използвайте LINQ, за да създадете нов списък, който съдържа само числата, които се делят на 3 без остатък, и ги отпечатайте.
Задача 2: Търсене на студенти
Създайте списък от обекти от клас Student (съдържащи Име и Възраст). Използвайте LINQ, за да намерите и отпечатате само студентите, които са пълнолетни (на 18 или повече години), сортирани по име по азбучен ред.
Задача 3: Обработка на думи
Даден е списък с думи. С една LINQ верига: вземете само думите с дължина поне 5 букви, превърнете ги в главни букви и ги подредете по дължина (от най-късата към най-дългата). Отпечатайте резултата.
Мини-тест за проверка
Отговори си наум (или на глас!) и чак тогава разкрий отговора.
1. Какво означава символът => и как се нарича?
2. Каква е разликата между .Where() и .Select()?
3. Какво ще върне .FirstOrDefault(), ако списъкът е празен или търсеният елемент не съществува?
4. Какво правят .Any() и .All() и какво връщат?
Речник на термините
Виж пълния речник на курсаusing System.Linq;.x => x > 5 — "вземи x, такова че x > 5". Лявата страна е параметърът, дясната — изразът.bool — условието, подавано на Where, Any, All, Count.list.Where(...).OrderBy(...).Select(...) — резултатът от единия е вход за следващия..ToList() — дотогава е само "рецепта"..ToList(), .ToArray().Sum, Average, Min, Max, Count.Провери знанията
Бърз тест с избор от отговори върху термините на модула — с точки и моментална обратна връзка.
Провери знанията си
7 въпроса върху термините от модула. Показваме определение — ти избираш правилния термин.
Препоръки за този модул
- Четете заявката на глас като изречение: "от продуктите, където цената < 200, взимам имената" — ако изречението звучи логично, заявката е вярна.
- Решете една задача и по двата начина — с цикли и с LINQ — и сравнете кода; така ще усетите какво точно ви спестява LINQ.
- Завършвайте с
.ToList(), когато искате резултата веднага и многократно — иначе заявката се изпълнява наново при всяко обхождане. - Ползвайте смислени имена в ламбдите при по-сложни заявки:
products.Where(product => ...)е по-четимо отp => ...за начинаещи.