C# Syntax and Variables

Value vs reference types, var, nullable reference types, string interpolation and control flow.

Value types vs reference types

This is the single most important distinction in C#'s type system:

  • Value types (structs, including all built-in numeric types, bool, char) hold their data directly. Assigning one to another copies the value.
  • Reference types (class, string, arrays, List<T>, delegates) hold a reference to data elsewhere on the heap. Assigning one copies the reference, not the underlying data — both variables then point at the same object.
C#
// Value type — each variable has its own independent copy
int a = 10;
int b = a;
b = 20;
Console.WriteLine(a);   // 10 — unaffected by changing b

// Reference type — both variables point at the same object
var listA = new List<int> { 1, 2, 3 };
var listB = listA;
listB.Add(4);
Console.WriteLine(listA.Count);   // 4 — listA sees the change made through listB

Conceptually, value types typically live on the stack (fast allocation/deallocation, no garbage collection pressure) while reference types live on the heap (managed by the garbage collector) — though this is a simplification; the precise rules involve boxing and closures.

var and type inference

var infers the variable's type from the right-hand side at compile time — C# remains fully statically typed either way; var is purely local syntax sugar, not dynamic typing:

C#
var age = 30;              // inferred as int
var name = "Ada";          // inferred as string
var prices = new List<double> { 9.99, 19.99 };   // inferred as List<double>

// age = "thirty";   // Error: cannot convert string to int — the type is fixed once inferred

Nullable reference types

By default in modern C# projects (enabled via <Nullable>enable</Nullable> in the .csproj, the default for new projects since .NET 6), the compiler distinguishes types that can be null from those that can't, using ?:

C#
string name = "Ada";        // cannot be null — the compiler warns if you try
string? nickname = null;    // explicitly nullable — allowed

// Console.WriteLine(name.Length);       // fine, name is guaranteed non-null
Console.WriteLine(nickname?.Length);     // null-conditional operator — safely returns null instead of throwing
Console.WriteLine(nickname ?? "no nickname");  // ?? provides a fallback when the left side is null

This is a compile-time analysis feature, not a runtime guarantee — it produces warnings (not hard errors) when you might be dereferencing a null, catching a huge share of NullReferenceException bugs before the code ever ships.

String interpolation

C#
string name = "Ada";
int age = 30;

string message = $"{name} is {age} years old.";
string upper = $"{name.ToUpper()} is {age * 12} months old.";  // expressions work too

Console.WriteLine(message);   // Ada is 30 years old.

Interpolated strings ($"...") are the idiomatic replacement for manual string.Format or + concatenation.

Control flow

C#
int score = 85;

// if / else if / else
if (score >= 90)
{
    Console.WriteLine("A");
}
else if (score >= 80)
{
    Console.WriteLine("B");
}
else
{
    Console.WriteLine("C or below");
}

// switch expression (C# 8+) — concise, returns a value directly
string grade = score switch
{
    >= 90 => "A",
    >= 80 => "B",
    >= 70 => "C",
    _ => "F",
};

// loops
for (int i = 0; i < 3; i++)
{
    Console.WriteLine(i);
}

foreach (var item in new[] { "a", "b", "c" })
{
    Console.WriteLine(item);
}

int count = 0;
while (count < 3)
{
    count++;
}

The switch expression (using => and no break statements) is the modern, concise form for producing a value; the classic switch statement with case/break still exists and is common for handling side effects branch-by-branch.

Common mistakes

  • Assuming assigning a List<T> or other reference type creates an independent copy — it only copies the reference; use .ToList() or a manual copy loop for an actual independent copy.
  • Ignoring nullable reference type warnings instead of handling them — they exist specifically to catch NullReferenceException bugs before runtime.
  • Confusing the switch expression (=>, returns a value) with the older switch statement (case/break, executes statements) — they look similar but serve different purposes.

Interview questions

Q: What's the practical difference between a value type and a reference type? Assigning a value type copies its actual data, so two variables holding "the same" value type are completely independent afterward. Assigning a reference type copies only the reference (pointer) to the underlying object, so two variables can end up pointing at — and both able to mutate — the exact same object in memory.

Q: What problem do nullable reference types solve? They let the compiler statically flag code paths where you might dereference a null reference type — the single most common cause of NullReferenceException at runtime in older C#. By explicitly marking which references are allowed to be null (string?) versus guaranteed non-null (string), the compiler can warn you before the code ever runs, rather than the exception surfacing in production.