C# Course
C# Crash Course
The Essential Reference Guide — Modern C# 12 & .NET
1. Hello World & Program Structure
Modern C# (9+) supports top-level statements — you no longer need a class or Main method for simple programs. The traditional structure still works and is still used in larger projects.
// Modern C# — top-level statements (C# 9+)
Console.WriteLine("Hello, World!");
Console.WriteLine("Welcome to C#!");// Traditional structure — still required in larger projects
namespace MyApp;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello, World!");
}
}C# vs Java
Coming from Java? C# is very similar — both are compiled, statically typed, and OOP-first. Key differences: C# has var, LINQ, async/await as first-class features, and uses Console.WriteLine instead of System.out.println.
2. Variables & Data Types
C# is statically typed. Use var for local type inference — the compiler still enforces the type.
// Explicit types
int age = 25;
double gpa = 3.85;
bool active = true;
char grade = 'A';
string name = "Ayman";
decimal price = 9.99m; // m suffix for decimal (financial)
// var — inferred by compiler
var score = 95; // int
var message = "Hello"; // string
// Nullable types — can hold null
int? nullableInt = null;
string? nullableString = null;
// Constants
const double PI = 3.14159;
// String interpolation
Console.WriteLine($"Name: {name}, Score: {score}");Tip
Use string? (nullable reference type) to make your intent explicit. Enable <Nullable>enable</Nullable> in your .csproj for compiler warnings on null misuse.
3. Operators
// Arithmetic
Console.WriteLine(10 + 3); // 13
Console.WriteLine(10 / 3); // 3 (integer division)
Console.WriteLine(10 % 3); // 1 (remainder)
Console.WriteLine(2 * 3.0); // 6.0
// Null-coalescing — return right side if left is null
string? input = null;
string result = input ?? "default"; // "default"
// Null-conditional — safe member access
int? len = input?.Length; // null (no crash)
// Null-coalescing assignment
input ??= "fallback"; // sets only if null
// Logical
Console.WriteLine(true && false); // false
Console.WriteLine(true || false); // true
Console.WriteLine(!true); // false4. Control Flow
int score = 85;
// if / else if / else
if (score >= 90) Console.WriteLine("A");
else if (score >= 80) Console.WriteLine("B"); // ← runs
else Console.WriteLine("F");
// Ternary
string pass = (score >= 60) ? "Pass" : "Fail";
// Switch expression (C# 8+) — elegant and exhaustive
string letter = score switch
{
>= 90 => "A",
>= 80 and < 90 => "B",
>= 70 => "C",
_ => "F" // _ is the discard / default
};
// Switch on type (pattern matching)
object obj = 42;
string desc = obj switch
{
int n => $"Integer: {n}",
string s => $"String: {s}",
_ => "Unknown"
};Modern C#
The switch expression (not statement) is the preferred modern form — it's an expression (returns a value), requires exhaustive cases, and eliminates fall-through bugs entirely.
5. Loops
// for
for (int i = 0; i < 5; i++)
Console.WriteLine(i);
// while
int count = 0;
while (count < 3) { Console.WriteLine(count); count++; }
// foreach — the most common loop in C#
string[] names = { "Ayman", "Sara", "Ali" };
foreach (var n in names)
Console.WriteLine(n);
// do-while
do { Console.WriteLine("runs at least once"); } while (false);
// break / continue
for (int i = 0; i < 10; i++) {
if (i == 3) continue; // skip 3
if (i == 7) break; // stop at 7
Console.Write(i + " ");
}6. Arrays & Collections
// Array — fixed size
int[] scores = { 90, 85, 78 };
Console.WriteLine(scores[0]); // 90
Console.WriteLine(scores.Length); // 3
// List<T> — dynamic, most common collection
var names = new List<string> { "Ayman", "Sara" };
names.Add("Ali");
names.Remove("Sara");
Console.WriteLine(names.Count); // 2
Console.WriteLine(names.Contains("Ali")); // True
// Dictionary<K,V> — key-value pairs
var grades = new Dictionary<string, int>
{
["Ayman"] = 95,
["Sara"] = 88
};
grades["Ali"] = 79;
Console.WriteLine(grades["Ayman"]); // 95
// HashSet<T> — unique values, no duplicates
var tags = new HashSet<string> { "cs", "python", "cs" };
Console.WriteLine(tags.Count); // 2 — duplicate removed7. Methods
// Standard method
static int Add(int a, int b) => a + b; // expression body
// Optional parameters
static void Greet(string name, string prefix = "Hello")
=> Console.WriteLine($"{prefix}, {name}!");
Greet("Ayman"); // Hello, Ayman!
Greet("Sara", "Welcome"); // Welcome, Sara!
// Named arguments
Greet(prefix: "Hi", name: "Ali");
// params — variable number of arguments
static int Sum(params int[] nums)
=> nums.Sum(); // using LINQ Sum()
Console.WriteLine(Sum(1, 2, 3, 4)); // 10
// Local function — defined inside another method
static int Factorial(int n)
{
return Inner(n);
int Inner(int x) => x <= 1 ? 1 : x * Inner(x - 1);
}8. OOP & Classes
public class Student
{
// Auto-implemented properties — cleaner than Java getters/setters
public string Name { get; set; }
public int Grade { get; init; } // init = set once at construction
// Constructor
public Student(string name, int grade)
=> (Name, Grade) = (name, grade); // tuple assignment
// Computed property
public string LetterGrade => Grade switch
{
>= 90 => "A", >= 80 => "B",
>= 70 => "C", _ => "F"
};
// Override ToString
public override string ToString()
=> $"{Name} → {LetterGrade} ({Grade})";
}
// Usage
var s = new Student("Ayman", 92);
Console.WriteLine(s); // Ayman → A (92)
// Object initializer syntax
var s2 = new Student("Sara", 85) { Name = "Sara A." };Records (C# 9+)
For immutable data objects, use record instead of class. Records auto-generate equality, ToString, and with expressions for non-destructive mutation. See Section 12.
9. Interfaces & Generics
// Interface — defines a contract
public interface IPrintable
{
void Print();
string Summary { get; } // interfaces can have properties
}
// Implement the interface
public class Report : IPrintable
{
public string Title { get; set; } = "";
public string Summary => $"Report: {Title}";
public void Print() => Console.WriteLine(Summary);
}
// Generic class — T is a type parameter
public class Box<T>
{
public T Value { get; }
public Box(T value) => Value = value;
public override string ToString() => $"Box[{Value}]";
}
var sBox = new Box<string>("Hello");
var iBox = new Box<int>(42);
Console.WriteLine(sBox); // Box[Hello]
Console.WriteLine(iBox); // Box[42]
// Generic method with constraint
static T Max<T>(T a, T b) where T : IComparable<T>
=> a.CompareTo(b) >= 0 ? a : b;
Console.WriteLine(Max(10, 20)); // 20
Console.WriteLine(Max("apple", "mango")); // mango10. LINQ
LINQ (Language Integrated Query) lets you query any collection using readable, SQL-like syntax directly in C#. It works on arrays, lists, databases, XML, and more.
using System.Linq;
int[] nums = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// Method syntax (most common in production)
var evens = nums.Where(n => n % 2 == 0); // [2,4,6,8,10]
var squares = nums.Select(n => n * n); // [1,4,9,16...]
var top3 = nums.OrderByDescending(n => n)
.Take(3); // [10,9,8]
Console.WriteLine(nums.Sum()); // 55
Console.WriteLine(nums.Average()); // 5.5
Console.WriteLine(nums.Max()); // 10
Console.WriteLine(nums.Any(n => n > 9)); // True
Console.WriteLine(nums.All(n => n > 0)); // True
Console.WriteLine(nums.Count(n => n % 2 == 0)); // 5
// Query syntax (closer to SQL)
var query =
from n in nums
where n % 2 == 0
orderby n descending
select n * n; // [100, 64, 36, 16, 4]
// LINQ on objects
var students = new[] {
new { Name = "Ayman", Grade = 95 },
new { Name = "Sara", Grade = 88 },
new { Name = "Ali", Grade = 72 }
};
var top = students
.Where(s => s.Grade >= 85)
.OrderByDescending(s => s.Grade)
.Select(s => s.Name);
foreach (var name in top)
Console.WriteLine(name); // Ayman, SaraLINQ is Lazy
LINQ queries are not executed until you iterate them (deferred execution). Call .ToList() or .ToArray() to materialise the results immediately and avoid repeated evaluation.
11. Async / Await
async and await let you write non-blocking code that reads like synchronous code. Essential for web requests, file I/O, and database calls.
using System.Net.Http;
// Mark a method async — it returns Task or Task<T>
static async Task<string> FetchDataAsync(string url)
{
using var client = new HttpClient();
// await suspends this method without blocking the thread
string content = await client.GetStringAsync(url);
return content;
}
// Call an async method with await
var data = await FetchDataAsync("https://example.com");
Console.WriteLine(data.Length);
// Run multiple tasks in parallel
var task1 = FetchDataAsync("https://a.com");
var task2 = FetchDataAsync("https://b.com");
var results = await Task.WhenAll(task1, task2);
// async void — only for event handlers (never use otherwise)
static async void OnButtonClick(object sender, EventArgs e)
{
await Task.Delay(1000); // non-blocking 1-second pause
Console.WriteLine("Done");
}Warning
Never call .Result or .Wait() on a Task from synchronous code — it causes deadlocks. Always await it instead, and make the calling method async too.
12. Modern C# Features
C# evolves rapidly. These features from C# 9–12 are now standard in modern codebases.
Records — immutable data objects
// record — auto-generates constructor, ToString, equality
public record Point(double X, double Y);
var p1 = new Point(1.0, 2.0);
var p2 = new Point(1.0, 2.0);
Console.WriteLine(p1 == p2); // True — value equality
Console.WriteLine(p1); // Point { X = 1, Y = 2 }
// 'with' — non-destructive mutation
var p3 = p1 with { Y = 99.0 }; // new Point(1.0, 99.0)Pattern Matching
object shape = new { Width = 4, Height = 5 };
// is pattern
if (shape is { } s)
Console.WriteLine("Not null");
// List patterns (C# 11)
int[] arr = { 1, 2, 3 };
if (arr is [1, .., 3])
Console.WriteLine("Starts with 1, ends with 3");Primary Constructors (C# 12)
// Parameters available throughout the class body
public class Student(string name, int grade)
{
public string Name => name;
public int Grade => grade;
public override string ToString() => $"{name}: {grade}";
}Delegates & Lambda Expressions
// Func<in, out> — built-in delegate for methods that return a value
Func<int, int, int> add = (a, b) => a + b;
Console.WriteLine(add(3, 4)); // 7
// Action<in> — built-in delegate for void methods
Action<string> print = msg => Console.WriteLine(msg);
print("Hello!");
// Predicate — returns bool (used in LINQ)
Predicate<int> isEven = n => n % 2 == 0;
Console.WriteLine(isEven(4)); // True
// Pass a lambda to a method
var evens = new[] { 1, 2, 3, 4 }
.Where(n => n % 2 == 0)
.ToList();