C# 4.0 added two small features that show up everywhere in modern code: named arguments and optional parameters. Together they make method calls self-documenting and help library authors avoid overload explosions for straightforward customization points.
They work especially well for logging, formatting, configuration, and interop-heavy APIs where the method signature is stable but most callers use only a couple of non-default settings.
Why it matters
Named arguments improve call-site clarity when positional arguments start to blur together, especially with multiple bool, string, or numeric values. Optional parameters let you expose sensible defaults once instead of maintaining a ladder of near-duplicate overloads.
That combination is a good fit for utility methods, client SDKs, and formatting helpers where most callers follow the common path but a few need to opt into extra behavior.
Cautions
Optional parameter defaults are baked in at the call site at compile time. If you ship a library and later change a default value, older compiled callers keep using the original value until they are rebuilt.
Also avoid turning every knob into an optional parameter. When a method grows too many options, a dedicated options object or builder usually scales better and reads more clearly.
Make multi-flag calls readable
Named arguments remove guesswork when a method has several parameters of the same shape or a few optional switches.
Valid since C# 4.0
Without var
using System;
public sealed class NotificationService
{
public void Send(string recipient, string subject, string body, bool highPriority, bool saveCopy)
{
Console.WriteLine("To: " + recipient);
Console.WriteLine("Subject: " + subject);
Console.WriteLine(
string.Format(
"Priority: {0} | Save copy: {1}",
highPriority ? "high" : "normal",
saveCopy));
Console.WriteLine(body);
}
}
public static class Program
{
public static void Main()
{
var service = new NotificationService();
service.Send(
"team@contoso.com",
"Deployment complete",
"The new release is live.",
true,
false);
}
}
With var
using System;
public sealed class NotificationService
{
public void Send(
string recipient,
string subject,
string body,
bool highPriority = false,
bool saveCopy = true)
{
Console.WriteLine("To: " + recipient);
Console.WriteLine("Subject: " + subject);
Console.WriteLine(
string.Format(
"Priority: {0} | Save copy: {1}",
highPriority ? "high" : "normal",
saveCopy));
Console.WriteLine(body);
}
}
public static class Program
{
public static void Main()
{
var service = new NotificationService();
service.Send(
recipient: "team@contoso.com",
subject: "Deployment complete",
body: "The new release is live.",
highPriority: true,
saveCopy: false);
}
}
Trim overloads with optional defaults
Optional parameters let one method cover the common path while still allowing targeted customization.
Valid since C# 4.0
using System;
public static class InvoiceFormatter
{
public static string FormatInvoice(
string customerName,
decimal amount,
string currency = "USD",
bool includeDueDate = false)
{
var message = string.Format(
"Invoice for {0}: {1:0.00} {2}",
customerName,
amount,
currency);
return includeDueDate
? message + " (due in 14 days)"
: message;
}
}
public static class Program
{
public static void Main()
{
Console.WriteLine(InvoiceFormatter.FormatInvoice("A. Datum", 1200m));
Console.WriteLine(
InvoiceFormatter.FormatInvoice(
"A. Datum",
1200m,
currency: "EUR",
includeDueDate: true));
}
}