◆ .NET Full Course

A comprehensive journey through C# and the .NET ecosystem — from fundamentals to advanced patterns, with clean examples and visual explanations.

16
Chapters
100+
Topics
150+
Code Examples

📚 What You'll Learn

  • C# language fundamentals: types, operators, control flow
  • Object-Oriented Programming: classes, inheritance, polymorphism, interfaces
  • Advanced C#: records, patterns, generics, LINQ, async/await
  • .NET platform: DI, configuration, middleware, EF Core, ASP.NET Core
  • Testing, design patterns, performance, and interview prep

🚀 Getting Started

Prerequisites: Install the .NET SDK from dotnet.microsoft.com. Then create your first project:

bash
dotnet new console -n MyApp && cd MyApp
dotnet run

All code examples in this course are ready to copy and paste into a dotnet new console project targeting .NET 8+.

Chapter 01

C# Basics — Variables, Types, Operators

C# is a modern, type-safe, object-oriented language. It runs on .NET and is used for web, desktop, mobile, cloud, and game development.

Hello World

csharp
Console.WriteLine("Hello, World!");

Variables & Data Types

C# supports value types (stack) and reference types (heap).

csharp
int age = 25;
double price = 19.99;
decimal salary = 4500.00m;
string name = "Alice";
char grade = 'A';
bool isActive = true;
var inferred = "C# infers the type";

Value Types vs Reference Types

STACK int x = 10 string s ---+ object o ---+ HEAP "Alice" (string) object instance
Stack stores value types and references; Heap stores object data

Nullables, Operators & Keywords

csharp
int? maybe = null;              // nullable value type
int sure = maybe ?? 0;          // null-coalescing
int len = maybe?.ToString()?.Length ?? -1; // null-conditional

// is / as pattern matching
object obj = "hello";
if (obj is string str) Console.WriteLine(str.Length);
string? s = obj as string;

// ref / out / in
void Modify(ref int r) { r = 10; }
void Init(out int o)  { o = 42; }

// const vs readonly
const int Months = 12;          // compile-time
readonly int Id;                // runtime, set in ctor

== vs .Equals()

csharp
string a = "hello", b = "hello";
Console.WriteLine(a == b);          // True (string overloads ==)
Console.WriteLine(a.Equals(b));     // True (same content)
Console.WriteLine((object)a == (object)b); // False! reference compare
Use decimal for financial calculations (exact decimal precision). Use var when the type is obvious.
Chapter 02

Control Flow — Conditions & Loops

Branching and iteration are the backbone of program logic. C# offers rich conditional and looping constructs.

If / Else If / Else

csharp
int temp = 30;
if (temp > 35) Console.WriteLine("Hot");
else if (temp > 20) Console.WriteLine("Warm");
else Console.WriteLine("Cold");

Switch Expression (Pattern Matching)

csharp
string type = day switch
{
    "Saturday" or "Sunday" => "Weekend",
    "Monday" => "Start of work week",
    _ => "Midweek"
};

Loops

csharp
for (int i = 0; i < 5; i++) Console.WriteLine(i);

string[] fruits = ["Apple", "Banana", "Cherry"];
foreach (var f in fruits) Console.WriteLine(f);

int x = 0;
while (x < 3) { Console.WriteLine(x); x++; }

int y = 0;
do { Console.WriteLine(y); y++; } while (y < 3);
Chapter 03

Object-Oriented Programming

The four pillars of OOP: Encapsulation, Inheritance, Polymorphism, Abstraction.

Classes & Objects

csharp
public class Person
{
    public string Name { get; set; }
    public int Age { get; init; }
    public Person(string name, int age) => (Name, Age) = (name, age);
    public void Greet() => Console.WriteLine($"Hi, I'm {Name}");
}
var p = new Person("Bob", 30);
p.Greet();

Inheritance & Polymorphism

Animal (base) Dog : Animal Cat : Animal override Speak() → Woof! / Meow!
Inheritance hierarchy with polymorphic method override
csharp
public class Animal
{
    public virtual void Speak() => Console.WriteLine("...");
}
public class Dog : Animal
{
    public override void Speak() => Console.WriteLine("Woof!");
}
Animal a = new Dog();
a.Speak();  // Woof!

Abstract Class vs Interface

FeatureAbstract ClassInterface
Fields / StateYesNo (C# 8+ static only)
ConstructorsYesNo
Default implementationYesYes (C# 8+)
Multiple inheritanceNo (single)Yes (many)
Access modifiersAllPublic (default impl can have other)
Use whenShared state/behaviorContract / capability

Sealed & Partial Classes

csharp
public sealed class FinalClass { }      // cannot be inherited

// Partial class: split across files
// File1.cs
public partial class Employee { public string Name { get; set; } }
// File2.cs
public partial class Employee { public decimal Salary { get; set; } }
Chapter 04

Advanced OOP — Interfaces, Records, Structs

Modern C# features for building robust, expressive types.

Interfaces

csharp
public interface ILogger { void Log(string message); }

public class ConsoleLogger : ILogger
{
    public void Log(string msg) => Console.WriteLine($"[LOG] {msg}");
}

public class FileLogger : ILogger
{
    public void Log(string msg) => File.AppendAllText("log.txt", msg);
}

Records (Immutable by Default)

csharp
public record Person(string Name, int Age);

var p1 = new Person("Alice", 30);
var p2 = p1 with { Age = 31 };           // non-destructive mutation
Console.WriteLine(p1 == p2);              // False (value equality)
Console.WriteLine(p1);                    // Person { Name = Alice, Age = 30 }

Class vs Struct vs Record

class Reference type Heap allocated Reference equality Mutable Supports inheritance Nullable by default Shared state struct Value type Stack / inline Value equality Mutable by default No inheritance Nullable only with ? Small values record Reference (class) Heap allocated Value equality Immutable (with) No inheritance* Nullable DTOs / immutable
Comparison: class (reference, mutable), struct (value, copy), record (reference, value-equality, immutable)

Extension Methods

csharp
public static class StringExtensions
{
    public static string Capitalize(this string s) =>
        s.Length > 0 ? char.ToUpper(s[0]) + s[1..] : s;
}
Console.WriteLine("hello".Capitalize());  // Hello
Chapter 05

Collections & Generics

Type-safe data structures and generic programming.

Generic Constraints

csharp
public class Repository<T> where T : class, new() { }
// where T : struct | IComparable<T> | BaseClass | notnull

Covariance & Contravariance

csharp
IEnumerable<string> strs = ["a","b"];
IEnumerable<object> objs = strs;        // covariant (out T)

Action<object> broad = o => Console.WriteLine(o);
Action<string> narrow = broad;          // contravariant (in T)

Common Collections

List<T> Dynamic array Dictionary<K,V> Hash table O(1) HashSet<T> Unique O(1) PriorityQueue Min-heap (.NET 6) Queue<T> FIFO Stack<T> LIFO SortedDict O(log n) sorted LinkedList<T> Doubly-linked
Common collection types and their characteristics

IEnumerable<T> vs IQueryable<T>

IEnumerable<T> executes in memory (LINQ to Objects). IQueryable<T> builds expression trees translated by providers (EF Core translates to SQL).
csharp
IEnumerable<Product> inMemory = localList.Where(p => p.Price > 10);
// filter runs in .NET memory

IQueryable<Product> fromDb = ctx.Products.Where(p => p.Price > 10);
// translates to: SELECT * FROM Products WHERE Price > 10

Yield Return

csharp
IEnumerable<int> GetEven(int max)
{
    for (int i = 0; i <= max; i += 2)
        yield return i;
}
foreach (var n in GetEven(10)) Console.WriteLine(n); // 0 2 4 6 8 10
Chapter 06

LINQ — Language Integrated Query

Declarative querying over collections, databases, XML, and more.

Deferred vs Immediate Execution

Deferred Where, Select, GroupBy Immediate ToList, ToArray, Count Materialize Snapshot
Deferred operators build the query; immediate operators execute it

LINQ uses deferred execution by default: the query runs when you iterate, not when you define it.

Common LINQ Operators

csharp
var people = new[] {
    new { Name = "Alice", Age = 30 },
    new { Name = "Bob", Age = 25 },
    new { Name = "Charlie", Age = 35 }
};
var adults = people.Where(p => p.Age >= 18);
var names = people.Select(p => p.Name);
var ordered = people.OrderBy(p => p.Age).ThenBy(p => p.Name);
var grouped = people.GroupBy(p => p.Age / 10);
var first = people.FirstOrDefault(p => p.Name == "Bob");
var anyOver30 = people.Any(p => p.Age > 30);
var maxAge = people.Max(p => p.Age);
var flat = classes.SelectMany(c => c.Students); // flattening
Chapter 07

Delegates, Events & Lambda Expressions

First-class functions and the event-driven paradigm in C#.

Delegates & Built-in Func/Action/Predicate

csharp
Func<int, int, int> add = (a, b) => a + b;
Action<string> print = msg => Console.WriteLine(msg);
Predicate<int> isEven = n => n % 2 == 0;

// Multicast delegate
Action notify = () => Console.WriteLine("First");
notify += () => Console.WriteLine("Second");
notify();  // "First", "Second"

Events & Encapsulation

Events restrict the delegate: outside the class, only += and -= are allowed. Invocation is restricted to the declaring class.
csharp
public class Button
{
    public event EventHandler? Clicked;
    public void Click()
    {
        Clicked?.Invoke(this, EventArgs.Empty);  // only inside class
    }
}
var btn = new Button();
btn.Clicked += (s, e) => Console.WriteLine("handled");
// btn.Clicked = null;      // COMPILE ERROR: cannot assign
// btn.Clicked.Invoke();    // COMPILE ERROR: cannot invoke

Closures (Captured Variables)

csharp
int factor = 3;
Func<int, int> multiplier = n => n * factor;  // factor captured
Console.WriteLine(multiplier(5));  // 15
Chapter 08

Async / Await & Task-based Programming

Non-blocking, asynchronous code for I/O-bound operations.

Async Flow Diagram

Main Thread Task (ThreadPool) Callback await releases the thread; continuation resumes when done
Async/await flow: thread returns to pool during I/O, resumes on completion

Basic Async Method

csharp
async Task<string> FetchDataAsync(string url)
{
    string result = await new HttpClient().GetStringAsync(url);
    return result;
}
string data = await FetchDataAsync("https://api.example.com");

ConfigureAwait & Deadlock Prevention

csharp
async Task<string> ReadFileAsync(string path)
{
    using var stream = File.OpenRead(path);
    byte[] buffer = new byte[4096];
    int read = await stream.ReadAsync(buffer, 0, buffer.Length)
        .ConfigureAwait(false);  // no context capture
    return Encoding.UTF8.GetString(buffer, 0, read);
}

// DEADLOCK: .Result or .Wait() in UI/ASP.NET context
// var data = GetDataAsync().Result;  // deadlocks!
// Use async all the way up instead

ValueTask vs Task

ValueTask avoids Task allocation when the result is synchronously available. Use in high-frequency sync-or-async paths.

csharp
public ValueTask<int> GetCachedAsync()
{
    if (_cache is not null)
        return new ValueTask<int>(_cache.Value);  // sync, no allocation
    return new ValueTask<int>(FetchAsync());       // async path
}

Async Streams (IAsyncEnumerable)

csharp
async IAsyncEnumerable<int> GetNumbersAsync()
{
    for (int i = 0; i < 5; i++)
    {
        await Task.Delay(100);
        yield return i;
    }
}
await foreach (var n in GetNumbersAsync()) Console.WriteLine(n);
Chapter 09

File I/O & Serialization

Reading, writing, and persisting data in multiple formats.

File Operations

csharp
await File.WriteAllTextAsync("example.txt", "Hello, File!");
string text = await File.ReadAllTextAsync("example.txt");

string[] lines = await File.ReadAllLinesAsync("data.csv");

// Streams for large files
using var reader = new StreamReader("large.txt");
string? line;
while ((line = await reader.ReadLineAsync()) != null)
    Console.WriteLine(line);

JSON Serialization

csharp
using System.Text.Json;

var person = new Person { Name = "Alice", Age = 30 };
string json = JsonSerializer.Serialize(person);
Person? deserialized = JsonSerializer.Deserialize<Person>(json);

// Options
var opts = new JsonSerializerOptions
{
    WriteIndented = true,
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
Chapter 10

Memory Management & Exception Handling

Understanding GC, disposal patterns, and robust error handling.

IDisposable & IAsyncDisposable

csharp
public class FileWriter : IDisposable
{
    private FileStream _stream;
    public FileWriter(string path) => _stream = new FileStream(path, FileMode.Create);
    public void Dispose()
    {
        _stream?.Dispose();
        GC.SuppressFinalize(this);
    }
}
using var writer = new FileWriter("test.txt");  // auto-disposed

// Async variant
await using var db = new DatabaseWriter();

Stack vs Heap & GC Generations

Stack (per-thread) ~1 MB, LIFO, fast Value types, frames Ref pointers to heap Heap (shared) Gen 0: short-lived Gen 1: promoted Gen 2: long-lived LOH >85KB Large objects Gen 2 only Fragmentation risk
Memory architecture: Stack, Heap generations, and Large Object Heap

Exception Handling & throw vs throw ex

csharp
try { Divide(10, 0); }
catch (DivideByZeroException ex) { Console.WriteLine($"Math: {ex.Message}"); }
catch (Exception ex) { Console.WriteLine($"Error: {ex.Message}"); }
finally { Console.WriteLine("Cleanup"); }

// throw vs throw ex
catch (Exception ex)
{
    // throw ex;  // BAD: resets stack trace
    throw;        // GOOD: preserves original stack trace
}
Chapter 11

.NET Core & Architecture

Project structure, dependency injection, and configuration.

Dependency Injection Lifetimes

LifetimeInstanceBest For
AddSingleton<T>()Single instance for app lifetimeConfiguration, cache, logging
AddScoped<T>()Per HTTP request / scopeDbContext, request-scoped services
AddTransient<T>()New instance every injectionLightweight, stateless services
csharp
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddSingleton<ICache, MemoryCache>();
builder.Services.AddTransient<IEmailService, EmailService>();
Chapter 12

Entity Framework Core

ORM for .NET: mapping objects to relational data.

DbContext & CRUD

csharp
public class Blog
{
    public int Id { get; set; }
    public string Title { get; set; } = "";
    public List<Comment> Comments { get; set; } = [];
}
public class AppDbContext : DbContext
{
    public DbSet<Blog> Blogs => Set<Blog>();
    public AppDbContext(DbContextOptions<AppDbContext> opts) : base(opts) { }
}

// CRUD
context.Blogs.Add(blog);  await context.SaveChangesAsync();
var blog = await context.Blogs.Include(b => b.Comments)
    .FirstOrDefaultAsync(b => b.Id == id);
blog.Title = "Updated"; await context.SaveChangesAsync();
context.Blogs.Remove(blog); await context.SaveChangesAsync();

Eager / Lazy / Explicit Loading

csharp
// Eager
var blogs = ctx.Blogs.Include(b => b.Posts)
    .ThenInclude(p => p.Comments).ToList();

// Explicit
var blog = ctx.Blogs.First();
await ctx.Entry(blog).Collection(b => b.Posts).LoadAsync();

// Lazy: add Microsoft.EntityFrameworkCore.Proxies
// services.AddDbContext<Db>(o => o.UseLazyLoadingProxies());

Add vs Attach vs Update vs Entry.State

csharp
ctx.Products.Add(product);     // State = Added (INSERT)
ctx.Products.Attach(product);  // State = Unchanged (known)
ctx.Products.Update(product);  // State = Modified (UPDATE all)
ctx.Entry(product).State = EntityState.Deleted;  // DELETE
Chapter 13

ASP.NET Core Web API

Building RESTful APIs with Minimal APIs and Controllers.

Middleware Pipeline

Exception HttpsRedir StaticFiles Auth Authorization Endpoint Request passes through each middleware; response flows back in reverse
ASP.NET Core middleware pipeline order

Minimal API & JWT

csharp
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(opts => {
        opts.Authority = "https://tenant.oauth.com";
        opts.Audience = "api://default";
    });
builder.Services.AddCors(opts => opts.AddPolicy("AllowFE",
    p => p.WithOrigins("http://localhost:3000").AllowAnyHeader()));
var app = builder.Build();
app.UseCors("AllowFE"); app.UseAuthentication(); app.UseAuthorization();
app.MapGet("/api/hello", () => "Hello World");
app.MapPost("/api/data", (MyData data) => Results.Created($"/api/data/{data.Id}", data));
app.Run();
Chapter 14

Unit Testing with xUnit

Writing maintainable, verifiable tests for your code.

Facts, Theories & Mocking

csharp
[Fact]
public void Add_ReturnsSum()
{
    var calc = new Calculator();
    Assert.Equal(5, calc.Add(2, 3));
}

[Theory]
[InlineData(1, 2, 3)]
[InlineData(-1, 1, 0)]
public void Add_Various(int a, int b, int expected)
    => Assert.Equal(expected, new Calculator().Add(a, b));

// Moq: Stub vs Mock
var stub = new Mock<IRepository>();
stub.Setup(r => r.Get(1)).Returns(new User { Id = 1 }); // stub

var mock = new Mock<ILogger>();
service.DoWork();
mock.Verify(l => l.Log("done"), Times.Once);  // mock
Chapter 15

Best Practices & Design Patterns

SOLID, Repository, CQRS, and performance tips.

SOLID Principles

LetterPrincipleMeaning
SSingle ResponsibilityOne class, one reason to change
OOpen/ClosedOpen for extension, closed for modification
LLiskov SubstitutionSubtypes must be substitutable for base
IInterface SegregationSmall, focused interfaces
DDependency InversionDepend on abstractions, not concretions

CQRS Pattern

CQRS separates read (Query) and write (Command) models. Commands mutate state, Queries return data — they never overlap.
csharp
public class CreateOrderCommand : IRequest<int> { ... }
public class GetOrdersQuery : IRequest<List<Order>> { ... }
// Typically used with MediatR library

Performance Tips

csharp
// StringBuilder for many concatenations
var sb = new StringBuilder();
for (int i = 0; i < 1000; i++) sb.Append(i);

// Span<T> for zero-allocation slicing
ReadOnlySpan<char> slice = "hello world".AsSpan(0, 5);

// ArrayPool for large temporary buffers
byte[] buffer = ArrayPool<byte>.Shared.Rent(4096);
try { /* use */ } finally { ArrayPool<byte>.Shared.Return(buffer); }
Chapter 16

Deep Dives & Interview Topics

Advanced concepts, threading, reflection, and senior-level design.

Expression Trees

csharp
using System.Linq.Expressions;
Expression<Func<int, int, int>> expr = (a, b) => a + b;
var compiled = expr.Compile();
Console.WriteLine(compiled(2, 3));  // 5
// Used by: EF Core, AutoMapper, MVC Views

Reflection & Attributes

csharp
[AttributeUsage(AttributeTargets.Property)]
public class DisplayNameAttribute : Attribute
{
    public string Name { get; }
    public DisplayNameAttribute(string name) => Name = name;
}
// Reading at runtime
var props = typeof(Product).GetProperties();
foreach (var prop in props)
{
    var attr = prop.GetCustomAttribute<DisplayNameAttribute>();
    Console.WriteLine($"{prop.Name}: {attr?.Name}");
}

Threading & Concurrent Collections

csharp
// lock
object _lock = new(); lock (_lock) { /* critical */ }

// SemaphoreSlim (async-aware)
var sem = new SemaphoreSlim(3, 3);
await sem.WaitAsync(); try { /* limited resource */ } finally { sem.Release(); }

// Concurrent collections
var dict = new ConcurrentDictionary<string, int>();

// Channel (producer-consumer)
var ch = System.Threading.Channels.Channel.Create<int>();
await ch.Writer.WriteAsync(42);
await foreach (var item in ch.Reader.ReadAllAsync()) Console.WriteLine(item);

IComparable vs IComparer

csharp
public class Person : IComparable<Person>
{
    public int CompareTo(Person? other) => Age.CompareTo(other?.Age);
}
public class NameComparer : IComparer<Person>
{
    public int Compare(Person? x, Person? y) => string.Compare(x?.Name, y?.Name);
}
people.Sort();                 // uses IComparable
people.Sort(new NameComparer());  // uses IComparer

Primary Constructors & Raw Strings (C# 11/12)

csharp
// Primary constructor (C# 12)
public class Person(string name, int age)
{
    public string Name { get; } = name;
}

// required modifier (C# 11)
public class Config
{
    public required string ConnectionString { get; set; }
}

// Raw string literals (C# 11)
string json = """
{
    "name": "Alice",
    "age": 30
}
""";

Dispose vs Finalize & Memory Leaks

csharp
// Finalize (~destructor): non-deterministic, GC calls it
// Dispose: deterministic, called via using
// Common memory leaks:
// - Event handlers not unregistered (publisher keeps subscriber alive)
// - Static collections growing unbounded
// - Large Object Heap fragmentation
// - Improperly disposed DbContexts / HttpClients

// WeakReference: allow GC to collect while still having a reference
var cache = new WeakReference(new StringBuilder("data"));
if (cache.Target is StringBuilder sb) Console.WriteLine(sb);

Design a Notification System

csharp
public interface INotificationChannel { Task SendAsync(Notification n); }
public class EmailChannel : INotificationChannel { /* SMTP */ }
public class SmsChannel : INotificationChannel { /* Twilio */ }

public class NotificationService(IEnumerable<INotificationChannel> channels)
{
    public async Task SendAsync(Notification notif)
    {
        await Task.WhenAll(channels.Select(c => c.SendAsync(notif)));
    }
}
// Considerations: retry, idempotency, templating, queuing