Skip to content

C# Variables and Types — Value Types vs Reference Types, var, and Default Values

DodaTech Updated 2026-06-28 7 min read

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, char
  • struct, 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)
  • class
  • interface
  • delegate
  • array
  • record 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

  1. Explain the difference between value types and reference types in memory allocation.
  2. What is the default value of a bool variable declared as a field?
  3. Why does var x = null; not compile? How would you fix it?
  4. What is boxing and why should you avoid it?
  5. Write a method that swaps two integers using ref parameters.

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

When should I use `var` vs explicit types?

Use var when the type is obvious from the right-hand side (e.g., var dict = new Dictionary<string, int>()). Use explicit types when the type is not immediately clear or when it improves readability.

What is the difference between `int` and `Int32`?

There is no difference. int is an alias for System.Int32. int is preferred in C# code for readability.

Can value types be null?

Not by default. Use nullable value types with ? suffix: int? nullableInt = null;. This wraps the value type in Nullable<T>.

How does C# handle overflow in integer arithmetic?

By default, C# uses unchecked context where overflow silently wraps. Use checked block or project setting to enable overflow checking.

What is the difference between `object` and `dynamic`?

object is a static type resolved at compile time. dynamic bypasses compile-time type checking and resolves at runtime. Use dynamic for COM interop or dynamic languages.

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