Skip to main content

C#: Nullable params vs default params

Updated 3 min readMarkdown
C#: Nullable params vs default params

While both nullable type parameters and default parameters are used to make method arguments optional, they solve entirely different problems.

Nullable types define what data a variable can hold (allowing it to be null), whereas default parameters define what value a method will use if the caller doesn't provide one.

The Code Comparison

1. Using a Nullable Type as parameter

When you use a nullable type as a parameter, the caller must pass a value (or explicitly pass null). Inside the method, you must check if a value exists before using it.

// The caller is forced to explicitly pass a value or null
void PrintScore(int? score)
{
    if (score.HasValue)
    {
        Console.WriteLine($"Score is: {score.Value}");
    }
    else
    {
        Console.WriteLine("No score provided.");
    }
}

// How you call it:
PrintScore(100);  // Output: Score is: 100
PrintScore(null); // Output: No score provided.
// PrintScore();  // ❌ Compile Error: No argument given that corresponds to the required parameter.

2. Using a Default Parameter

When you use a default parameter, you assign a fallback value using the = operator in the method signature. The caller can completely omit the argument when calling the method.

// The caller can omit this argument entirely. If they do, 'score' defaults to 0.
void PrintScore(int score = 0)
{
    // No null check needed; 'score' is a normal, safe integer.
    Console.WriteLine($"Score is: {score}");
}

// How you call it:
PrintScore(100); // Output: Score is: 100
PrintScore();    // Output: Score is: 0 (Omitted, used the default)

Direct Comparison

Feature Nullable Type Parameters (T?) Default Parameters (T param = value)
Primary Purpose Allows a variable to represent a missing, unassigned, or undefined state (null). Allows a method argument to be omitted by the caller, falling back to a pre-defined value.
Call Syntax The caller must explicitly pass a value or null. The caller can skip the argument entirely.
Inside the Method You must safely check for null (HasValue or != null) before using it. The variable is immediately ready to use with a guaranteed, non-null value.
Value Restriction Can only represent the valid range of the type plus null. Can be assigned any compile-time constant default value (e.g., 0, "Guest", false).

Combining Both Parameters

There are times when you want to combine both patterns. For example, in C# this won't work:

void ProcessTransaction(decimal amount, DateTime transactionDate = DateTime.UtcNow) // ❌ THIS WILL NOT COMPILE. Error: The expression being assigned to 'transactionDate' must be a constant
{   
    Console.WriteLine($"Processing ${amount} on {transactionDate}");
}

DateTime.UtcNow is a runtime property, not a compile-time constant.

In C#, default parameters are baked directly into the calling application's code at compile time. Because DateTime.UtcNow changes every millisecond, the compiler cannot determine its value when building your application.

The only default values allowed for a standard DateTime parameter are compile-time constants, such as default (which evaluates to 1/1/0001 12:00:00 AM).

The Working Idiomatic Solution

To achieve exactly what you want (making the date optional and defaulting to the exact moment the method runs), you must combine the nullable type with a null constant default parameter:

void ProcessTransaction(decimal amount, DateTime? transactionDate = null)
{
    // 1. 'null' is a valid compile-time constant for a nullable type.
    // 2. The ?? operator can now be used because transactionDate CAN be null.
    DateTime finalDate = transactionDate ?? DateTime.UtcNow;
    
    Console.WriteLine($"Processing ${amount} on {finalDate}");
}

Thanks for reading and I hope this was helpful.

About the author

  • Software engineer and blogger

No comments yet. Be the first to comment.