Back to Blog
C#

C# Named Arguments: Syntax and Usage

c# named arguments: Learn how to use C# named arguments to make method calls clearer, avoid parameter-order mistakes, and handle optional parameters safely.

C#named argumentsmethod parametersoptional parameterscode readability
Diagram showing named arguments mapping parameter names to values in a C# method call.

c# named arguments requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

In C#, named arguments let you pass arguments to a method by specifying the parameter name, rather than relying solely on position. This changes how method calls are read and maintained, and it affects overload resolution in ways that are easy to miss.

What Named Arguments Are and Why They Exist

In C#, a method call normally passes arguments positionally: the first argument goes to the first parameter, the second to the second, and so on. Named arguments break that coupling by allowing you to write parameterName: value in the call. The compiler then matches the argument to the parameter with that name, regardless of its position in the parameter list.

The feature exists primarily for readability. When a method has several parameters of the same type, or when a boolean or numeric value is passed without context, a positional call can be opaque. Named arguments let the call document itself.

public void Configure(string host, int port, bool useTls) { // ... } // Positional call Configure("example.com", 443, true); // Named call Configure(host: "example.com", port: 443, useTls: true);

The named version makes it clear which value corresponds to which parameter. It also removes the risk of accidentally swapping two arguments of the same type.

Basic Syntax and Placement Rules

The syntax for a named argument is parameterName: argumentValue. The parameter name must match the name of a parameter in the method signature, and the argument value must be implicitly convertible to that parameter's type.

Named arguments can appear in any order in the call. The compiler resolves them by name, not by position.

Configure(useTls: true, host: "example.com", port: 443);

This is valid and behaves identically to the previous call. The order of named arguments does not affect the binding.

There is one important restriction: if you mix positional and named arguments, the positional arguments must be placed before any named arguments. The compiler enforces this to keep the mapping unambiguous.

// Valid: positional first, then named Configure("example.com", port: 443, useTls: true); // Invalid: named before positional Configure(host: "example.com", 443, useTls: true); // CS8323

The compiler error CS8323 states: "Named argument 'host' is used out-of-position but is followed by an unnamed argument." This is a hard error; there is no way to make the compiler accept it.

Mixing Positional and Named Arguments

When you mix positional and named arguments, the positional arguments must appear first. After the first named argument, every subsequent argument must also be named. This rule exists because once you start naming arguments, the compiler cannot reliably infer which parameter a positional argument would target if it appeared later.

// Allowed Configure("example.com", useTls: true, port: 443); // Not allowed Configure(useTls: true, "example.com", port: 443); // Compiler error

The reason is straightforward: the positional argument "example.com" would have to map to the first unnamed parameter, but the named argument useTls already consumed the parameter useTls. The compiler would need to skip over it, which is not supported.

In practice, mixing is useful when you want to name only the less obvious parameters while leaving the common ones positional. For example, a method with a long parameter list might have a few parameters that are always passed in the same order, and a few that are optional or rarely changed.

Named Arguments with Optional Parameters

Named arguments become especially valuable when a method has optional parameters. Without named arguments, you must supply values for all preceding optional parameters if you want to set a later one. With named arguments, you can skip any optional parameter and set only the ones you need.

public void Log(string message, string level = "Info", bool includeTimestamp = true) { // ... } // Positional: must provide level to set includeTimestamp Log("Server started", "Info", false); // Named: set only includeTimestamp Log("Server started", includeTimestamp: false);

This eliminates the need to pass default values explicitly and makes the call more readable. It also reduces the risk of passing a value to the wrong optional parameter when several have the same type.

One thing to keep in mind: when you use named arguments with optional parameters, the compiler still requires that all required parameters are supplied. You cannot skip a required parameter, even with a name.

Common Mistakes and Compiler Errors

The most common mistake is misspelling a parameter name. The compiler will report an error like CS1739: "The best overload for 'X' does not have a parameter named 'Y'." This error is caught at compile time, which is good, but it can be confusing if you are using a library and the parameter names are not what you expect.

Another mistake is placing a positional argument after a named argument. The compiler error for that is CS8323, as mentioned earlier. This is a hard error; there is no way to make the compiler accept it.

A subtler issue arises with overload resolution. When you use named arguments, the compiler uses the parameter names to select an overload. If two overloads have different parameter names, the named arguments may match only one of them, which can change which overload is chosen.

public void Draw(int x, int y) { } public void Draw(int left, int top) { } // This call matches the first overload because of the parameter names Draw(x: 10, y: 20); // This call matches the second overload Draw(left: 10, top: 20);

If you use positional arguments, the call is ambiguous and would not compile. Named arguments can resolve the ambiguity, but they also couple your code to the parameter names of the chosen overload. If the library later renames a parameter, your call breaks.

Readability and Maintainability Tradeoffs

Named arguments improve readability when a method has many parameters, especially when several are of the same type. They also make the intent of boolean or numeric arguments clear. For example, Configure(useTls: true) is more informative than Configure(true).

However, named arguments add verbosity. A call with five named arguments is longer than the same call with positional arguments. In a codebase where method calls are frequent, this can make the code harder to scan quickly. The tradeoff is between self-documenting code and conciseness.

There is also a maintainability concern: if a method's parameter names change, every call that uses named arguments must be updated. This is a form of coupling that does not exist with positional arguments. When you control the method signature, renaming a parameter is a breaking change for all callers that use named arguments. With positional arguments, renaming a parameter has no effect on callers.

This is not a reason to avoid named arguments entirely, but it is a reason to use them deliberately. For internal methods where the signature is stable, named arguments can be a good choice. For public APIs, consider whether parameter names are part of the contract you want to maintain.

The table below summarizes the key differences between positional and named arguments.

AspectPositionalNamed
Readability with many same-type paramsLowHigh
Risk of swapping argumentsHighLow
Coupling to parameter namesNoneStrong
Call verbosityLowHigher
Overload disambiguationLimitedCan resolve ambiguity

When to Avoid Named Arguments

Named arguments are not always the best choice. If a method has a single parameter, naming it adds no value. If a method has two parameters with clearly different types, positional arguments are usually fine. For example, MoveTo(10, 20) is clear enough if the method is documented as taking x and y coordinates.

Named arguments also become problematic when you need to pass arguments in a specific order for performance reasons. In C#, named arguments have no runtime cost; they are resolved at compile time. The generated IL is identical to a positional call. So there is no performance penalty to worry about.

The real cost is in code churn and readability. If a method call is short and the parameter names are obvious from the method name, named arguments add noise. Use them when the call would otherwise be ambiguous.

Named Arguments in Overload Resolution

As mentioned earlier, named arguments affect overload resolution. The compiler matches argument names to parameter names, so an overload is only considered if all named arguments correspond to parameters in that overload. This can be used to disambiguate overloads that would otherwise be ambiguous with positional arguments.

Consider two overloads that take the same number of parameters but with different names:

public void Update(int x, int y) { } public void Update(int left, int top) { }

A positional call Update(10, 20) is ambiguous because both overloads have the same parameter types. A named call Update(x: 10, y: 20) selects the first overload, and Update(left: 10, top: 20) selects the second. This is a real use case.

But this also means that if you later add a new overload with different parameter names, existing named calls may suddenly bind to a different overload. That can change behavior silently. This is a subtle maintainability risk.

When designing an API, if you expect callers to use named arguments, you should treat parameter names as part of the public contract. Renaming a parameter is a breaking change for those callers. If you do not want to commit to parameter names, document that callers should use positional arguments instead.

c# named arguments: Practical Usage and Code Examples | RYUSLOG DEV