Default Values of Generic Type Parameters in C
In C#, generic type parameters make it impossible to know in advance
whether a type argument will be a value type or a reference type. This
article provides an overview of how the default(T)
expression and the modern default literal bridge this gap,
ensuring type safety by producing the correct zero-initialized state for
any given generic parameter T.
The Need for default(T)
When working with a generic parameter T, you cannot
simply assign null because T might be a value
type such as int, bool, or a custom
struct. Value types cannot be null unless
explicitly declared as nullable (T?). Similarly, you cannot
assign 0 because T might be a reference type
like string or a custom class.
The default(T) operator solves this problem at compile
time by dynamically substituting the appropriate default value depending
on what T resolves to.
How
default(T) Evaluates Different Types
The value produced by default(T) depends entirely on the
underlying classification of T:
- Reference Types: If
Tis a class, interface, delegate, or record class,default(T)evaluates tonull. - Numeric Value Types: If
Tis a primitive numeric type (int,float,double,decimal,byte, etc.),default(T)evaluates to zero (0,0.0, etc.). - Boolean: If
Tisbool,default(T)evaluates tofalse. - Enumerations: If
Tis anenum,default(T)returns the underlying integral zero cast to the enum type, which corresponds to whichever enum member is assigned the value0(or simply0if no member has that value). - Structs (Custom Value Types): If
Tis astruct,default(T)allocates a struct instance where every internal field is initialized to its own default value (binary zero). - Nullable Value Types (
Nullable<T>orT?for structs):default(T?)evaluates to a nullable instance whereHasValueisfalse.
Syntax Evolution: The
default Literal
Starting with C# 7.1, the language introduced the target-typed
default literal. Where the compiler can infer the type, you
can omit (T):
public class Repository<T>
{
// C# 7.0 and earlier
public T GetOldDefault()
{
return default(T);
}
// C# 7.1 and later
public T GetModernDefault()
{
T item = default;
return item;
}
}Both syntaxes produce identical Intermediate Language (IL) instructions and runtime behavior.
Checking for Default Values
Checking whether a variable of type T holds its default
value requires caution. You cannot always use the ==
operator on an unconstrained type parameter T, because the
compiler cannot guarantee that == is defined for
T.
To reliably compare a generic variable against its default value, use
EqualityComparer<T>.Default:
public bool IsDefault<T>(T value)
{
return EqualityComparer<T>.Default.Equals(value, default);
}This pattern safely handles both reference types (checking for
null) and value types (checking for zero or all-zero bit
patterns) without boxing overhead for built-in types.