C# Variable Initialization: Rules and Defaults
Learn how C# variable initialization works: default field values, definite assignment for locals, var inference, ref/out rules, and common errors.
C# variable initialization is governed by definite assignment rules enforced at compile time. A local variable must be definitely assigned before its value is read, or the compiler reports an error. This differs from fields, which receive a default value automatically. Understanding these rules is essential for writing predictable code.
Default Values for Fields and Array Elements
Fields declared in a class are automatically initialized to their default value before the constructor body runs. The default value is the result of default(T) for the field's type: zero for numeric types, false for bool, and null for reference types. For a struct, default(T) has all fields at their default values, but an explicit struct constructor must assign every field before returning.
class Example { private int _number; // default 0 private string _text; // default null private bool _flag; // default false }
Array elements follow the same rule. When you create an array with new int[5], each element is initialized to 0. For a reference type array, each element is null until you assign it.
Local variables do not get a default value. The compiler requires that every local variable be explicitly assigned before it is used. This is a deliberate design decision: it prevents reading memory that may contain stale data.
int count; Console.WriteLine(count); // CS0165: Use of unassigned local variable 'count'
Declaring and Initializing in One Step
The most straightforward form of C# variable initialization is to declare and assign in a single statement. This satisfies the compiler's definite assignment rule and makes the initial state explicit.
int count = 10; string name = "Ada"; var status = "active";
The var keyword does not introduce dynamic typing. It instructs the compiler to infer the variable's type from the initializer expression. status is a string, and count is an int. Using var is a readability choice, not a performance decision.
The default Literal and default(T)
C# has a default literal that produces the default value of a type. It was introduced in C# 7.1 and is especially useful when you want a type-neutral default value without specifying the type explicitly.
int number = default; // 0 string text = default; // null bool flag = default; // false
Before C# 7.1, the explicit form was default(T). The literal form is equivalent and works in any context where the target type is known. For nullable value types, default is null:
int? maybeNumber = default; // null
In generic methods, default(T) is the explicit way to obtain a default value when T is unconstrained. Because the target type is known from the context, default also works, and since C# 7.1 a method can return default; in this situation.
public T GetDefault<T>() { return default(T); }
Initializing out and ref Arguments
Method parameters with the out modifier do not require the caller to initialize the variable before the call. The method must assign a value to the parameter before returning. This is a common pattern for methods that return multiple results.
bool TryParse(string input, out int result) { if (int.TryParse(input, out result)) { return true; } result = 0; return false; }
In contrast, a ref parameter must be initialized before the call because the method may read it without writing first. The compiler enforces this distinction.
int value = 5; Modify(ref value);
Common Compiler Errors and Their Causes
The most frequent initialization-related error is CS0165, "Use of unassigned local variable." It occurs when you attempt to read a local variable that the compiler cannot prove has been assigned. This often happens in conditional branches:
bool condition = false; int result; if (condition) { result = 1; } Console.WriteLine(result); // CS0165: use of unassigned local variable 'result'
The compiler does not track runtime conditions; it requires that every possible execution path assigns the variable before use. The fix is to initialize the variable at declaration or to provide an else branch.
Another related error is CS0170, "Use of possibly unassigned field," which can appear when you access a struct field that has not been assigned in all constructors. Struct constructors must assign every field before returning.
Choosing Between Explicit Types and var
var is not a replacement for explicit types; it is a tool for reducing visual clutter when the type is obvious from the initializer. For example, var dictionary = new Dictionary<string, List<int>>(); is clearer than repeating the full type name.
However, using var with a method call can obscure the actual type if the method's return type is not obvious. In that case, an explicit type improves readability and prevents accidental type mismatches. The choice is about maintainability, not runtime behavior.
Object Initializers and Collection Initializers
Object initializers let you set property values at construction time without writing multiple assignment statements. They are syntactic sugar that the compiler converts into property assignments after the constructor runs.
var person = new Person { Name = "Ada", Age = 36 };
This is equivalent to:
var person = new Person(); person.Name = "Ada"; person.Age = 36;
Collection initializers use a similar shorthand. The compiler expands the initial elements into calls to an applicable Add method on the constructed collection:
var numbers = new List<int> { 1, 2, 3 };
Object and collection initializers are useful when you want to create an object or collection in a fully initialized state. They do not change the underlying initialization rules; the constructor still runs first.
Runtime and Allocation Considerations
Initialization for a value type is a copy operation. For a local int, int x = 10; stores the value in the local slot or register. If the field is part of a reference type object, the field storage already exists in the object's heap allocation; assigning the value does not create a separate heap object. Initializing a reference type variable with new allocates an object on the heap and assigns the reference to the variable. Default values are set by the runtime as part of object construction or array allocation, which is why uninitialized fields are safe to read.
The default literal and default(T) produce the default value that the runtime uses for uninitialized fields and array elements. They are useful when you need to explicitly reset a variable or when writing generic code that must handle any type.