Tall LINQ vs Wide LINQ

Tall LINQ vs Wide LINQ – jak pisać czytelny i bezpieczny LINQ w C#

Tall LINQ vs Wide LINQ – jak pisać czytelny i bezpieczny LINQ w C#

Pisanie zapytań LINQ w C# wygląda na proste… dopóki kod nie zacznie żyć własnym życiem. Jedna długa linia, kilka Where, Select, OrderBy i nagle czytelność znika, code review boli, a każda zmiana grozi błędem. W tym artykule pokażę Ci, dlaczego Tall LINQ wygrywa z Wide LINQ, i jak pisać zapytania LINQ, które są czytelne, bezpieczne i przyjazne w utrzymaniu.

Czym jest Wide LINQ?

Wide LINQ to sposób zapisu zapytania LINQ w jednej (lub prawie jednej) długiej linii kodu, gdzie kolejne operacje są łączone poziomo.

var eligibleHeroes  = heroes.Where(hero => hero.Agility > 80).OrderBy(hero => hero.Level).Select(hero => new { hero.Name, hero.Class, hero.Level }).ToList();

Dlaczego Wide LINQ jest problemem?

❌ trudny do czytania – mózg skanuje kod w poziomie
❌ trudny do debugowania – nie wiesz, który etap zmienia dane
❌ trudny do rozbudowy – każda nowa operacja zwiększa chaos

To nie jest kwestia gustu. To problem ergonomii kodu.


Tall LINQ – lepsze podejście do zapytań LINQ

Czym jest Tall LINQ?

Tall LINQ to zapis zapytań LINQ w pionie, gdzie:

  • jedna operacja LINQ = jedna linia
  • każdy etap pipeline’u jest wyraźnie widoczny
  • kolejność przetwarzania danych jest oczywista
var eligibleHeroes  = heroes
    .Where(hero => hero.Agility > 80)   // Filtrowanie
    .OrderBy(hero => hero.Level)        // Sortowanie
    .Select(hero => new { hero.Name, hero.Class, hero.Level }) // Projekcja
    .ToList();    // Materializacja

Dlaczego Tall LINQ działa lepiej?

✔ czytelność na pierwszy rzut oka
✔ łatwe debugowanie krok po kroku
✔ bezpieczne zmiany i refaktoryzacja
✔ kod odporny na zmiany IDE, fontu i szerokości ekranu

Tall LINQ pokazuje przepływ danych, a nie ukrywa go w jednej linii.


Najczęstsze błędy przy pisaniu LINQ

❌ Antywzorzec 1: „Bo mieści się w jednej linii”

Monitor nie jest specyfikacją architektury. Kod musi być czytelny w:

  • code review
  • diffie Gita
  • historii projektu

Wide LINQ łamie się przy każdej zmianie kontekstu.


❌ Antywzorzec 2: LINQ jako konkurs na one-linery

var result = items.Where(x => x.IsActive && x.Count > 5)
    .Select(x => DoSomething(x))
    .Where(x => x != null)
    .OrderByDescending(x => x.Score)
    .Take(10)
    .ToList();

W wersji „wide” byłoby to kompletnie nieczytelne. LINQ to pipeline transformacji, nie jedno zdanie.


❌ Antywzorzec 3: Brak intencji w warunkach

.Where(h => h.IsActive && h.Agility > 80 && h.Level > 10 && h.Class != HeroClass.Mage)

Dlaczego ten warunek istnieje? Co oznacza „eligible hero”?

✅ Lepsze rozwiązanie: Tall LINQ + intencja

.Where(IsEligibleHero)
private static bool IsEligibleHero(Hero hero)
{
    return hero.IsActive
        && hero.Agility > 80
        && hero.Level > 10
        && hero.Class != HeroClass.Mage;
}

Teraz:
✔ LINQ mówi CO robimy
✔ metoda mówi DLACZEGO
✔ logikę da się testować i używać ponownie


Kiedy stosować Tall LINQ?

Stosuj Tall LINQ, gdy:

  • piszesz kod produkcyjny
  • pracujesz w zespole
  • zapytania będą rozwijane w czasie

Nie przesadzaj, gdy:

  • robisz krótki przykład w dokumentacji
  • piszesz jednorazowy skrypt
  • zapytanie ma maksymalnie 2 operacje

Checklist: Czy Twój LINQ jest czytelny?

  • Jedna operacja = jedna linia
  • Każdy etap pipeline’u jest widoczny
  • Skomplikowane warunki są w metodach
  • Kod da się łatwo debugować

Jeśli zaznaczyłeś wszystko – jesteś na dobrej drodze.


Podsumowanie

Tall LINQ to nie moda. To praktyka, która ratuje projekty przed chaosem.
Kod piszesz raz. Czytasz go dziesiątki razy. Zrób przysługę sobie i zespołowi.

Czytelność > ego programisty.


Zobacz też


Call to Action

👉 Zostaw komentarz: jak piszesz LINQ w swoim projekcie?
👉 Udostępnij artykuł komuś, kto nadal pisze LINQ w jednej linii.


Kod Odcinka

Program.cs

using DevHobby.Code.TallVsWideLINQ;
using System.Text.Json;
using System.Text.Json.Serialization;

var jsonPath = Path.Combine(AppContext.BaseDirectory, "Data", "heroes.json");
var json = File.ReadAllText(jsonPath);

var heroes = JsonSerializer.Deserialize<List<Hero>>(json, new JsonSerializerOptions()
{
    PropertyNameCaseInsensitive = true,
    Converters =
    {
        new JsonStringEnumConverter()
    }
}) ?? throw new InvalidOperationException("Nie udało się wczytać danych bohaterów.");


var eligibleHeroes  = heroes
    .Where(hero => hero.Agility > 80)
    .OrderBy(hero => hero.Level)
    .Select(hero => new { hero.Name, hero.Class, hero.Level })
    .ToList();


Console.WriteLine("=== Eligible Heroes ===");
foreach (var hero in eligibleHeroes)
{
    Console.WriteLine(
        $"{hero.Name,-10} | " +
        $"{hero.Class,-8} | " +
        $"Lvl: {hero.Level,2} | "
    );
}

Hero.cs

namespace DevHobby.Code.TallVsWideLINQ;

public class Hero
{
    public int Id { get; set; }

    public string Name { get; set; } = default!;

    public HeroClass Class { get; set; }

    public int Level { get; set; }

    public int Agility { get; set; }

    public bool IsActive { get; set; }

    public DateTime LastBattleDate { get; set; }
}

public enum HeroClass
{
    Warrior,
    Archer,
    Mage,
    Rogue,
    Paladin
}

heroes.json

[
  {
    "Id": 1,
    "Name": "Arthos",
    "Class": "Warrior",
    "Level": 12,
    "Agility": 65,
    "IsActive": true,
    "LastBattleDate": "2026-01-10T14:20:00Z"
  },
  {
    "Id": 2,
    "Name": "Lyra",
    "Class": "Archer",
    "Level": 18,
    "Agility": 92,
    "IsActive": true,
    "LastBattleDate": "2026-01-22T09:10:00Z"
  },
  {
    "Id": 3,
    "Name": "Mordren",
    "Class": "Mage",
    "Level": 20,
    "Agility": 55,
    "IsActive": false,
    "LastBattleDate": "2025-11-01T18:45:00Z"
  },
  {
    "Id": 4,
    "Name": "Kara",
    "Class": "Rogue",
    "Level": 15,
    "Agility": 88,
    "IsActive": true,
    "LastBattleDate": "2026-01-25T21:00:00Z"
  },
  {
    "Id": 5,
    "Name": "Thalion",
    "Class": "Paladin",
    "Level": 22,
    "Agility": 60,
    "IsActive": true,
    "LastBattleDate": "2025-12-15T11:30:00Z"
  },
  {
    "Id": 6,
    "Name": "Selene",
    "Class": "Mage",
    "Level": 14,
    "Agility": 70,
    "IsActive": true,
    "LastBattleDate": "2026-01-03T16:10:00Z"
  },
  {
    "Id": 7,
    "Name": "Brom",
    "Class": "Warrior",
    "Level": 9,
    "Agility": 50,
    "IsActive": true,
    "LastBattleDate": "2025-10-10T08:00:00Z"
  },
  {
    "Id": 8,
    "Name": "Nyx",
    "Class": "Rogue",
    "Level": 19,
    "Agility": 95,
    "IsActive": true,
    "LastBattleDate": "2026-01-27T23:15:00Z"
  },
  {
    "Id": 9,
    "Name": "Eldrin",
    "Class": "Archer",
    "Level": 11,
    "Agility": 78,
    "IsActive": false,
    "LastBattleDate": "2025-09-05T13:40:00Z"
  },
  {
    "Id": 10,
    "Name": "Varok",
    "Class": "Warrior",
    "Level": 25,
    "Agility": 72,
    "IsActive": true,
    "LastBattleDate": "2026-01-15T17:55:00Z"
  },
  {
    "Id": 11,
    "Name": "Iria",
    "Class": "Mage",
    "Level": 16,
    "Agility": 83,
    "IsActive": true,
    "LastBattleDate": "2026-01-20T10:25:00Z"
  },
  {
    "Id": 12,
    "Name": "Doran",
    "Class": "Paladin",
    "Level": 13,
    "Agility": 58,
    "IsActive": false,
    "LastBattleDate": "2025-08-18T07:00:00Z"
  },
  {
    "Id": 13,
    "Name": "Riven",
    "Class": "Rogue",
    "Level": 21,
    "Agility": 90,
    "IsActive": true,
    "LastBattleDate": "2026-01-28T19:45:00Z"
  },
  {
    "Id": 14,
    "Name": "Alaric",
    "Class": "Warrior",
    "Level": 17,
    "Agility": 68,
    "IsActive": true,
    "LastBattleDate": "2026-01-05T12:10:00Z"
  },
  {
    "Id": 15,
    "Name": "Fenra",
    "Class": "Archer",
    "Level": 14,
    "Agility": 85,
    "IsActive": true,
    "LastBattleDate": "2026-01-18T15:30:00Z"
  },
  {
    "Id": 16,
    "Name": "Zarek",
    "Class": "Mage",
    "Level": 8,
    "Agility": 40,
    "IsActive": true,
    "LastBattleDate": "2025-07-01T09:00:00Z"
  },
  {
    "Id": 17,
    "Name": "Kael",
    "Class": "Rogue",
    "Level": 23,
    "Agility": 97,
    "IsActive": true,
    "LastBattleDate": "2026-01-29T08:00:00Z"
  },
  {
    "Id": 18,
    "Name": "Morwen",
    "Class": "Paladin",
    "Level": 19,
    "Agility": 62,
    "IsActive": true,
    "LastBattleDate": "2026-01-12T20:40:00Z"
  },
  {
    "Id": 19,
    "Name": "Sylas",
    "Class": "Archer",
    "Level": 10,
    "Agility": 74,
    "IsActive": false,
    "LastBattleDate": "2025-06-10T14:00:00Z"
  },
  {
    "Id": 20,
    "Name": "Vexa",
    "Class": "Rogue",
    "Level": 18,
    "Agility": 89,
    "IsActive": true,
    "LastBattleDate": "2026-01-26T18:20:00Z"
  }
]

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *