🔥 Zapisy zamknięte, ale możesz pobrać Roadmapę .NET i dołączyć do listy oczekujących — Pobierz i dołącz do Listy VIP →

Extension Methods w C# — wywołania łańcuchowe (Fluent API)

poprzednim artykule poznałeś podstawy metod rozszerzających — składnię, słowo kluczowe this i zasady ich definiowania. Tutaj skupiamy się na jednym, konkretnym zastosowaniu: budowaniu wywołań łańcuchowych (method chaining), czyli serii operacji zapisanych jako jeden, płynny ciąg — dokładnie tak, jak działa LINQ (list.Where(...).Select(...).ToList()).

Warunek konieczny: zwracaj ten sam typ

Klucz do wywołań łańcuchowych jest prosty: gdy metoda rozszerzająca zwraca wartość tego samego typu, co jej argument wejściowy, możesz od razu wywołać na wyniku kolejną metodę — rozszerzającą albo standardową. Każde kolejne wywołanie “doczepia się” do poprzedniego, tworząc łańcuch.

Przykład: StringBuilder

Załóżmy, że mamy klasę StringBuilder i chcemy dodać metody rozszerzające, które umożliwią nam łańcuchowe dodawanie tekstu, formatowanie itp.

using System;
using System.Text;

public static class StringBuilderExtensions
{
    public static StringBuilder AppendLineChainable(this StringBuilder sb, string value)
    {
        return sb.AppendLine(value);
    }

    public static StringBuilder AppendFormatted(this StringBuilder sb, string format, params object[] args)
    {
        return sb.AppendFormat(format, args);
    }
}

class Program
{
    static void Main()
    {
        StringBuilder sb = new StringBuilder();
        sb.AppendLineChainable("Hello, World!")
          .AppendFormatted("The current date and time: {0}", DateTime.Now)
          .AppendLineChainable("Goodbye, World!");

        Console.WriteLine(sb.ToString());
    }
}

AppendLineChainable i AppendFormatted zwracają ten sam StringBuilder, na którym operują — dzięki temu kolejne wywołanie może “doczepić się” bezpośrednio do wyniku poprzedniego, bez zapisywania wyników pośrednich do zmiennych.

Łańcuchowe operacje na liście liczb

Drugi, dydaktyczny przykład — metody rozszerzające dla List<int> pozwalające filtrować, transformować (mapować) i formatować dane w jednym, czytelnym łańcuchu:

  • Filter: filtruje listę według określonego kryterium,
  • Transform: przekształca (mapuje) każdy element listy,
  • AsString: konwertuje listę na sformatowany ciąg znaków.
using System;
using System.Collections.Generic;
using System.Linq;

public static class ListExtensions
{
    public static List<int> Filter(this List<int> list, Func<int, bool> predicate)
    {
        return list.Where(predicate).ToList();
    }

    public static List<int> Transform(this List<int> list, Func<int, int> transformer)
    {
        return list.Select(transformer).ToList();
    }

    public static string AsString(this List<int> list)
    {
        return string.Join(", ", list);
    }
}

class Program
{
    static void Main()
    {
        List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

        string result = numbers.Filter(n => n % 2 == 0) // filtruje parzyste liczby
                                .Transform(n => n * 2)   // podwaja każdą liczbę
                                .AsString();             // konwertuje listę na string

        Console.WriteLine(result);
    }
}

Zwróć uwagę: to dokładnie ten sam wzorzec, na którym zbudowane jest LINQ — Filter/Transform/AsString odpowiadają koncepcyjnie wbudowanym Where/Select/string.Join. Pisząc własne metody rozszerzające tego typu, w praktyce najczęściej lepiej sięgnąć po gotowe LINQ — ale zrozumienie, jak je zbudować samemu, pokazuje mechanikę, która stoi za każdym łańcuchem .Where(...).Select(...).

Mutowanie vs zwracanie nowej instancji

Warto świadomie rozróżnić dwa style łańcuchowania, bo mylenie ich bywa źródłem trudnych do znalezienia błędów:

  • Styl mutujący (jak AppendLineChainable powyżej) — metoda modyfikuje przekazany obiekt i zwraca tę samą referencję. Wywołanie sb.AppendLineChainable(...) zmienia oryginalny sb, nawet jeśli nie przypiszesz wyniku do zmiennej.
  • Styl niemutujący (jak Filter/Transform powyżej, wzorem LINQ) — metoda zwraca nową instancję, a oryginał pozostaje bez zmian. Jeśli zignorujesz zwróconą wartość (nie przypiszesz jej do zmiennej), efektywnie nic się nie stanie — częsty błąd początkujących przy LINQ, gdzie lista.Where(...) bez .ToList() i przypisania nie zmienia oryginalnej listy.

Dla własnych metod rozszerzających w stylu łańcuchowym warto trzymać się konsekwentnie jednego podejścia i jasno je udokumentować — mieszanie obu stylów w jednym łańcuchu wywołań łatwo prowadzi do nieoczekiwanego zachowania.

Zalety wywołań łańcuchowych

  • Czytelność — kolejne operacje są zapisane w sposób płynny i ciągły, bez zmiennych pośrednich.
  • Modularność — każda operacja to osobna, niezależna metoda, którą łatwo testować w izolacji.
  • Elastyczność — metody rozszerzające dodają nową funkcjonalność bez modyfikacji oryginalnej klasy.

Wywołania łańcuchowe najlepiej sprawdzają się przy budowaniu ciągów znaków, operacjach na kolekcjach i płynnych interfejsach konfiguracyjnych (fluent builders) — wszędzie tam, gdzie seria kolejnych, powiązanych operacji jest naturalnie sekwencyjna.

👨‍💻
Mariusz Jurczenko
Senior .NET Developer · 10+ lat doświadczenia komercyjnego

Programista .NET z doświadczeniem komercyjnym w firmach takich jak NFZ, Kamsoft, Diagnostyka, Hermes Reply Polska czy Etisoft Smart Solutions. Twórca kursów, z których skorzystało już ponad 11 000 osób w Strefie Kursów i ponad 1 000 kursantów na dev-hobby.pl.

Specjalizacja: Clean Code, Clean Architecture i uczenie programowania tak, żeby dało się je naprawdę zrozumieć — nie wykuć.

🚀 Co dalej?

Zobacz to w praktyce na wideo i pobierz darmową roadmapę, żeby ułożyć naukę w spójną ścieżkę do pierwszej pracy.

8 comments

Dodaj komentarz

czytanie to początek

Zamień wiedzę w umiejętności

Pobierz darmową Roadmapę .NET i ułóż takie tematy jak ten w spójną ścieżkę do pierwszej pracy.

Pobieram roadmapę →