C# Optional Parameters

Optional parameters let you define method parameters with default values. Callers can skip those parameters when calling the method, and the default value is used automatically. This reduces the need for multiple overloaded versions of the same method.

The Problem Optional Parameters Solve

Without optional parameters, giving users flexibility means creating many overloaded versions of a method:

// Without optional parameters — 3 overloads needed:
void Greet(string name)                          { ... }
void Greet(string name, string language)         { ... }
void Greet(string name, string language, bool loud) { ... }

With optional parameters, one method handles all cases:

// With optional parameters — 1 method handles all:
void Greet(string name, string language = "English", bool loud = false)
{
    ...
}

Syntax

Assign a default value to a parameter using =. Optional parameters must come after required parameters.

void PrintInfo(string name, int age = 0, string city = "Unknown")
{
    Console.WriteLine(name + ", Age: " + age + ", City: " + city);
}

Calling With and Without Optional Arguments

PrintInfo("Alice");                      // Alice, Age: 0, City: Unknown
PrintInfo("Bob", 25);                    // Bob, Age: 25, City: Unknown
PrintInfo("Carol", 30, "London");        // Carol, Age: 30, City: London

Optional Parameter Fill-In Diagram

┌────────────────────────────────────────────────────────────────┐
│  void PrintInfo(string name, int age = 0, string city = "?")   │
├────────────────────────────────────────────────────────────────┤
│                                                                │
│  PrintInfo("Alice")                                            │
│       name = "Alice"    ← provided                             │
│       age  = 0          ← default used                         │
│       city = "Unknown"  ← default used                         │
│                                                                │
│  PrintInfo("Bob", 25)                                          │
│       name = "Bob"      ← provided                             │
│       age  = 25         ← provided                             │
│       city = "Unknown"  ← default used                         │
│                                                                │
│  PrintInfo("Carol", 30, "London")                              │
│       name = "Carol"    ← provided                             │
│       age  = 30         ← provided                             │
│       city = "London"   ← provided                             │
│                                                                │
└────────────────────────────────────────────────────────────────┘

Rules for Optional Parameters

┌────────────────────────────────────────────────────────────┐
│  RULES                                                     │
├────────────────────────────────────────────────────────────┤
│  ✅ Optional params must come AFTER required params        │
│  ✅ Default values must be compile-time constants          │
│  ✅ You can have multiple optional params                  │
│  ❌ Optional params cannot come BEFORE required params     │
└────────────────────────────────────────────────────────────┘

// VALID:
void Send(string message, bool urgent = false, int retries = 3) { }

// INVALID:
void Send(bool urgent = false, string message, int retries = 3) { }
// ❌ required param 'message' comes after optional

Named Arguments

Named arguments let you specify which parameter you are passing by using its name. This lets you skip optional parameters in the middle of a list.

void Configure(string host, int port = 80, bool ssl = false, int timeout = 30)
{
    Console.WriteLine($"Host: {host}, Port: {port}, SSL: {ssl}, Timeout: {timeout}");
}

// Skip 'port' and 'ssl', only set 'host' and 'timeout':
Configure("example.com", timeout: 60);
// Host: example.com, Port: 80, SSL: False, Timeout: 60

// Set by name in any order:
Configure(ssl: true, host: "secure.com", port: 443);
// Host: secure.com, Port: 443, SSL: True, Timeout: 30

Named Arguments Diagram

┌────────────────────────────────────────────────────────────┐
│  Configure("example.com", timeout: 60)                     │
│                                                            │
│  host    = "example.com"  ← positional (1st param)         │
│  port    = 80             ← default (skipped)              │
│  ssl     = false          ← default (skipped)              │
│  timeout = 60             ← named argument                 │
└────────────────────────────────────────────────────────────┘

Real-World Example: Email Sender

using System;

void SendEmail(
    string to,
    string subject,
    string body = "",
    string cc = "",
    bool isHtml = false,
    int priority = 1)
{
    Console.WriteLine("To: " + to);
    Console.WriteLine("Subject: " + subject);
    Console.WriteLine("Body: " + (body == "" ? "(empty)" : body));
    Console.WriteLine("CC: " + (cc == "" ? "(none)" : cc));
    Console.WriteLine("HTML: " + isHtml);
    Console.WriteLine("Priority: " + priority);
    Console.WriteLine("---");
}

// Basic email:
SendEmail("alice@example.com", "Hello");

// Email with HTML and high priority:
SendEmail("bob@example.com", "Report", isHtml: true, priority: 5);

// Full email:
SendEmail("carol@example.com", "Meeting", "See you at 3pm",
          cc: "dave@example.com", isHtml: false, priority: 2);

Optional Parameters vs Overloading

┌────────────────────────────┬────────────────────────────────┐
│ Method Overloading         │ Optional Parameters            │
├────────────────────────────┼────────────────────────────────┤
│ Multiple method signatures │ One method with defaults       │
├────────────────────────────┼────────────────────────────────┤
│ Each version has full code │ Defaults fill in unused args   │
├────────────────────────────┼────────────────────────────────┤
│ More code, more to maintain│ Less code, easier to maintain  │
├────────────────────────────┼────────────────────────────────┤
│ More flexible (different   │ Limited to same parameter order│
│ parameter types possible)  │                                │
├────────────────────────────┼────────────────────────────────┤
│ Use when logic differs     │ Use when logic stays the same  │
│ between versions           │ and only values differ         │
└────────────────────────────┴────────────────────────────────┘

Valid Default Value Types

// These are valid default values (compile-time constants):
void Example(
    int x = 10,              // integer literal ✅
    double y = 3.14,         // decimal literal ✅
    string s = "hello",      // string literal ✅
    bool flag = true,        // bool literal ✅
    char c = 'A',            // char literal ✅
    object obj = null        // null ✅
) { }

// These are INVALID:
void Bad(
    int size = GetSize(),    // ❌ method call not allowed
    DateTime now = DateTime.Now  // ❌ runtime value not allowed
) { }

Quick Summary

┌──────────────────────────────────────────────────────────────┐
│  Optional parameters: assign default values in the signature │
│  Named arguments: use param name when calling (name: value)  │
│                                                              │
│  Rules:                                                      │
│  • Optional params always go at the end                      │
│  • Default must be a compile-time constant                   │
│  • Named args let you skip middle optional params            │
│  • Combine named + positional: positional must come first    │
└──────────────────────────────────────────────────────────────┘

Optional parameters and named arguments work as a team. Together they make method calls cleaner, reduce boilerplate code, and keep your method signatures flexible without the complexity of creating multiple overloads for every variation you need.

Leave a Comment

Your email address will not be published. Required fields are marked *