LINQ — это набор инструментов в C# для удобной работы с коллекциями (списками, массивами). Он позволяет фильтровать, сортировать, группировать и преобразовывать данные в одну строку кода.
Например в стратегии это незаменимый инструмент для работы с инвентарем, юнитами, заданиями и ресурсами.
- Что такое LINQ и зачем он нужен?
- ❌ Без LINQ (Много кода)
- ✅ С LINQ (Одна строка)
- Подключение LINQ
- Основные методы LINQ
- Фильтрация
- Агрегация
- Сортировка
- Группировка
- Объединение
- Практические примеры для стратегии
- Инвентарь игрока
- Постройки
- Боевая система
- Квесты и достижения
- Друзья и клан
- Отложенное выполнение (Deferred Execution)
- Производительность LINQ в играх
- ⚠️ Проблемы
- ✅ Когда использовать LINQ
- 🚫 Избегайте в Update()
- LINQ без аллокаций (Unity LINQ)
- Вариант 1: Обычный цикл
- Вариант 2: Unity.Collections (ECS)
- Вариант 3: Библиотеки без аллокаций
- Расширенные возможности LINQ
- Цепочка методов (Method Chaining)
- LINQ Query Syntax (Альтернативный синтаксис)
- Группировка с агрегацией
- Join (Объединение двух коллекций)
- Частые ошибки
- Чек-лист для вашей игры
- Итоговая таблица методов
- Пример: Полный класс с LINQ для стратегии
Что такое LINQ и зачем он нужен?
❌ Без LINQ (Много кода)
List<Unit> selectedUnits = new List<Unit>();
foreach (var unit in allUnits)
{
if (unit.IsSelected && unit.Health > 0)
{
selectedUnits.Add(unit);
}
}
selectedUnits.Sort((a, b) => a.Level.CompareTo(b.Level));✅ С LINQ (Одна строка)
var selectedUnits = allUnits
.Where(u => u.IsSelected && u.Health > 0)
.OrderBy(u => u.Level)
.ToList();Выгода: Код короче, читаемее, меньше ошибок.
Подключение LINQ
В начале файла добавьте:
using System.Linq;Важно для Unity: LINQ создает небольшой overhead (аллокации памяти). В Update() с тысячами вызовов лучше использовать обычные циклы. Для UI, инвентаря, меню — LINQ идеален.
Основные методы LINQ
Фильтрация
|
Метод
|
Описание
|
Пример
|
|---|---|---|
Where() |
Фильтрует коллекцию
|
.Where(u => u.Health > 0) |
First() |
Первый элемент
|
.First(u => u.IsSelected) |
FirstOrDefault() |
Первый или null
|
.FirstOrDefault(u => u.IsSelected) |
Last() |
Последний элемент
|
.Last(u => u.IsSelected) |
Single() |
Единственный элемент
|
.Single(u => u.IsLeader) |
// Найти всех живых юнитов
var aliveUnits = allUnits.Where(u => u.Health > 0).ToList();
// Найти первого выбранного юнита
var selectedUnit = allUnits.FirstOrDefault(u => u.IsSelected);
// Найти юнита по ID
var unit = allUnits.Single(u => u.Id == targetId);Преобразование
|
Метод
|
Описание
|
Пример
|
|---|---|---|
Select() |
Преобразует каждый элемент
|
.Select(u => u.Name) |
SelectMany() |
Flatten вложенных коллекций
|
.SelectMany(u => u.Inventory) |
Cast<T>() |
Приводит тип
|
.Cast<Soldier>() |
OfType<T>() |
Фильтрует по типу
|
.OfType<Building>() |
// Получить список имен всех юнитов
var names = allUnits.Select(u => u.Name).ToList();
// Получить все предметы из инвентарей всех юнитов
var allItems = allUnits.SelectMany(u => u.Inventory).ToList();
// Получить только здания
var buildings = allObjects.OfType<Building>().ToList();Агрегация
|
Метод
|
Описание
|
Пример
|
|---|---|---|
Count() |
Количество элементов
|
.Count(u => u.IsSelected) |
Sum() |
Сумма
|
.Sum(u => u.GoldCost) |
Average() |
Среднее
|
.Average(u => u.Level) |
Min() / Max() |
Мин/Макс
|
.Max(u => u.Power) |
Any() |
Есть ли хотя бы один
|
.Any(u => u.IsEnemy) |
All() |
Все ли соответствуют
|
.All(u => u.IsReady) |
// Сколько живых юнитов?
int aliveCount = allUnits.Count(u => u.Health > 0);
// Общая стоимость всех построек
int totalCost = buildings.Sum(b => b.Cost);
// Есть ли враги в радиусе?
bool hasEnemies = nearbyUnits.Any(u => u.IsEnemy);
// Все ли юниты готовы к атаке?
bool allReady = squad.All(u => u.IsReady);
// Средний уровень игроков в клане
float avgLevel = clanMembers.Average(m => m.Level);Сортировка
|
Метод
|
Описание
|
Пример
|
|---|---|---|
OrderBy() |
Сортировка по возрастанию
|
.OrderBy(u => u.Level) |
OrderByDescending() |
По убыванию
|
.OrderByDescending(u => u.Power) |
ThenBy() |
Дополнительная сортировка
|
.OrderBy(u => u.Type).ThenBy(u => u.Level) |
// Сортировать юнитов по уровню (сильные первые)
var sortedUnits = allUnits.OrderByDescending(u => u.Level).ToList();
// Сортировать по типу, затем по уровню
var sorted = allUnits
.OrderBy(u => u.UnitType)
.ThenByDescending(u => u.Level)
.ToList();Группировка
|
Метод
|
Описание
|
Пример
|
|---|---|---|
GroupBy() |
Группирует по ключу
|
.GroupBy(u => u.Type) |
// Сгруппировать юнитов по типу
var grouped = allUnits.GroupBy(u => u.Type);
foreach (var group in grouped)
{
Debug.Log($"Тип: {group.Key}, Количество: {group.Count()}");
// group.Key = тип юнита
// group = коллекция юнитов этого типа
}
// Получить количество юнитов каждого типа
var unitCounts = allUnits
.GroupBy(u => u.Type)
.ToDictionary(g => g.Key, g => g.Count());Объединение
|
Метод
|
Описание
|
Пример
|
|---|---|---|
Union() |
Объединение без дубликатов
|
list1.Union(list2) |
Concat() |
Объединение с дубликатами
|
list1.Concat(list2) |
Intersect() |
Общие элементы
|
list1.Intersect(list2) |
Except() |
Элементы из первого, которых нет во втором
|
list1.Except(list2) |
// Объединить два списка юнитов
var allTroops = infantry.Union(cavalry);
// Найти общих друзей между двумя игроками
var mutualFriends = player1.Friends.Intersect(player2.Friends);
// Найти юнитов, которых нет в отборе
var available = allUnits.Except(selectedUnits);Практические примеры для стратегии
Инвентарь игрока
// Найти все предметы редкости "Legendary"
var legendaryItems = inventory
.Where(item => item.Rarity == Rarity.Legendary)
.ToList();
// Посчитать общее количество золота во всех предметах
int totalGold = inventory
.Where(item => item.Type == ItemType.Gold)
.Sum(item => item.Amount);
// Найти самый дорогой предмет
var mostExpensive = inventory
.OrderByDescending(item => item.Price)
.FirstOrDefault();Постройки
// Найти все готовые постройки
var readyBuildings = buildings
.Where(b => b.BuildTime <= Time.time)
.ToList();
// Найти постройку, которая скоро завершится (в течение 5 минут)
var almostReady = buildings
.Where(b => b.BuildTime > Time.time && b.BuildTime <= Time.time + 300)
.OrderBy(b => b.BuildTime)
.FirstOrDefault();
// Посчитать общую мощь всех зданий
int totalPower = buildings.Sum(b => b.Power);Боевая система
// Найти всех врагов в радиусе атаки
var enemiesInRange = allUnits
.Where(u => u.IsEnemy && Vector3.Distance(u.Position, myPosition) <= attackRange)
.OrderBy(u => u.Health) // Сначала слабых
.ToList();
// Найти ближайшего врага
var nearestEnemy = allUnits
.Where(u => u.IsEnemy)
.OrderBy(u => Vector3.Distance(u.Position, myPosition))
.FirstOrDefault();
// Проверить, есть ли живые союзники рядом
bool hasAlliesNearby = allUnits
.Any(u => u.IsAlly && u.Health > 0 && Vector3.Distance(u.Position, myPosition) <= 10f);Квесты и достижения
// Проверить выполнение квеста "Убить 10 врагов"
bool questComplete = completedQuests
.Any(q => q.Type == QuestType.KillEnemies && q.Count >= 10);
// Получить все доступные квесты (не начатые и не завершенные)
var availableQuests = allQuests
.Where(q => q.Status == QuestStatus.Available && q.LevelRequirement <= player.Level)
.ToList();
// Найти квест с наибольшей наградой
var bestRewardQuest = availableQuests
.OrderByDescending(q => q.RewardGold)
.FirstOrDefault();Друзья и клан
// Найти друзей онлайн
var onlineFriends = friends
.Where(f => f.IsOnline)
.OrderByDescending(f => f.LastLogin)
.ToList();
// Посчитать общую мощь клана
int clanPower = clanMembers.Sum(m => m.Power);
// Найти лидеров клана (топ-3 по мощи)
var leaders = clanMembers
.OrderByDescending(m => m.Power)
.Take(3)
.ToList();Отложенное выполнение (Deferred Execution)
Важно! LINQ запросы не выполняются сразу, а только при обращении к результату.
// Запрос создан, но НЕ выполнен
var query = allUnits.Where(u => u.Health > 0);
// Изменили данные
allUnits[0].Health = 0;
// Запрос выполнится ЗДЕСЬ с уже обновленными данными
var result = query.ToList();Чтобы выполнить сразу — используйте .ToList() или .ToArray():
var result = allUnits.Where(u => u.Health > 0).ToList(); // Выполняется сразуПроизводительность LINQ в играх
⚠️ Проблемы
- Аллокации памяти: Каждый .Where(), .Select(), .ToList() создает новые объекты.
- GC (Garbage Collector): Частые аллокации вызывают сборку мусора → фризы.
- Медленнее циклов: LINQ примерно в 2-3 раза медленнее обычных foreach.
✅ Когда использовать LINQ
|
Ситуация
|
LINQ
|
Цикл
|
|---|---|---|
|
UI, меню, инвентарь
|
✅ Да
|
❌ Избыточно
|
|
Сохранение/загрузка
|
✅ Да
|
❌ Избыточно
|
|
Запросы к серверу
|
✅ Да
|
❌ Избыточно
|
Update() каждый кадр |
❌ Нет
|
✅ Да
|
|
Тысячи юнитов в бою
|
❌ Нет
|
✅ Да
|
|
Прототипирование
|
✅ Да
|
⚠️ Дольше
|
🚫 Избегайте в Update()
// ❌ ПЛОХО: Вызывается 60 раз в секунду
void Update()
{
var aliveUnits = allUnits.Where(u => u.Health > 0).ToList();
// Аллокация памяти каждый кадр!
}
// ✅ ХОРОШО: Кэшируем результат
private List<Unit> _aliveUnits;
private float _cacheTime;
void Update()
{
if (Time.time - _cacheTime > 1f) // Обновляем раз в секунду
{
_aliveUnits = allUnits.Where(u => u.Health > 0).ToList();
_cacheTime = Time.time;
}
}
// ✅ ЛУЧШЕ: Обычный цикл без аллокаций
void Update()
{
foreach (var unit in allUnits)
{
if (unit.Health > 0)
{
unit.Process();
}
}
}LINQ без аллокаций (Unity LINQ)
Для критичных к производительности мест используйте:
Вариант 1: Обычный цикл
List<Unit> GetAliveUnits(List<Unit> units)
{
var result = new List<Unit>();
for (int i = 0; i < units.Count; i++)
{
if (units[i].Health > 0)
{
result.Add(units[i]);
}
}
return result;
}Вариант 2: Unity.Collections (ECS)
using Unity.Collections;
NativeList<Unit> aliveUnits = new NativeList<Unit>(Allocator.Temp);
foreach (var unit in allUnits)
{
if (unit.Health > 0)
{
aliveUnits.Add(unit);
}
}Вариант 3: Библиотеки без аллокаций
- Unity.Linq — LINQ без аллокаций
- LinqAF — LINQ без аллокаций
- Collections-Pooled — Пулы для коллекций
Расширенные возможности LINQ
Цепочка методов (Method Chaining)
var result = allUnits
.Where(u => u.IsSelected) // 1. Фильтруем
.Where(u => u.Health > 0) // 2. Еще фильтр
.OrderByDescending(u => u.Power) // 3. Сортируем
.Take(5) // 4. Берем топ-5
.Select(u => u.Id) // 5. Преобразуем
.ToList(); // 6. В списокLINQ Query Syntax (Альтернативный синтаксис)
// Method Syntax (более популярный)
var result = allUnits.Where(u => u.Health > 0).OrderBy(u => u.Level).ToList();
// Query Syntax (похож на SQL)
var result = (from u in allUnits
where u.Health > 0
orderby u.Level
select u).ToList();Группировка с агрегацией
// Посчитать количество и общую мощь юнитов каждого типа
var stats = allUnits
.GroupBy(u => u.Type)
.Select(g => new
{
Type = g.Key,
Count = g.Count(),
TotalPower = g.Sum(u => u.Power),
AvgLevel = g.Average(u => u.Level)
})
.ToList();
foreach (var stat in stats)
{
Debug.Log($"{stat.Type}: {stat.Count} юнитов, мощь {stat.TotalPower}");
}Join (Объединение двух коллекций)
// Соединить юнитов с их улучшениями
var unitsWithUpgrades = allUnits
.Join(
allUpgrades,
unit => unit.Id,
upgrade => upgrade.UnitId,
(unit, upgrade) => new { Unit = unit, Upgrade = upgrade }
)
.ToList();Частые ошибки
|
Ошибка
|
Проблема
|
Решение
|
|---|---|---|
.First() без проверки |
Краш если элементов нет
|
Используйте
.FirstOrDefault() |
.Single() при 0 или 2+ элементах |
Краш
|
Используйте
.SingleOrDefault() |
|
LINQ в
Update() |
Фризы из-за аллокаций
|
Используйте кэш или циклы
|
|
Забывают
.ToList() |
Отложенное выполнение, повторные вычисления
|
Добавляйте
.ToList() в конце |
|
Много цепочек
|
Сложно отлаживать
|
Разбивайте на переменные
|
// ❌ ПЛОХО: Может крашнуть
var unit = allUnits.First(u => u.IsSelected);
// ✅ ХОРОШО: Безопасно
var unit = allUnits.FirstOrDefault(u => u.IsSelected);
if (unit != null) { /* ... */ }
// ❌ ПЛОХО: Запрос выполняется каждый раз при обращении
var query = allUnits.Where(u => u.Health > 0);
var count = query.Count(); // Выполняется
var list = query.ToList(); // Выполняется снова!
// ✅ ХОРОШО: Выполняется один раз
var list = allUnits.Where(u => u.Health > 0).ToList();
var count = list.Count;Чек-лист для вашей игры
- UI и меню — ✅ Используйте LINQ freely
- Инвентарь — ✅ LINQ идеален для фильтрации предметов
- Сохранение данных — ✅ LINQ для преобразования перед сохранением
- Боевая логика — ⚠️ Осторожно, кэшируйте результаты
- Update() цикл — ❌ Избегайте LINQ, используйте обычные циклы
- Поиск путей, AI — ❌ Избегайте LINQ для производительности
- Запросы к серверу — ✅ LINQ для обработки ответов
Итоговая таблица методов
|
Категория
|
Методы
|
|---|---|
|
Фильтрация
|
Where(), First(), FirstOrDefault(), Single(), Take(), Skip() |
|
Преобразование
|
Select(), SelectMany(), Cast(), OfType() |
|
Агрегация
|
Count(), Sum(), Average(), Min(), Max(), Any(), All() |
|
Сортировка
|
OrderBy(), OrderByDescending(), ThenBy(), Reverse() |
|
Группировка
|
GroupBy(), ToDictionary(), ToLookup() |
|
Объединение
|
Union(), Concat(), Intersect(), Except(), Join() |
|
Преобразование
|
ToList(), ToArray(), ToDictionary(), IEnumerable() |
Пример: Полный класс с LINQ для стратегии
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class ArmyManager : MonoBehaviour
{
public List<Unit> allUnits;
// Получить армию игрока
public List<Unit> GetPlayerArmy(string playerId)
{
return allUnits
.Where(u => u.OwnerId == playerId && u.Health > 0)
.ToList();
}
// Получить топ юнитов по мощи
public List<Unit> GetTopUnits(int count)
{
return allUnits
.Where(u => u.Health > 0)
.OrderByDescending(u => u.Power)
.Take(count)
.ToList();
}
// Посчитать общую стоимость содержания армии
public int GetTotalUpkeep(string playerId)
{
return allUnits
.Where(u => u.OwnerId == playerId)
.Sum(u => u.UpkeepCost);
}
// Найти юнитов, которых можно улучшить
public List<Unit> GetUpgradeableUnits(string playerId, int maxCost)
{
return allUnits
.Where(u => u.OwnerId == playerId &&
u.CanUpgrade &&
u.UpgradeCost <= maxCost)
.OrderBy(u => u.UpgradeCost)
.ToList();
}
// Проверить, есть ли армия игрока
public bool HasArmy(string playerId)
{
return allUnits.Any(u => u.OwnerId == playerId && u.Health > 0);
}
// Сгруппировать юнитов по типу для UI
public Dictionary<UnitType, int> GetUnitCounts(string playerId)
{
return allUnits
.Where(u => u.OwnerId == playerId && u.Health > 0)
.GroupBy(u => u.Type)
.ToDictionary(g => g.Key, g => g.Count());
}
}LINQ — это мощный инструмент, который делает код чище и выразительнее. Он идеально подойдет для работы с инвентарем, постройками, квестами и UI. Главное — помните о производительности и не используйте LINQ в горячих циклах (Update(), бой, AI).





