C# Variables and Types — Value Types vs Reference Types, var, and Default Values
In this tutorial, you will learn about C# Variables and Types. We cover key concepts, practical examples, and best practices to help you master this topic.
C# variables are named storage locations with explicit types that determine how data is stored, accessed, and manipulated, distinguishing between value types stored on the stack and reference types stored on the heap.
What You'll Learn
You will understand the fundamental difference between value types and reference types in C#, learn how to declare variables using explicit types and the var keyword, discover default values for different types, master type conversion (implicit and explicit casting), and understand how memory allocation works for each type category.
Why It Matters
The distinction between value and reference types affects every aspect of C# programming: performance, memory usage, parameter passing, equality comparison, and object lifetime. Misunderstanding this distinction leads to bugs, memory leaks, and performance issues. This concept is foundational for all subsequent topics including generics, collections, and async programming.
Real-World Use
A high-frequency trading system in C# uses structs (value types) for order books to avoid heap allocation overhead. Web applications use reference types for complex business objects. Game developers using Unity choose structs for vector math and reference types for game entity behaviors. Understanding when to use each type directly impacts application performance.
Learning Path
graph LR
A["03: Hello World"] --> B["04: Variables & Types"]
B --> C["05: Built-in Types"]
C --> D["06: Operators"]
D --> E["07: Control Flow"]
style A fill:#4a90d9,stroke:#2c5f8a,color:#fff
style B fill:#4a90d9,stroke:#2c5f8a,color:#fff
style C fill:#4a90d9,stroke:#2c5f8a,color:#fff
style D fill:#4a90d9,stroke:#2c5f8a,color:#fff
style E fill:#4a90d9,stroke:#2c5f8a,color:#fff
Variable Declaration
// Explicit type declaration
int age = 25;
string name = "Alice";
double price = 19.99;
bool isActive = true;
// Multiple variables on one line
int x = 10, y = 20, z = 30;
// Type inference with var
var count = 42; // int
var greeting = "Hi"; // string
var rate = 3.14; // double
The var keyword tells the compiler to infer the type from the right-hand side. The inferred type is fixed at compile time. var is not the same as dynamic.
Value Types vs Reference Types
Value Types
Value types directly contain their data. Each variable has its own copy of the data. They are typically stored on the stack (or inline within other objects).
int a = 10;
int b = a; // Copy of value
b = 20; // a is still 10
Console.WriteLine($"a = {a}, b = {b}"); // a = 10, b = 20
Value types include:
- All numeric types (
int,double,decimal,float,long,byte,short) bool,charstruct,enum- Nullable value types (
int?,bool?)
Reference Types
Reference types store a reference (pointer) to the data on the heap. Multiple variables can reference the same object. Changes through one variable affect the other.
var list1 = new List<int> { 1, 2, 3 };
var list2 = list1; // Both reference the same object
list2.Add(4);
Console.WriteLine($"list1 count: {list1.Count}"); // 4
Console.WriteLine($"list2 count: {list2.Count}"); // 4
Reference types include:
string(immutable, but still a reference type)classinterfacedelegatearrayrecord class
Default Values
Uninitialized variables cannot be used locally, but fields and array elements have default values:
| Type | Default Value |
|---|---|
int, long, float, double, decimal |
0 |
bool |
false |
char |
'\0' |
enum |
0 (underlying value) |
struct |
All fields set to their defaults |
string |
null |
class, interface, array |
null |
int defaultInt = default; // 0
bool defaultBool = default; // false
string defaultString = default; // null
string? nullableString = default; // null
Type Conversion
Implicit Conversion
When the conversion is guaranteed to succeed without data loss:
int num = 100;
long bigNum = num; // int to long (safe)
double d = num; // int to double (safe)
float f = num; // int to float (may lose precision)
Explicit Conversion (Cast)
When data loss is possible or the conversion is not implicitly allowed:
double pi = 3.14159;
int truncated = (int)pi; // 3 (truncation!)
Console.WriteLine(truncated);
long big = 3000000000;
int overflow = (int)big; // Overflow! Runtime check (unchecked context)
Console.WriteLine(overflow); // May show unexpected value
Using Convert and Parse
string numberText = "42";
int parsed = int.Parse(numberText); // Throws if invalid
bool success = int.TryParse(numberText, out int result); // Safe parsing
double fromString = Convert.ToDouble("3.14");
int fromDouble = Convert.ToInt32(3.99); // 4 (rounding!)
Boxling and Unboxing
When a value type is converted to object (a reference type), it is boxed, allocating heap memory:
int value = 42;
object boxed = value; // Boxing: value type copied to heap
int unboxed = (int)boxed; // Unboxing: copy from heap back to stack
Boxing is expensive and creates garbage. Avoid it in performance-sensitive code. Generics eliminate most boxing scenarios.
Passing Parameters
By Value (Default)
void ModifyValue(int x)
{
x = 100; // Only modifies local copy
}
int num = 10;
ModifyValue(num);
Console.WriteLine(num); // 10 (unchanged)
For reference types, the reference is passed by value:
void ModifyList(List<int> items)
{
items.Add(100); // Modifies the original list
items = new List<int>(); // Does NOT affect original reference
}
var list = new List<int> { 1, 2, 3 };
ModifyList(list);
Console.WriteLine(list.Count); // 4
By Reference
void ModifyByRef(ref int x)
{
x = 100; // Modifies original variable
}
int num = 10;
ModifyByRef(ref num);
Console.WriteLine(num); // 100 (changed!)
Common Mistakes
Mistake 1: Assuming All Types Behave Like Value Types
Assigning a reference type variable to another does not copy the data. Both point to the same object. Use a constructor or MemberwiseClone for copying.
Mistake 2: Using var Excessively
While var is convenient, overusing it can harm readability, especially when the right-hand side does not make the type obvious. Use var when the type is clear from context.
Mistake 3: Forgetting That string Is a Reference Type
Strings behave like value types in many ways because they are immutable. But passing a string to a method and modifying it inside (via ref or StringBuilder) reveals its reference nature.
Mistake 4: Unnecessary Boxing
Passing value types to methods expecting object or IEnumerable causes boxing. Use generics to avoid this.
Mistake 5: Assuming default Is Always 0
For reference types, default is null. Calling methods on a default reference type variable throws NullReferenceException.
Mistake 6: Confusing out and ref Parameters
Both pass by reference, but out parameters do not need to be initialized before calling, while ref parameters do.
Practice Questions
- Explain the difference between value types and reference types in memory allocation.
- What is the default value of a
boolvariable declared as a field? - Why does
var x = null;not compile? How would you fix it? - What is boxing and why should you avoid it?
- Write a method that swaps two integers using
refparameters.
Challenge
Write a program that demonstrates the difference between value type and reference type behavior. Create a struct and a class with the same fields. Show how modifying a copy affects the original in each case.
FAQ
Mini Project
Create a program that demonstrates value and reference type differences:
struct Point
{
public int X;
public int Y;
}
class Rectangle
{
public int Width;
public int Height;
}
Console.WriteLine("=== Value Type (struct) Demo ===");
Point p1 = new Point { X = 10, Y = 20 };
Point p2 = p1; // Copy
p2.X = 99;
Console.WriteLine($"p1: ({p1.X}, {p1.Y})"); // (10, 20) - unchanged
Console.WriteLine($"p2: ({p2.X}, {p2.Y})"); // (99, 20) - changed
Console.WriteLine("\n=== Reference Type (class) Demo ===");
Rectangle r1 = new Rectangle { Width = 10, Height = 20 };
Rectangle r2 = r1; // Same object
r2.Width = 99;
Console.WriteLine($"r1: {r1.Width}x{r1.Height}"); // 99x20 - changed!
Console.WriteLine($"r2: {r2.Width}x{r2.Height}"); // 99x20 - changed
Expected output:
=== Value Type (struct) Demo ===
p1: (10, 20)
p2: (99, 20)
=== Reference Type (class) Demo ===
r1: 99x20
r2: 99x20
What's Next
You now understand the core type system in C#. The next lesson covers each built-in type in detail: integers, floating-point numbers, decimal, bool, char, string, object, and dynamic.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro