Syntax and types

The project layout, top-level statements, the value and reference split, and nullability — the decisions that shape everything else.

A project, not just a file

C# compiles to an assembly that runs on the .NET runtime. The build is driven by a .csproj file, so the unit of work is a project rather than a single source file.

dotnet new console -o Hello
cd Hello
dotnet run                       # restore, build and execute
dotnet build -c Release          # compile only
dotnet add package Humanizer     # add a NuGet dependency
dotnet publish -c Release -r linux-x64 --self-contained
// Program.cs — top-level statements, C# 9 onwards; no Main method required
using System;
using System.Collections.Generic;
using System.Linq;

var scores = new List<int> { 90, 85, 77 };
var average = scores.Average();

Console.WriteLine($"Average: {average:F1} across {scores.Count} scores");

record Score(int Value, string Subject);      // an immutable data type
var best = new Score(90, "Maths");
Console.WriteLine(best);                      // Score { Value = 90, Subject = Maths }
CommandPurpose
dotnet new consoleScaffold a project
dotnet runRestore, build and run
dotnet testRun the test project
dotnet add package XAdd a NuGet dependency
dotnet publishProduce the deployable output

Value types and reference types

A value type holds its data inline and is copied on assignment. A reference type holds a reference to an object on the heap, so assignment copies the reference and both names end up looking at the same object.

KindExamplesCopy behaviour
Value typeint, double, bool, char, decimal, DateTime, struct, enumA copy of the data
Reference typestring, class, arrays, List<T>, delegatesA copy of the reference
record structA value-typed recordA copy of the data, with generated equality
stringAn immutable reference typeThe reference is copied, but no method can change the text
struct Point { public int X, Y; }

var a = new Point { X = 1, Y = 2 };
var b = a;               // a full copy
b.X = 99;
Console.WriteLine(a.X);  // 1 — a is untouched

var list1 = new List<int> { 1, 2 };
var list2 = list1;       // the same object
list2.Add(3);
Console.WriteLine(list1.Count);   // 3

// boxing turns a value into an object; unboxing casts it back and can throw
object boxed = 42;
int unboxed = (int)boxed;

// value types can be nullable, and ?? supplies a default
int? missing = null;
int actual = missing ?? 0;
💡
Enable nullable reference types (<Nullable>enable</Nullable> in the project file). The compiler then tracks which references may be null and warns where the bug would appear, instead of letting a NullReferenceException surface in production.

Text and formatting

var name = "Ada";
var greeting = $"Hello, {name}! The time is {DateTime.Now:HH:mm}.";

// a verbatim string treats its contents literally: a quote is written twice
var title = @"She said ""hello"" and left.";

// a raw string literal (C# 11) needs no doubling at all
var json = """
           { "name": "Ada", "active": true }
           """;

var upper = name.ToUpperInvariant();     // culture-independent
var same = string.Equals(name, "ada", StringComparison.OrdinalIgnoreCase);

var sb = new System.Text.StringBuilder();
for (var i = 0; i < 100; i++) sb.Append(i).Append(',');
var built = sb.ToString();               // one buffer, not a hundred strings
  • $ interpolates a string, @ makes it verbatim, and the two combine as $@.
  • Strings are immutable, so every + inside a loop allocates a new one; use StringBuilder for repeated concatenation.
  • Compare with an explicit StringComparison: Ordinal or OrdinalIgnoreCase for identifiers and protocol values, and a culture-aware overload only for text shown to people.
  • List<int> and other generic types are declared with angle brackets, and the compiler checks every use.

FAQ

Should I use a struct or a class?
A struct for a small value of a few fields that behaves like a number, such as money or a coordinate. A class for anything with identity, mutable state or inheritance. Default to a class until you have a reason not to.
Why does dividing two ints give 0?
Integer division truncates. Convert one operand first, for example (double)total / count, or declare the variable as decimal when the value is money.

Classes and interfaces LINQ and async/await

Last refreshed 2026-09-18.