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.
// 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:
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 ?:
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
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
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
NullReferenceExceptionbugs before runtime. - Confusing the
switchexpression (=>, returns a value) with the olderswitchstatement (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.