C# Default Literal for Type Initialization

This article provides an overview of the default literal introduced in C# 7.1, explaining how it functions as a simplified mechanism for type initialization. You will learn how the compiler infers the appropriate default values for value types, reference types, and generics, the specific syntactic contexts where it can be applied, and the scenarios where explicit type specification remains necessary.


Understanding the default Literal

In earlier versions of C#, initializing a variable to its default state required the default(T) operator, explicitly passing the type name (for example, default(int) or default(CancellationToken)). Starting with C# 7.1, the language introduced the default literal, which allows you to omit the redundant type argument whenever the compiler can infer the target type from the context.

Under the hood, the default literal produces the exact same result as default(T):

  • Value Types: Numeric types evaluate to 0, bool evaluates to false, char evaluates to \0, and custom struct instances have all their fields recursively set to their respective default values.
  • Nullable Value Types (Nullable<T>): Evaluates to an instance where HasValue is false (effectively null).
  • Reference Types: Any class, interface, delegate, or array evaluates to null.
  • Generic Type Parameters: Dynamically resolves to either zero-initialized memory or null, depending on whether the concrete type parameter substituted at runtime is a value type or a reference type.

Common Usage Scenarios

The compiler allows the default literal anywhere a target type is already established:

1. Variable Declarations and Assignments

When declaring a variable with an explicit type, default eliminates the need to repeat the type name:

int counter = default;                     // Evaluates to 0
string message = default;                  // Evaluates to null
Guid identifier = default;                 // Evaluates to Guid.Empty
DateTime timestamp = default;              // Evaluates to DateTime.MinValue
KeyValuePair<int, string> pair = default; // Evaluates to [0, null]

2. Method Optional Parameters

The default literal is widely used to provide default values for optional arguments, particularly for structs like CancellationToken:

public async Task FetchDataAsync(string url, CancellationToken cancellationToken = default)
{
    // If not supplied by the caller, cancellationToken is initialized to CancellationToken.None
}

3. Return Statements

When a method return type is explicitly declared, default provides a clean exit without referencing the type:

public Point GetCoordinates()
{
    if (!TryCompute(out Point pt))
    {
        return default; // Returns new Point(0, 0)
    }
    return pt;
}

4. Pattern Matching and Conditionals

You can compare values against default or use it in ternary expressions where the target type is unambiguous:

// Checking if a struct equals its default state
if (timestamp == default)
{
    // Handle uninitialized date
}

// Ternary assignment
string status = isValid ? "Success" : default;

Limitations and Compiler Errors

Because the default literal relies entirely on target typing, it cannot be used in contexts where the compiler cannot deduce the expected type:

  • Implicitly Typed Variables (var): Writing var x = default; causes a compilation error (CS0815) because the compiler cannot infer what type x should be. In this situation, either use an explicit type (int x = default;) or use the older operator syntax (var x = default(int);).
  • Untyped Expressions: You cannot pass default directly into an overloaded method or an unconstrained generic argument if the compiler has no way to determine which overload or type to select.