Menu

Parametry opcjonalne i argumenty nazwane w C#: wartości domyślne i zasady

Jak działają parametry opcjonalne i argumenty nazwane w C#: wartości domyślne i zasada stałej czasu kompilacji, kolejność parametrów, pomijanie argumentów po nazwie, parametry opcjonalne a przeciążenia, atrybuty informacji o wywołującym i pułapka wersjonowania wartości domyślnych wkompilowanych w wywołującego.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

Parametr opcjonalny ma w deklaracji metody wartość domyślną, więc wywołujący mogą go pominąć. Argument nazwany przekazuje wartość według nazwy parametru, a nie pozycji. Razem pozwalają jednej metodzie obsłużyć wiele kształtów wywołania bez sterty przeciążeń.

Wynik:

to ana@mail.com: (no subject), retries 3
to ben@mail.com: Invoice #1042, retries 3
to cy@mail.com: (no subject) [URGENT], retries 3
to dev@mail.com: Build failed, retries 0

Trzecie wywołanie pomija subject i ustawia urgent po nazwie; bez argumentów nazwanych musiałoby ponownie przekazać "(no subject)" tylko po to, by dojść do trzeciej pozycji. Ostatnie wywołanie przekazuje wszystkie argumenty po nazwie w innej kolejności niż w deklaracji, co jest dozwolone.

Zasady wartości domyślnych

Wartość domyślna musi być czymś, co kompilator potrafi obliczyć:

  • stałą (3, "INFO", true, 1.5m, polem const, składową enuma)
  • null dla typu referencyjnego lub nullable
  • default(T) albo new T() dla typu wartościowego T

Wszystko, co obliczane jest w czasie działania, zostaje odrzucone. Klasyczny przypadek to data:

static void Schedule(string task, DateTime at = DateTime.Now) { }
// error CS1736: Default parameter value for 'at' must be a compile-time constant

Standardowe obejście to parametr nullable z domyślną wartością null, rozstrzygany w ciele metody:

Wynik:

backup at 2026-01-01 09:00, tags: 0
report at 2026-03-15 18:30, tags: 0
deploy at 2026-01-01 09:00, tags: 2

Ta sama sztuczka dotyczy kolekcji: wartość domyślna new List<string>() nie jest stałą, więc ustaw domyślnie null i utwórz listę w środku. To również pozwala uniknąć znanego z Pythona błędu współdzielonej, modyfikowalnej wartości domyślnej, który C# wyklucza, dopuszczając tylko stałe.

Zasady kolejności

Najpierw parametry wymagane, potem opcjonalne, a tablica params (jeśli jest) na końcu:

static void Log(string message, string level = "INFO", params string[] tags) { }   // OK

static void Log(string level = "INFO", string message) { }
// error CS1737: Optional parameters must appear after all required parameters

Parametry ref i out nie mogą być opcjonalne.

Po stronie wywołania argumenty pozycyjne wypełniają parametry od początku. Argumenty nazwane mogą następować po nich w dowolnej kolejności. Od C# 7.2 argument nazwany może też pojawić się przed pozycyjnym, ale tylko wtedy, gdy stoi na swojej własnej pozycji (SendEmail("a@b.c", subject: "Hi", true)); w starszych wersjach wszystkie argumenty nazwane muszą być na końcu. Pominięcie wymaganego parametru, nawet przy nazwaniu innych, to błąd kompilacji.

Argumenty nazwane dla czytelności

Argumenty nazwane przydają się nawet wtedy, gdy nic nie jest opcjonalne. Dosłowne true, false, null i gołe liczby niczego nie mówią w miejscu wywołania:

ResizeImage(photo, 800, 600, true, false);                                   // which is which?
ResizeImage(photo, width: 800, height: 600, keepAspect: true, upscale: false);

Zmiana nazwy parametru staje się wtedy zmianą łamiącą zgodność dla wywołujących, którzy używają tej nazwy, o czym warto pamiętać w publicznej bibliotece.

Parametry opcjonalne a przeciążenia

Przed C# 4 ta sama elastyczność wymagała przeciążenia dla każdej kombinacji. Parametry opcjonalne zwijają je w jedną metodę:

// overloads
static void Connect(string host) => Connect(host, 443);
static void Connect(string host, int port) => Connect(host, port, 30);
static void Connect(string host, int port, int timeoutSeconds) { /* ... */ }

// one method with optional parameters
static void Connect(string host, int port = 443, int timeoutSeconds = 30) { /* ... */ }

Gdy istnieją oba warianty, rozstrzyganie przeciążeń preferuje kandydata, który nie wymaga uzupełniania żadnej wartości domyślnej:

Wynik:

Greet()
Greet(string) with Lena

Greet() pasuje do obu metod, a kompilator wybiera tę bez pominiętego parametru opcjonalnego. Mieszanie obu technik dla tej samej nazwy daje głównie wywołania, których cel trudno przewidzieć, więc wybierz jedną na metodę.

Wybieraj przeciążenia, gdy warianty potrzebują innego kodu albo innych typów parametrów, a parametry opcjonalne, gdy różnią się tylko wartościami domyślnymi.

Wartości domyślne są wkompilowane w wywołującego

Wartość domyślna nie jest wyszukiwana w czasie działania. Kompilator kopiuje ją do każdego miejsca wywołania podczas kompilacji kodu wywołującego. Connect("api.shop.com") kompiluje się do Connect("api.shop.com", 443, 30).

Ma to konsekwencje dla bibliotek. Załóżmy, że wersja 1 pakietu udostępnia Connect(string host, int timeoutSeconds = 30), a wersja 2 zmienia wartość domyślną na 10. Aplikacja skompilowana z wersją 1 dalej przekazuje 30 po podmianie DLL na wersję 2, dopóki sama nie zostanie ponownie skompilowana. Dodanie nowego parametru opcjonalnego do istniejącej metody publicznej również psuje już skompilowanych wywołujących, bo sygnatura metody się zmieniła, a oni wciąż szukają starej (MissingMethodException w czasie działania).

W obrębie jednej aplikacji kompilowanej w całości nigdy nie ma to znaczenia. W publicznych API w pakietach NuGet problemu unikniesz dzięki przeciążeniom (które trzymają wartości domyślne w bibliotece) albo domyślnemu null rozstrzyganemu w ciele metody.

Wartości domyślne a zadeklarowany typ

Ta sama zasada czasu kompilacji oznacza, że gdy interfejs i klasa deklarują wartości domyślne, wartość domyślna pochodzi z typu zmiennej, przez którą wywołujesz metodę, a nie z obiektu:

Wynik:

printing "report" x5
printing "report" x1

Ten sam obiekt, dwie różne wartości domyślne. Trzymaj identyczne wartości domyślne w interfejsie i jego implementacjach albo deklaruj je tylko w jednym miejscu.

Atrybuty informacji o wywołującym

Parametry opcjonalne napędzają też atrybuty informacji o wywołującym z System.Runtime.CompilerServices. Kompilator wypełnia je szczegółami miejsca wywołania:

Wynik:

[Main:18] starting
[SaveOrder:13] order saved

W ten sposób biblioteki logujące zapisują, skąd pochodzi komunikat, bez wpisywania tego przez wywołującego, a implementacje INotifyPropertyChanged dostają nazwę właściwości. [CallerFilePath] w ten sam sposób dodaje ścieżkę pliku źródłowego.

Najczęściej zadawane pytania

Jak zrobić parametr opcjonalny w C#?

Nadaj mu wartość domyślną w deklaracji: static void Log(string message, string level = "INFO"). Wywołujący mogą wtedy napisać Log("started") albo Log("failed", "ERROR"). Parametry opcjonalne muszą występować po wszystkich wymaganych, a wartość domyślna musi być stałą czasu kompilacji.

Czym są argumenty nazwane w C#?

To argumenty przekazywane z nazwą parametru: SendEmail(to: "ana@mail.com", urgent: true). Pozwalają pominąć parametry opcjonalne w środku, przekazywać argumenty w dowolnej kolejności i sprawiają, że wywołania z dosłownymi true/false albo liczbami są czytelne od razu.

Dlaczego nie mogę użyć DateTime.Now jako domyślnej wartości parametru?

Wartości domyślne muszą być stałymi czasu kompilacji, a DateTime.Now jest obliczane w czasie działania, więc kompilator zgłasza CS1736, Default parameter value for 'at' must be a compile-time constant. Zamiast tego użyj parametru nullable: DateTime? at = null, a potem w ciele DateTime time = at ?? DateTime.Now;.

Parametry opcjonalne czy przeciążenia metod w C#?

Parametry opcjonalne są prostsze, gdy warianty różnią się tylko wartościami domyślnymi. Przeciążenia są lepsze, gdy warianty potrzebują innej logiki lub innych typów, a także w publicznych bibliotekach, bo wartość domyślna jest wkompilowana w każdego wywołującego i późniejsza jej zmiana nie dociera do już skompilowanego kodu.

Co oznacza "Optional parameters must appear after all required parameters"?

Błąd CS1737: po parametrze z wartością domyślną występuje parametr bez niej. Przenieś wymagane parametry na początek: (string to, bool urgent = false), a nie (bool urgent = false, string to). Po parametrze opcjonalnym mogą wystąpić tylko kolejne parametry opcjonalne albo tablica params.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ