Menu

C# Optional Parameters and Named Arguments: Defaults and Rules

How optional parameters and named arguments work in C#: default values and the compile-time constant rule, parameter order, skipping arguments by name, optional parameters versus overloads, caller info attributes, and the versioning pitfall of defaults baked into callers.

This page includes runnable editors - edit, run, and see output instantly.

An optional parameter has a default value in the method declaration, so callers may leave it out. A named argument passes a value by parameter name instead of position. Together they let one method serve many call shapes without a pile of overloads.

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

The third call skips subject and sets urgent by name; without named arguments it would have to pass "(no subject)" again just to reach the third position. The last call passes every argument by name in a different order from the declaration, which is legal.

Rules for Default Values

A default value must be something the compiler can compute:

  • a constant (3, "INFO", true, 1.5m, a const field, an enum member)
  • null for a reference or nullable type
  • default(T), or new T() for a value type T

Anything evaluated at run time is rejected. The classic case is a date:

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

The standard workaround is a nullable parameter with null as the default, resolved in the body:

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

The same trick covers collections: a default of new List<string>() is not a constant, so default to null and create the list inside. That also avoids the Python-style shared-mutable-default bug, which C# rules out by only allowing constants.

Order Rules

Required parameters come first, optional ones after, and a params array (if any) last:

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

ref and out parameters cannot be optional.

On the calling side, positional arguments fill parameters from the start. Named arguments can follow them in any order. Since C# 7.2, a named argument can also appear before a positional one, but only when it is in its own position (SendEmail("a@b.c", subject: "Hi", true)); in older versions named arguments must all come last. Leaving out a required parameter, even when naming others, is a compile error.

Named Arguments for Readability

Named arguments are useful even when nothing is optional. Literal true, false, null and bare numbers say nothing at the call site:

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

Renaming a parameter becomes a breaking change for callers that use the name, which is worth remembering in a public library.

Optional Parameters Versus Overloads

Before C# 4, the same flexibility needed an overload per combination. Optional parameters collapse them into one method:

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

When both exist, overload resolution prefers a candidate that does not need any default filled in:

Output:

Greet()
Greet(string) with Lena

Greet() matches both methods, and the compiler picks the one without an omitted optional parameter. Mixing the two techniques on the same name mostly produces calls whose target is hard to predict, so pick one per method.

Choose overloads when the variants need different code or different parameter types, and optional parameters when they only differ in default values.

Defaults Are Baked into the Caller

A default value is not looked up at run time. The compiler copies it into each call site when the calling code is compiled. Connect("api.shop.com") compiles to Connect("api.shop.com", 443, 30).

That has a consequence for libraries. Suppose version 1 of a package ships Connect(string host, int timeoutSeconds = 30), and version 2 changes the default to 10. An application compiled against version 1 keeps passing 30 after you drop in the version 2 DLL, until the application itself is recompiled. Adding a new optional parameter to an existing public method also breaks already-compiled callers, because the method's signature changed and they still look for the old one (a MissingMethodException at run time).

Within one application that is compiled as a whole this never matters. For public APIs in NuGet packages, overloads (which keep the defaults inside the library) or a null default resolved in the body avoid the problem.

Defaults and the Declared Type

The same compile-time rule means that when an interface and a class both declare defaults, the default comes from the type of the variable you call through, not from the object:

Output:

printing "report" x5
printing "report" x1

Same object, two different defaults. Keep default values identical between an interface and its implementations, or declare them in only one place.

Caller Information Attributes

Optional parameters also power the caller info attributes in System.Runtime.CompilerServices. The compiler fills them in with details of the call site:

Output:

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

This is how logging libraries record where a message came from without the caller typing it, and how INotifyPropertyChanged implementations get the property name. [CallerFilePath] adds the source file path in the same way.

Frequently Asked Questions

How do I make a parameter optional in C#?

Give it a default value in the declaration: static void Log(string message, string level = "INFO"). Callers can then write Log("started") or Log("failed", "ERROR"). Optional parameters must come after all required ones, and the default must be a compile-time constant.

What are named arguments in C#?

Arguments passed with the parameter name: SendEmail(to: "ana@mail.com", urgent: true). They let you skip optional parameters in the middle, pass arguments in any order, and make calls with literal true/false or numbers readable at a glance.

Why can't I use DateTime.Now as a default parameter value?

Defaults must be compile-time constants, and DateTime.Now is computed at run time, so the compiler reports CS1736, Default parameter value for 'at' must be a compile-time constant. Use a nullable parameter instead: DateTime? at = null, then DateTime time = at ?? DateTime.Now; in the body.

Should I use optional parameters or method overloads in C#?

Optional parameters are simpler when the variants only differ by default values. Overloads are better when variants need different logic or types, and in public libraries, because a default value is compiled into every caller and changing it later does not reach already-compiled code.

What does "Optional parameters must appear after all required parameters" mean?

Error CS1737: a parameter with a default value is followed by one without. Move the required parameters first: (string to, bool urgent = false), not (bool urgent = false, string to). After an optional parameter, only more optional parameters or a params array may follow.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED