Menu

Parametri opzionali e argomenti con nome in C#: default e regole

Come funzionano i parametri opzionali e gli argomenti con nome in C#: valori predefiniti e la regola della costante in fase di compilazione, ordine dei parametri, saltare argomenti usando il nome, parametri opzionali contro overload, attributi caller info e la trappola del versioning dei default copiati nei chiamanti.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

Un parametro opzionale ha un valore predefinito nella dichiarazione del metodo, quindi chi chiama può ometterlo. Un argomento con nome passa un valore usando il nome del parametro invece della posizione. Insieme permettono a un solo metodo di servire molti tipi di chiamata senza una pila di overload.

Output:

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

La terza chiamata salta subject e imposta urgent per nome; senza argomenti con nome dovrebbe passare di nuovo "(no subject)" solo per arrivare alla terza posizione. L'ultima chiamata passa ogni argomento per nome in un ordine diverso dalla dichiarazione, ed è consentito.

Regole per i valori predefiniti

Un valore predefinito deve essere qualcosa che il compilatore può calcolare:

  • una costante (3, "INFO", true, 1.5m, un campo const, un membro di un enum)
  • null per un tipo riferimento o nullable
  • default(T), oppure new T() per un tipo valore T

Tutto ciò che viene valutato in esecuzione è rifiutato. Il caso classico è una data:

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

La soluzione standard è un parametro nullable con null come valore predefinito, risolto nel corpo:

Output:

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

Lo stesso trucco vale per le collezioni: un default new List<string>() non è una costante, quindi usa null come default e crea la lista all'interno. Così eviti anche il bug del default mutabile condiviso tipico di Python, che C# esclude permettendo solo costanti.

Regole di ordine

Prima vengono i parametri obbligatori, poi quelli opzionali, e per ultimo un eventuale array params:

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

I parametri ref e out non possono essere opzionali.

Dal lato della chiamata, gli argomenti posizionali riempiono i parametri a partire dall'inizio. Gli argomenti con nome possono seguirli in qualsiasi ordine. Da C# 7.2 un argomento con nome può comparire anche prima di uno posizionale, ma solo quando si trova nella sua posizione (SendEmail("a@b.c", subject: "Hi", true)); nelle versioni precedenti gli argomenti con nome devono stare tutti alla fine. Omettere un parametro obbligatorio, anche nominando gli altri, è un errore di compilazione.

Argomenti con nome per la leggibilità

Gli argomenti con nome sono utili anche quando nulla è opzionale. I letterali true, false, null e i numeri da soli non dicono nulla nel punto della chiamata:

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

Rinominare un parametro diventa una modifica che rompe i chiamanti che usano il nome, cosa da ricordare in una libreria pubblica.

Parametri opzionali o overload

Prima di C# 4, la stessa flessibilità richiedeva un overload per ogni combinazione. I parametri opzionali li riducono a un unico metodo:

// 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) { /* ... */ }

Quando esistono entrambi, la risoluzione degli overload preferisce un candidato che non ha bisogno di riempire alcun valore predefinito:

Output:

Greet()
Greet(string) with Lena

Greet() corrisponde a entrambi i metodi, e il compilatore sceglie quello senza parametri opzionali omessi. Mescolare le due tecniche sullo stesso nome produce soprattutto chiamate di cui è difficile prevedere la destinazione, quindi scegline una per ogni metodo.

Scegli gli overload quando le varianti richiedono codice diverso o tipi di parametro diversi, e i parametri opzionali quando differiscono solo nei valori predefiniti.

I default vengono copiati nel chiamante

Un valore predefinito non viene cercato in esecuzione. Il compilatore lo copia in ogni punto di chiamata quando compila il codice chiamante. Connect("api.shop.com") viene compilato come Connect("api.shop.com", 443, 30).

Questo ha una conseguenza per le librerie. Supponi che la versione 1 di un pacchetto pubblichi Connect(string host, int timeoutSeconds = 30) e che la versione 2 cambi il default in 10. Un'applicazione compilata con la versione 1 continua a passare 30 dopo che hai inserito la DLL della versione 2, finché l'applicazione stessa non viene ricompilata. Anche aggiungere un nuovo parametro opzionale a un metodo pubblico esistente rompe i chiamanti già compilati, perché la firma del metodo è cambiata e loro cercano ancora quella vecchia (una MissingMethodException in esecuzione).

All'interno di una singola applicazione compilata tutta insieme questo non conta mai. Per le API pubbliche nei pacchetti NuGet, gli overload (che tengono i default dentro la libreria) o un default null risolto nel corpo evitano il problema.

I default e il tipo dichiarato

La stessa regola della fase di compilazione fa sì che, quando un'interfaccia e una classe dichiarano entrambe dei default, il default venga dal tipo della variabile attraverso cui chiami, non dall'oggetto:

Output:

printing "report" x5
printing "report" x1

Stesso oggetto, due default diversi. Mantieni identici i valori predefiniti tra un'interfaccia e le sue implementazioni, oppure dichiarali in un solo punto.

Attributi caller info

I parametri opzionali alimentano anche gli attributi caller info di System.Runtime.CompilerServices. Il compilatore li riempie con i dettagli del punto di chiamata:

Output:

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

È così che le librerie di logging registrano da dove arriva un messaggio senza che chi chiama debba scriverlo, e che le implementazioni di INotifyPropertyChanged ottengono il nome della proprietà. [CallerFilePath] aggiunge allo stesso modo il percorso del file sorgente.

Domande frequenti

Come rendo opzionale un parametro in C#?

Dagli un valore predefinito nella dichiarazione: static void Log(string message, string level = "INFO"). Chi chiama può poi scrivere Log("started") oppure Log("failed", "ERROR"). I parametri opzionali devono venire dopo tutti quelli obbligatori, e il valore predefinito deve essere una costante in fase di compilazione.

Cosa sono gli argomenti con nome in C#?

Argomenti passati con il nome del parametro: SendEmail(to: "ana@mail.com", urgent: true). Ti permettono di saltare parametri opzionali in mezzo, passare gli argomenti in qualsiasi ordine e rendere leggibili a colpo d'occhio le chiamate con true/false letterali o numeri.

Perché non posso usare DateTime.Now come valore predefinito di un parametro?

I valori predefiniti devono essere costanti in fase di compilazione, e DateTime.Now viene calcolato in esecuzione, quindi il compilatore segnala CS1736, Default parameter value for 'at' must be a compile-time constant. Usa invece un parametro nullable: DateTime? at = null, poi DateTime time = at ?? DateTime.Now; nel corpo.

Meglio i parametri opzionali o gli overload dei metodi in C#?

I parametri opzionali sono più semplici quando le varianti differiscono solo per i valori predefiniti. Gli overload sono migliori quando le varianti richiedono logica o tipi diversi, e nelle librerie pubbliche, perché un valore predefinito viene compilato dentro ogni chiamante e cambiarlo in seguito non raggiunge il codice già compilato.

Cosa significa "Optional parameters must appear after all required parameters"?

Errore CS1737: un parametro con valore predefinito è seguito da uno senza. Sposta prima i parametri obbligatori: (string to, bool urgent = false), non (bool urgent = false, string to). Dopo un parametro opzionale possono seguire solo altri parametri opzionali o un array params.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA