C# Developer Roadmap 2026: Skills, Tools & Path to Becoming a Pro
Summary
Complete C# roadmap for 2026. Learn C# from zero to hero with clear steps, skills, tools, and projects to become a job-ready .NET developer.
Your Complete Journey from Beginner to Professional with Special Focus on C# 14 Revolutionary Features
💡 This comprehensive roadmap covers everything from programming basics to mastering the groundbreaking C# 14 features (Extension Members,
fieldkeyword, and more) that will define modern C# development in 2026.
Table of Contents
Foundation Phase
Core C# Phase
Specialization Phase
LEVEL 0 – Programming Fundamentals
⚠️ Skip to Level 1 if: You already know C/C++ or another programming language
📖 Step 0.1 – Core Programming Logic
Learn fundamental programming concepts in any beginner-friendly language:
Core Concepts
✅ What is a program, compiler, and runtime
✅ Variables and data types
✅ Input/output operations (
Console.ReadLine(),Console.WriteLine())✅ Conditional statements (
if/else)✅ Loop structures (
for,while,do-while)✅ Functions and methods
✅ Basic algorithms and problem-solving
Practice Problems
Solve 30-50 basic problems on:
HackerRank (Easy level)
Codeforces (Div. 3/4)
LeetCode (Easy)
Problem Categories:
Sum of numbers, averages
Finding max/min values
Factorial calculation
Prime number detection
Pattern printing (stars, pyramids)
Simple array operations
Resources
🛠️ LEVEL 1 – Development Environment & Version Control
🖥️ Step 1.1 – Setup Development Environment
Install Required Tools
1. .NET SDK
Download latest LTS version (.NET 10 recommended)
Verify installation:
dotnet --version
2. IDE/Editor
Visual Studio 2025/2026 (Community Edition) - Full-featured IDE with C# 14 support
OR VS Code + C# Dev Kit extension - Lightweight option
Important: Ensure your IDE supports C# 14 features (Extension Members,
fieldkeyword)
3. SQL Server
SQL Server Express (for local development)
OR SQL Server LocalDB
Azure Data Studio or SSMS for database management
Your First C# Program
// Create project
dotnet new console -n HelloWorld
cd HelloWorld
// Program.cs
Console.WriteLine("Hello, C# World!");
Run your program:
dotnet run
Resources
📦 Step 1.2 – Version Control with Git
Why Git?
Track code changes over time
Collaborate with teams
Revert to previous versions
Create experimental branches safely
Essential Git Concepts
Repository Types:
Local: Your computer
Remote: GitHub, GitLab, Azure DevOps
Commit Process:
One-step: Direct commit (SVN style)
Two-step: Stage → Commit (Git style)
Core Git Commands
# Initialize repository
git init
# Clone existing repository
git clone <url>
# Check status
git status
# Stage changes
git add <file>
git add . # Stage all
# Commit changes
git commit -m "Descriptive message"
# View history
git log
git log --oneline
Working with Remotes
# Add remote
git remote add origin <url>
# Push to remote
git push origin main
# Pull from remote
git pull origin main
# Fetch without merging
git fetch origin
Authentication Methods
HTTPS: Username + Personal Access Token
SSH: SSH key pairs (more secure, no password prompts)
Branching & Merging
# Create branch
git branch feature-login
# Switch to branch
git checkout feature-login
# OR (newer syntax)
git switch feature-login
# Create and switch in one command
git checkout -b feature-login
# Merge branch
git checkout main
git merge feature-login
# Delete branch
git branch -d feature-login
Handling Merge Conflicts
When conflicts occur:
Git marks conflicted files
Open files and look for conflict markers:
<<<<<<< HEAD
Your changes
=======
Their changes
>>>>>>> branch-name
Manually resolve conflicts
Stage resolved files:
git add <file>Complete merge:
git commit
Practice Tasks
✅ Create GitHub account
✅ Create repository for learning projects
✅ Push at least 5 commits
✅ Create a branch, make changes, merge back
✅ Practice resolving a merge conflict
Resources
💻 LEVEL 2 – C# Syntax & Basic Types
🔢 Step 2.1 – Data Types & Variables
Value Types vs Reference Types
Value Types (stored on stack)
int,double,decimal,float,bool,charstruct,enumStore actual value
Reference Types (stored on heap)
string,object,arrayclass,interface,delegateStore reference to memory location
Built-in Types
// Integer types
byte age = 25; // 0 to 255
short year = 2025; // -32,768 to 32,767
int count = 1000; // -2.1B to 2.1B
long population = 8000000000L;
// Floating-point types
float rate = 3.14f; // 7 digits precision
double precise = 3.14159265; // 15-16 digits
decimal money = 99.99m; // 28-29 digits (financial)
// Other types
bool isActive = true;
char grade = 'A';
string name = "John Doe";
Variables & Constants
// Variables (can change)
int counter = 0;
counter = 10;
// Constants (cannot change)
const double PI = 3.14159;
const int MAX_USERS = 100;
// Read-only (set once, at runtime)
readonly string ConnectionString;
Arrays
Single-Dimensional Arrays:
// Declaration and initialization
int[] numbers = new int[5];
int[] scores = { 85, 90, 78, 92, 88 };
// Access elements
scores[0] = 95;
int firstScore = scores[0];
// Array length
int length = scores.Length;
Multidimensional Arrays:
// 2D array (matrix)
int[,] matrix = new int[3, 4];
int[,] grid = {
{ 1, 2, 3 },
{ 4, 5, 6 },
{ 7, 8, 9 }
};
// Access
int value = grid[1, 2]; // 6
Jagged Arrays (Array of Arrays):
int[][] jaggedArray = new int[3][];
jaggedArray[0] = new int[] { 1, 2, 3, 4 };
jaggedArray[1] = new int[] { 5, 6 };
jaggedArray[2] = new int[] { 7, 8, 9 };
Console Input/Output
// Output
Console.WriteLine("Hello World!");
Console.Write("No newline");
// String interpolation
string name = "Alice";
int age = 30;
Console.WriteLine($"Name: {name}, Age: {age}");
// Input
Console.WriteLine("Enter your name:");
string userName = Console.ReadLine();
// Parsing input
Console.WriteLine("Enter a number:");
int number = int.Parse(Console.ReadLine());
// OR (safer)
if (int.TryParse(Console.ReadLine(), out int result))
{
Console.WriteLine($"You entered: {result}");
}
Practice Projects
✅ Calculator (basic arithmetic)
✅ Grade calculator (input scores, calculate average)
✅ Array manipulation (sum, average, max, min)
✅ Temperature converter (C to F and vice versa)
Resources
🔀 Step 2.2 – Statements & Operators
Conditional Statements
If-Else:
int score = 85;
if (score >= 90)
{
Console.WriteLine("Grade: A");
}
else if (score >= 80)
{
Console.WriteLine("Grade: B");
}
else if (score >= 70)
{
Console.WriteLine("Grade: C");
}
else
{
Console.WriteLine("Grade: F");
}
Switch Statement:
string dayOfWeek = "Monday";
switch (dayOfWeek)
{
case "Monday":
case "Tuesday":
case "Wednesday":
case "Thursday":
case "Friday":
Console.WriteLine("Weekday");
break;
case "Saturday":
case "Sunday":
Console.WriteLine("Weekend");
break;
default:
Console.WriteLine("Invalid day");
break;
}
Operators
Arithmetic Operators:
int a = 10, b = 3;
int sum = a + b; // 13
int diff = a - b; // 7
int product = a * b; // 30
int quotient = a / b; // 3 (integer division)
int remainder = a % b; // 1 (modulo)
// Increment/Decrement
a++; // Post-increment
++a; // Pre-increment
b--; // Post-decrement
--b; // Pre-decrement
Relational Operators:
bool isEqual = (a == b); // false
bool isNotEqual = (a != b); // true
bool isGreater = (a > b); // true
bool isLess = (a < b); // false
bool isGreaterOrEqual = (a >= b);
bool isLessOrEqual = (a <= b);
Logical Operators:
bool result1 = true && false; // AND - false
bool result2 = true || false; // OR - true
bool result3 = !true; // NOT - false
// Short-circuit evaluation
bool x = false && ExpensiveOperation(); // ExpensiveOperation not called
Bitwise Operators:
int x = 5; // 0101 in binary
int y = 3; // 0011 in binary
int and = x & y; // 0001 = 1
int or = x | y; // 0111 = 7
int xor = x ^ y; // 0110 = 6
int not = ~x; // Inverts all bits
int leftShift = x << 1; // 1010 = 10
int rightShift = x >> 1; // 0010 = 2
Ternary Operator:
int age = 18;
string status = (age >= 18) ? "Adult" : "Minor";
Loop Structures
For Loop:
for (int i = 0; i < 10; i++)
{
Console.WriteLine($"Iteration {i}");
}
// Loop through array
int[] numbers = { 1, 2, 3, 4, 5 };
for (int i = 0; i < numbers.Length; i++)
{
Console.WriteLine(numbers[i]);
}
While Loop:
int counter = 0;
while (counter < 5)
{
Console.WriteLine(counter);
counter++;
}
Do-While Loop:
int num;
do
{
Console.WriteLine("Enter a positive number:");
num = int.Parse(Console.ReadLine());
} while (num <= 0);
Foreach Loop:
string[] fruits = { "Apple", "Banana", "Orange" };
foreach (string fruit in fruits)
{
Console.WriteLine(fruit);
}
Break & Continue:
// Break - exit loop
for (int i = 0; i < 10; i++)
{
if (i == 5) break;
Console.WriteLine(i); // 0, 1, 2, 3, 4
}
// Continue - skip iteration
for (int i = 0; i < 10; i++)
{
if (i % 2 == 0) continue;
Console.WriteLine(i); // 1, 3, 5, 7, 9
}
Practice Projects
✅ Pattern printing (pyramids, diamonds)
✅ Number guessing game
✅ Menu-driven calculator
✅ Prime number checker
✅ Fibonacci sequence generator
Resources
📝 Step 2.3 – Working with Text
String Fundamentals
string message = "Hello, World!";
// Properties
int length = message.Length; // 13
// Indexing
char first = message[0]; // 'H'
char last = message[^1]; // '!' (index from end, C# 8+)
Essential String Methods
string text = " Hello World ";
// Case conversion
string upper = text.ToUpper(); // " HELLO WORLD "
string lower = text.ToLower(); // " hello world "
// Trimming
string trimmed = text.Trim(); // "Hello World"
string trimStart = text.TrimStart(); // "Hello World "
string trimEnd = text.TrimEnd(); // " Hello World"
// Searching
bool contains = text.Contains("World"); // true
int index = text.IndexOf("World"); // 8
int lastIndex = text.LastIndexOf("o"); // 10
// Substring
string sub = text.Substring(2, 5); // "Hello"
// Replace
string replaced = text.Replace("World", "C#"); // " Hello C# "
// Split
string csv = "Apple,Banana,Orange";
string[] fruits = csv.Split(','); // ["Apple", "Banana", "Orange"]
// Join
string joined = string.Join(", ", fruits); // "Apple, Banana, Orange"
// Checking
bool isEmpty = string.IsNullOrEmpty(text); // false
bool isWhitespace = string.IsNullOrWhiteSpace(" "); // true
String Concatenation vs Interpolation
string firstName = "John";
string lastName = "Doe";
int age = 30;
// Concatenation (old way)
string msg1 = "Name: " + firstName + " " + lastName + ", Age: " + age;
// Interpolation (modern, preferred)
string msg2 = $"Name: {firstName} {lastName}, Age: {age}";
// Multi-line interpolation
string msg3 = $@"
Name: {firstName} {lastName}
Age: {age}
Status: Active
";
String Formatting
double price = 1234.56;
DateTime now = [DateTime.Now](<http://DateTime.Now>);
// Number formatting
string currency = $"{price:C}"; // $1,234.56
string decimal2 = $"{price:F2}"; // 1234.56
string percent = $"{0.85:P}"; // 85.00%
// Date formatting
string date1 = $"{now:yyyy-MM-dd}"; // 2025-11-20
string date2 = $"{now:dd/MM/yyyy}"; // 20/11/2025
string time = $"{now:HH:mm:ss}"; // 02:21:51
string full = $"{now:yyyy-MM-dd HH:mm}"; // 2025-11-20 02:21
Char Operations
char ch = 'A';
// Checking
bool isLetter = char.IsLetter(ch); // true
bool isDigit = char.IsDigit('5'); // true
bool isUpper = char.IsUpper(ch); // true
bool isLower = char.IsLower(ch); // false
bool isWhiteSpace = char.IsWhiteSpace(' '); // true
// Conversion
char upper = char.ToUpper('a'); // 'A'
char lower = char.ToLower('A'); // 'a'
StringBuilder (Performance)
using System.Text;
// Bad for many concatenations (creates many string objects)
string result = "";
for (int i = 0; i < 1000; i++)
{
result += i.ToString(); // Slow!
}
// Good - StringBuilder is mutable
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++)
{
sb.Append(i);
}
string result = sb.ToString();
// StringBuilder methods
sb.AppendLine("New line");
sb.Insert(0, "Start: ");
sb.Remove(0, 7);
sb.Replace("old", "new");
sb.Clear();
Practice Projects
✅ Palindrome checker
✅ Word counter
✅ Text-based menu system
✅ String reverser
✅ Caesar cipher encoder/decoder
✅ Email validator (basic)
Resources
🏗️ LEVEL 3 – Object-Oriented Programming
📦 Step 3.1 – Classes & Objects Fundamentals
What are Classes and Objects?
Class: Blueprint for creating objects
Object: Instance of a class
// Define a class
public class Student
{
// Fields (private by convention)
private string name;
private int age;
// Properties (public interface)
public string Name { get; set; }
public int Age { get; set; }
// Constructor
public Student(string name, int age)
{
Name = name;
Age = age;
}
// Method
public void PrintInfo()
{
Console.WriteLine($"Student: {Name}, Age: {Age}");
}
}
// Create and use objects
Student student1 = new Student("Alice", 20);
Student student2 = new Student("Bob", 22);
student1.PrintInfo(); // Student: Alice, Age: 20
Namespaces
namespace MyCompany.ProjectName.Models
{
public class Product
{
public string Name { get; set; }
public decimal Price { get; set; }
}
}
// Using namespaces
using MyCompany.ProjectName.Models;
Product product = new Product();
Access Modifiers
public class Example
{
public int PublicField; // Accessible everywhere
private int PrivateField; // Only within this class
protected int ProtectedField; // This class + derived classes
internal int InternalField; // Within same assembly
protected internal int ProtectedInternalField; // Protected OR internal
private protected int PrivateProtectedField; // Protected AND internal
}
Properties
Auto-Implemented Properties:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
// Read-only auto-property
public string Id { get; }
// Init-only property (C# 9+)
public DateTime BirthDate { get; init; }
}
var person = new Person
{
Name = "John",
Age = 30,
BirthDate = new DateTime(1995, 1, 1)
};
Full Properties with Backing Field:
public class BankAccount
{
private decimal _balance;
public decimal Balance
{
get { return _balance; }
set
{
if (value >= 0)
_balance = value;
else
throw new ArgumentException("Balance cannot be negative");
}
}
}
Expression-Bodied Properties:
public class Circle
{
public double Radius { get; set; }
// Computed property
public double Area => Math.PI * Radius * Radius;
public double Circumference => 2 * Math.PI * Radius;
}
Method Overloading
public class Calculator
{
// Same method name, different parameters
public int Add(int a, int b)
{
return a + b;
}
public double Add(double a, double b)
{
return a + b;
}
public int Add(int a, int b, int c)
{
return a + b + c;
}
}
Constructors
public class Product
{
public string Name { get; set; }
public decimal Price { get; set; }
public string Category { get; set; }
// Default constructor
public Product()
{
Category = "General";
}
// Parameterized constructor
public Product(string name, decimal price)
{
Name = name;
Price = price;
Category = "General";
}
// Constructor chaining
public Product(string name, decimal price, string category)
: this(name, price) // Calls other constructor first
{
Category = category;
}
}
Destructor/Finalizer
public class Resource
{
// Finalizer (rarely needed in modern C#)
~Resource()
{
// Cleanup code
Console.WriteLine("Resource being finalized");
}
}
Readonly vs Const
public class Configuration
{
// Const - compile-time constant
public const int MAX_SIZE = 100;
// Readonly - runtime constant
public readonly string ConnectionString;
public Configuration(string connString)
{
ConnectionString = connString; // Can set in constructor
}
}
Practice Projects
✅ Student management system (Student class with properties and methods)
✅ Product catalog (Product, Category classes)
✅ Bank account simulator (Account class with deposit/withdraw)
✅ Library system (Book, Member classes)
Resources
🎭 Step 3.2 – Advanced Class Types
Static Classes & Members
// Static class (cannot be instantiated)
public static class MathHelper
{
public static double PI = 3.14159;
public static double CalculateCircleArea(double radius)
{
return PI * radius * radius;
}
}
// Usage
double area = MathHelper.CalculateCircleArea(5);
// Regular class with static members
public class Counter
{
private static int count = 0; // Shared across all instances
public int InstanceId { get; }
public Counter()
{
count++;
InstanceId = count;
}
public static int GetTotalCount() => count;
}
Abstract Classes
// Cannot be instantiated directly
public abstract class Shape
{
public string Color { get; set; }
// Abstract method (must be implemented by derived classes)
public abstract double CalculateArea();
// Concrete method (can be inherited as-is)
public void Display()
{
Console.WriteLine($"Shape Color: {Color}, Area: {CalculateArea()}");
}
}
public class Circle : Shape
{
public double Radius { get; set; }
// Must implement abstract method
public override double CalculateArea()
{
return Math.PI * Radius * Radius;
}
}
public class Rectangle : Shape
{
public double Width { get; set; }
public double Height { get; set; }
public override double CalculateArea()
{
return Width * Height;
}
}
Interfaces
// Interface defines contract
public interface IDrawable
{
void Draw();
void Erase();
}
public interface IResizable
{
void Resize(double factor);
}
// Class can implement multiple interfaces
public class Shape : IDrawable, IResizable
{
public void Draw()
{
Console.WriteLine("Drawing shape");
}
public void Erase()
{
Console.WriteLine("Erasing shape");
}
public void Resize(double factor)
{
Console.WriteLine($"Resizing by factor {factor}");
}
}
Method Overriding
public class Animal
{
// Virtual method can be overridden
public virtual void MakeSound()
{
Console.WriteLine("Some generic animal sound");
}
}
public class Dog : Animal
{
// Override virtual method
public override void MakeSound()
{
Console.WriteLine("Woof!");
}
}
public class Cat : Animal
{
public override void MakeSound()
{
Console.WriteLine("Meow!");
}
}
// Usage
Animal animal1 = new Dog();
Animal animal2 = new Cat();
animal1.MakeSound(); // Woof!
animal2.MakeSound(); // Meow!
Hiding Members with 'new'
public class BaseClass
{
public void Display()
{
Console.WriteLine("Base Display");
}
}
public class DerivedClass : BaseClass
{
// Hides base method (not override)
public new void Display()
{
Console.WriteLine("Derived Display");
}
}
// Usage
DerivedClass obj = new DerivedClass();
obj.Display(); // "Derived Display"
BaseClass baseRef = new DerivedClass();
baseRef.Display(); // "Base Display" (calls base version)
Sealed Keyword
// Sealed class cannot be inherited
public sealed class FinalClass
{
public void DoSomething() { }
}
// This would cause error:
// public class CannotDerive : FinalClass { }
// Sealed method cannot be overridden further
public class Base
{
public virtual void Method() { }
}
public class Middle : Base
{
public sealed override void Method() { }
}
public class Derived : Middle
{
// This would cause error:
// public override void Method() { }
}
Partial Classes
// File1.cs
public partial class Employee
{
public string Name { get; set; }
public void Work()
{
Console.WriteLine("Working...");
}
}
// File2.cs
public partial class Employee
{
public decimal Salary { get; set; }
public void TakeBreak()
{
Console.WriteLine("Taking a break...");
}
}
// Both parts combined at compile time
Practice Projects
✅ Shape hierarchy (Shape, Circle, Rectangle, Triangle with IDrawable)
✅ Animal kingdom (Animal base, various animals with specific behaviors)
✅ Payment system (IPayment interface, CreditCard, PayPal, BankTransfer)
✅ Logger system (ILogger interface, FileLogger, ConsoleLogger, DatabaseLogger)
Resources
📊 Step 3.3 – Other Important Types
Struct vs Class
// Struct - value type
public struct Point
{
public int X { get; set; }
public int Y { get; set; }
public Point(int x, int y)
{
X = x;
Y = y;
}
}
// Class - reference type
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
// Behavior difference
Point p1 = new Point(10, 20);
Point p2 = p1; // Copies value
p2.X = 100;
Console.WriteLine(p1.X); // Still 10
Person person1 = new Person { Name = "John" };
Person person2 = person1; // Copies reference
[person2.Name](<http://person2.Name>) = "Jane";
Console.WriteLine([person1.Name](<http://person1.Name>)); // Changed to "Jane"
When to use struct:
Small data structures (< 16 bytes)
Immutable data
Represents a single value (like Point, Color, DateTime)
Enums
// Define enum
public enum OrderStatus
{
Pending, // 0
Processing, // 1
Shipped, // 2
Delivered, // 3
Cancelled // 4
}
// With explicit values
public enum ErrorCode
{
Success = 0,
NotFound = 404,
ServerError = 500,
Unauthorized = 401
}
// Flags enum (for combinations)
[Flags]
public enum FileAccess
{
None = 0,
Read = 1,
Write = 2,
Execute = 4,
ReadWrite = Read | Write // 3
}
// Usage
OrderStatus status = OrderStatus.Processing;
if (status == OrderStatus.Delivered)
{
Console.WriteLine("Order delivered!");
}
// Convert to/from string
string statusName = status.ToString(); // "Processing"
OrderStatus parsed = Enum.Parse<OrderStatus>("Shipped");
// Flags usage
FileAccess access = [FileAccess.Read](<http://FileAccess.Read>) | FileAccess.Write;
bool canRead = access.HasFlag([FileAccess.Read](<http://FileAccess.Read>)); // true
DateTime
// Current date and time
DateTime now = [DateTime.Now](<http://DateTime.Now>); // Local time
DateTime utcNow = DateTime.UtcNow; // UTC time
DateTime today = [DateTime.Today](<http://DateTime.Today>); // Date only (time = 00:00:00)
// Create specific date
DateTime date1 = new DateTime(2025, 11, 20);
DateTime date2 = new DateTime(2025, 11, 20, 14, 30, 0);
// Operations
DateTime tomorrow = now.AddDays(1);
DateTime nextWeek = now.AddDays(7);
DateTime nextMonth = now.AddMonths(1);
DateTime lastYear = now.AddYears(-1);
// Comparison
TimeSpan difference = tomorrow - now;
bool isBefore = date1 < date2;
// Formatting
string formatted1 = now.ToString("yyyy-MM-dd"); // 2025-11-20
string formatted2 = now.ToString("dd/MM/yyyy HH:mm:ss"); // 20/11/2025 02:21:51
string formatted3 = now.ToString("MMMM dd, yyyy"); // November 20, 2025
// Parsing
DateTime parsed = DateTime.Parse("2025-11-20");
if (DateTime.TryParse("invalid", out DateTime result))
{
Console.WriteLine(result);
}
Parameter Modifiers
ref - Pass by reference:
void Increment(ref int number)
{
number++;
}
int value = 10;
Increment(ref value);
Console.WriteLine(value); // 11
out - Output parameter:
bool TryDivide(int a, int b, out int result)
{
if (b == 0)
{
result = 0;
return false;
}
result = a / b;
return true;
}
if (TryDivide(10, 2, out int quotient))
{
Console.WriteLine(quotient); // 5
}
params - Variable number of arguments:
int Sum(params int[] numbers)
{
return numbers.Sum();
}
int total1 = Sum(1, 2, 3); // 6
int total2 = Sum(1, 2, 3, 4, 5, 6); // 21
in - Read-only reference (C# 7.2+):
void ProcessLargeStruct(in LargeStruct data)
{
// Can read but not modify
Console.WriteLine(data.Value);
}
Tuples
// Named tuple
(string Name, int Age) person = ("John", 30);
Console.WriteLine([person.Name](<http://person.Name>)); // John
// Tuple return value
(int Min, int Max) GetRange(int[] numbers)
{
return (numbers.Min(), numbers.Max());
}
var range = GetRange(new[] { 5, 2, 8, 1, 9 });
Console.WriteLine($"Min: {range.Min}, Max: {range.Max}");
// Tuple deconstruction
(int min, int max) = GetRange(new[] { 5, 2, 8, 1, 9 });
Console.WriteLine($"Min: {min}, Max: {max}");
Nullable Types
// Nullable value types
int? nullableInt = null;
DateTime? nullableDate = null;
// Check for null
if (nullableInt.HasValue)
{
int value = nullableInt.Value;
}
// Null-coalescing operator
int result = nullableInt ?? 0; // Use 0 if null
// Null-coalescing assignment (C# 8+)
nullableInt ??= 10; // Assign 10 if null
Anonymous Types
var person = new
{
Name = "Alice",
Age = 25,
City = "New York"
};
Console.WriteLine($"{[person.Name](<http://person.Name>)} is {person.Age} years old");
// Often used with LINQ
var results = students
.Select(s => new { [s.Name](<http://s.Name>), s.Grade })
.ToList();
Dynamic Type
dynamic value = 10;
value = "Hello";
value = new { Name = "Test" };
// No compile-time checking (use carefully!)
Console.WriteLine([value.Name](<http://value.Name>));
Practice Projects
✅ Age calculator using DateTime
✅ Deadline reminder system
✅ File permission system using Flags enum
✅ Status tracking system using enums
Resources
🔧 Step 3.4 – Generics & Collections
Generic Classes
// Generic class
public class Box<T>
{
private T _content;
public void Put(T item)
{
_content = item;
}
public T Get()
{
return _content;
}
}
// Usage
Box<int> intBox = new Box<int>();
intBox.Put(42);
int value = intBox.Get();
Box<string> stringBox = new Box<string>();
stringBox.Put("Hello");
Generic Methods
public class Utilities
{
public static T FindMax<T>(T[] array) where T : IComparable<T>
{
T max = array[0];
foreach (T item in array)
{
if (item.CompareTo(max) > 0)
max = item;
}
return max;
}
}
// Usage
int[] numbers = { 5, 2, 8, 1, 9 };
int max = Utilities.FindMax(numbers);
Generic Constraints
// where T : class - must be reference type
public class ClassConstraint<T> where T : class
{
}
// where T : struct - must be value type
public class StructConstraint<T> where T : struct
{
}
// where T : new() - must have parameterless constructor
public class Repository<T> where T : new()
{
public T Create()
{
return new T();
}
}
// where T : BaseClass - must inherit from BaseClass
public class DerivedConstraint<T> where T : Animal
{
}
// where T : IInterface - must implement interface
public class InterfaceConstraint<T> where T : IComparable
{
}
// Multiple constraints
public class MultiConstraint<T> where T : class, IDisposable, new()
{
}
Default Keyword for Generics
public T GetDefaultValue<T>()
{
return default(T); // 0 for int, null for reference types, etc.
}
Generic Collections
List<T>:
List<string> names = new List<string>();
names.Add("Alice");
names.Add("Bob");
names.AddRange(new[] { "Charlie", "David" });
// Access
string first = names[0];
int count = names.Count;
// Search
bool contains = names.Contains("Alice");
int index = names.IndexOf("Bob");
// Remove
names.Remove("Alice");
names.RemoveAt(0);
// Iterate
foreach (string name in names)
{
Console.WriteLine(name);
}
// LINQ operations
var filtered = names.Where(n => n.StartsWith("D")).ToList();
Dictionary<TKey, TValue>:
Dictionary<string, int> ages = new Dictionary<string, int>();
ages.Add("Alice", 25);
ages["Bob"] = 30; // Add or update
// Access
int aliceAge = ages["Alice"];
// Safe access
if (ages.TryGetValue("Charlie", out int charlieAge))
{
Console.WriteLine(charlieAge);
}
// Check existence
bool hasAlice = ages.ContainsKey("Alice");
bool hasAge30 = ages.ContainsValue(30);
// Iterate
foreach (KeyValuePair<string, int> kvp in ages)
{
Console.WriteLine($"{kvp.Key}: {kvp.Value}");
}
// Or
foreach (var (name, age) in ages)
{
Console.WriteLine($"{name}: {age}");
}
HashSet<T>:
HashSet<int> numbers = new HashSet<int>();
numbers.Add(1);
numbers.Add(2);
numbers.Add(2); // Duplicate, not added
// numbers = {1, 2}
// Set operations
HashSet<int> set1 = new HashSet<int> { 1, 2, 3 };
HashSet<int> set2 = new HashSet<int> { 3, 4, 5 };
set1.UnionWith(set2); // {1, 2, 3, 4, 5}
set1.IntersectWith(set2); // {3}
set1.ExceptWith(set2); // {1, 2}
Queue<T>:
Queue<string> queue = new Queue<string>();
queue.Enqueue("First");
queue.Enqueue("Second");
queue.Enqueue("Third");
string first = queue.Dequeue(); // "First" (FIFO)
string peek = queue.Peek(); // "Second" (doesn't remove)
Stack<T>:
Stack<int> stack = new Stack<int>();
stack.Push(1);
stack.Push(2);
stack.Push(3);
int top = stack.Pop(); // 3 (LIFO)
int peek = stack.Peek(); // 2 (doesn't remove)
Non-Generic Collections (Avoid)
// ArrayList - avoid, use List<T> instead
ArrayList list = new ArrayList();
list.Add(1);
list.Add("string"); // No type safety!
// Hashtable - avoid, use Dictionary<TKey, TValue> instead
Hashtable table = new Hashtable();
Practice Projects
✅ Generic repository for CRUD operations
✅ In-memory database using Dictionary and List
✅ Task manager using appropriate collections
✅ Contact book with search functionality
Resources
🔌 Step 3.5 – Important Interfaces
IEnumerable & IEnumerable<T>
// IEnumerable allows foreach iteration
public class CustomCollection : IEnumerable<int>
{
private List<int> _items = new List<int>();
public void Add(int item) => _items.Add(item);
public IEnumerator<int> GetEnumerator()
{
return _items.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
// Usage
CustomCollection collection = new CustomCollection();
collection.Add(1);
collection.Add(2);
foreach (int item in collection)
{
Console.WriteLine(item);
}
ICollection & ICollection<T>
// ICollection adds Count, Add, Remove, Contains
public class MyCollection<T> : ICollection<T>
{
private List<T> _items = new List<T>();
public int Count => _items.Count;
public bool IsReadOnly => false;
public void Add(T item) => _items.Add(item);
public bool Remove(T item) => _items.Remove(item);
public bool Contains(T item) => _items.Contains(item);
public void Clear() => _items.Clear();
public void CopyTo(T[] array, int arrayIndex)
{
_items.CopyTo(array, arrayIndex);
}
public IEnumerator<T> GetEnumerator() => _items.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
IDisposable
// For resources that need cleanup
public class FileProcessor : IDisposable
{
private FileStream _fileStream;
private bool _disposed = false;
public FileProcessor(string filePath)
{
_fileStream = File.OpenRead(filePath);
}
public void ProcessFile()
{
// Process the file
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed) return;
if (disposing)
{
// Dispose managed resources
_fileStream?.Dispose();
}
// Dispose unmanaged resources
_disposed = true;
}
~FileProcessor()
{
Dispose(false);
}
}
// Using statement automatically calls Dispose
using (var processor = new FileProcessor("data.txt"))
{
processor.ProcessFile();
} // Dispose called automatically
// Or with C# 8+ syntax
using var processor2 = new FileProcessor("data.txt");
processor2.ProcessFile();
// Dispose called at end of scope
ICloneable
public class Person : ICloneable
{
public string Name { get; set; }
public int Age { get; set; }
public Address Address { get; set; }
// Shallow copy
public object Clone()
{
return this.MemberwiseClone();
}
// Deep copy (custom method)
public Person DeepClone()
{
return new Person
{
Name = [this.Name](<http://this.Name>),
Age = this.Age,
Address = new Address
{
Street = this.Address.Street,
City = [this.Address.City](<http://this.Address.City>)
}
};
}
}
Practice Projects
✅ Custom collection implementing IEnumerable
✅ File reader/writer with proper IDisposable implementation
✅ Database connection wrapper with using pattern
Resources
🏛️ Step 3.6 – OOP Principles & SOLID
Four Pillars of OOP
1. Encapsulation:
public class BankAccount
{
// Hide implementation details
private decimal _balance;
// Controlled access through properties/methods
public decimal Balance
{
get => _balance;
private set => _balance = value;
}
public void Deposit(decimal amount)
{
if (amount > 0)
_balance += amount;
}
public bool Withdraw(decimal amount)
{
if (amount > 0 && amount <= _balance)
{
_balance -= amount;
return true;
}
return false;
}
}
2. Inheritance:
// Base class
public class Vehicle
{
public string Brand { get; set; }
public void Start() => Console.WriteLine("Vehicle started");
}
// Derived class inherits from base
public class Car : Vehicle
{
public int NumberOfDoors { get; set; }
public void OpenTrunk() => Console.WriteLine("Trunk opened");
}
3. Abstraction:
// Hide complex implementation, show only essentials
public interface IPaymentProcessor
{
void ProcessPayment(decimal amount);
}
public class CreditCardProcessor : IPaymentProcessor
{
public void ProcessPayment(decimal amount)
{
// Complex credit card processing logic hidden
ValidateCard();
ConnectToGateway();
ChargeCard(amount);
SendConfirmation();
}
private void ValidateCard() { }
private void ConnectToGateway() { }
private void ChargeCard(decimal amount) { }
private void SendConfirmation() { }
}
4. Polymorphism:
public abstract class Shape
{
public abstract void Draw();
}
public class Circle : Shape
{
public override void Draw()
{
Console.WriteLine("Drawing a circle");
}
}
public class Square : Shape
{
public override void Draw()
{
Console.WriteLine("Drawing a square");
}
}
// Polymorphism in action
List<Shape> shapes = new List<Shape>
{
new Circle(),
new Square(),
new Circle()
};
foreach (Shape shape in shapes)
{
shape.Draw(); // Calls appropriate Draw method
}
SOLID Principles
S - Single Responsibility Principle:
Each class should have only one reason to change.
// ❌ Bad - Multiple responsibilities
public class User
{
public void Register() { }
public void SendEmail() { }
public void SaveToDatabase() { }
}
// ✅ Good - Single responsibility per class
public class User
{
public string Name { get; set; }
public string Email { get; set; }
}
public class UserRegistration
{
public void Register(User user) { }
}
public class EmailService
{
public void SendEmail(string to, string subject, string body) { }
}
public class UserRepository
{
public void Save(User user) { }
}
O - Open/Closed Principle:
Open for extension, closed for modification.
// ❌ Bad - Need to modify when adding new shapes
public class AreaCalculator
{
public double CalculateArea(object shape)
{
if (shape is Circle circle)
return Math.PI * circle.Radius * circle.Radius;
else if (shape is Rectangle rect)
return rect.Width * rect.Height;
// Need to add more if-else for new shapes
return 0;
}
}
// ✅ Good - Can extend without modifying
public abstract class Shape
{
public abstract double CalculateArea();
}
public class Circle : Shape
{
public double Radius { get; set; }
public override double CalculateArea()
=> Math.PI * Radius * Radius;
}
public class Rectangle : Shape
{
public double Width { get; set; }
public double Height { get; set; }
public override double CalculateArea()
=> Width * Height;
}
L - Liskov Substitution Principle:
Derived classes must be substitutable for their base classes.
// ✅ Good - Square can substitute Rectangle
public class Rectangle
{
public virtual double Width { get; set; }
public virtual double Height { get; set; }
public double CalculateArea() => Width * Height;
}
public class Square : Rectangle
{
private double _side;
public override double Width
{
get => _side;
set => _side = value;
}
public override double Height
{
get => _side;
set => _side = value;
}
}
I - Interface Segregation Principle:
Don't force clients to depend on interfaces they don't use.
// ❌ Bad - Fat interface
public interface IWorker
{
void Work();
void Eat();
void Sleep();
}
// ✅ Good - Segregated interfaces
public interface IWorkable
{
void Work();
}
public interface IFeedable
{
void Eat();
}
public interface ISleepable
{
void Sleep();
}
public class Human : IWorkable, IFeedable, ISleepable
{
public void Work() { }
public void Eat() { }
public void Sleep() { }
}
public class Robot : IWorkable
{
public void Work() { }
// No need to implement Eat and Sleep
}
D - Dependency Inversion Principle:
Depend on abstractions, not concretions.
// ❌ Bad - High-level module depends on low-level module
public class EmailService
{
public void SendEmail(string message) { }
}
public class NotificationService
{
private EmailService _emailService = new EmailService();
public void Notify(string message)
{
_emailService.SendEmail(message);
}
}
// ✅ Good - Both depend on abstraction
public interface IMessageSender
{
void Send(string message);
}
public class EmailSender : IMessageSender
{
public void Send(string message) { }
}
public class SmsSender : IMessageSender
{
public void Send(string message) { }
}
public class NotificationService
{
private readonly IMessageSender _messageSender;
public NotificationService(IMessageSender messageSender)
{
_messageSender = messageSender;
}
public void Notify(string message)
{
_messageSender.Send(message);
}
}
Practice Tasks
✅ Review previous code and identify SOLID violations
✅ Refactor a messy class to follow SRP
✅ Design a plugin system following OCP
✅ Create a notification system following DIP
Resources
🚀 LEVEL 4 – Advanced C# Features
🎯 Step 4.1 – Delegates, Events & Lambda Expressions
Delegates
// Define delegate type
public delegate void MessageDelegate(string message);
public class Notification
{
public void SendEmail(string message)
{
Console.WriteLine($"Email: {message}");
}
public void SendSMS(string message)
{
Console.WriteLine($"SMS: {message}");
}
}
// Usage
Notification notif = new Notification();
MessageDelegate del = notif.SendEmail;
del("Hello!"); // Email: Hello!
// Multicast delegate
del += notif.SendSMS;
del("Hello!"); // Both Email and SMS sent
Func and Action
// Func - has return value
Func<int, int, int> add = (a, b) => a + b;
int result = add(5, 3); // 8
Func<string, bool> isLong = (s) => s.Length > 10;
// Action - no return value
Action<string> print = (msg) => Console.WriteLine(msg);
print("Hello!");
Action<int, int> printSum = (a, b) => Console.WriteLine(a + b);
Lambda Expressions
// Single parameter
Func<int, int> square = x => x * x;
// Multiple parameters
Func<int, int, int> multiply = (x, y) => x * y;
// No parameters
Func<int> getRandomNumber = () => new Random().Next();
// Statement lambda
Action<int> printInfo = x =>
{
Console.WriteLine($"Number: {x}");
Console.WriteLine($"Square: {x * x}");
};
// Used with List methods
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
var evens = numbers.Where(n => n % 2 == 0).ToList();
Events
// Publisher
public class VideoEncoder
{
// Define delegate
public delegate void VideoEncodedEventHandler(object sender, EventArgs e);
// Define event based on delegate
public event VideoEncodedEventHandler VideoEncoded;
public void Encode(string title)
{
Console.WriteLine($"Encoding {title}...");
Thread.Sleep(3000);
// Raise event
OnVideoEncoded();
}
protected virtual void OnVideoEncoded()
{
VideoEncoded?.Invoke(this, EventArgs.Empty);
}
}
// Subscriber
public class MailService
{
public void OnVideoEncoded(object sender, EventArgs e)
{
Console.WriteLine("Sending email notification...");
}
}
// Usage
var encoder = new VideoEncoder();
var mailService = new MailService();
// Subscribe to event
encoder.VideoEncoded += mailService.OnVideoEncoded;
encoder.Encode("Video 1");
Modern Event Pattern with EventHandler<T>:
// Custom event args
public class VideoEventArgs : EventArgs
{
public string Title { get; set; }
}
public class VideoEncoder
{
// Using built-in EventHandler<T>
public event EventHandler<VideoEventArgs> VideoEncoded;
public void Encode(string title)
{
Console.WriteLine($"Encoding {title}...");
OnVideoEncoded(new VideoEventArgs { Title = title });
}
protected virtual void OnVideoEncoded(VideoEventArgs e)
{
VideoEncoded?.Invoke(this, e);
}
}
Practice Projects
✅ Event-driven notification system
✅ Custom calculator using delegates
✅ Progress reporting system with events
Resources
🔍 Step 4.2 – LINQ (Language Integrated Query)
LINQ Basics
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// Method syntax (preferred)
var evens = numbers.Where(n => n % 2 == 0);
var squares = [numbers.Select](<http://numbers.Select>)(n => n * n);
var first5 = numbers.Take(5);
var skip5 = numbers.Skip(5);
// Query syntax
var evens2 = from n in numbers
where n % 2 == 0
select n;
Common LINQ Methods
Filtering:
var result = numbers.Where(n => n > 5);
var first = numbers.First(n => n > 5); // Throws if not found
var firstOrDefault = numbers.FirstOrDefault(n => n > 100); // Returns default(T) if not found
var single = numbers.Single(n => n == 5); // Throws if 0 or >1 results
Projection:
var doubled = [numbers.Select](<http://numbers.Select>)(n => n * 2);
var objects = [numbers.Select](<http://numbers.Select>)(n => new { Number = n, Square = n * n });
// SelectMany - flattens nested collections
List<List<int>> nested = new List<List<int>>
{
new List<int> { 1, 2 },
new List<int> { 3, 4, 5 }
};
var flattened = nested.SelectMany(list => list); // { 1, 2, 3, 4, 5 }
Ordering:
var ascending = numbers.OrderBy(n => n);
var descending = numbers.OrderByDescending(n => n);
// Multiple ordering
var students = GetStudents();
var sorted = students
.OrderBy(s => s.Grade)
.ThenByDescending(s => s.Age);
Grouping:
var grouped = students.GroupBy(s => s.Grade);
foreach (var group in grouped)
{
Console.WriteLine($"Grade {group.Key}:");
foreach (var student in group)
{
Console.WriteLine($" {[student.Name](<http://student.Name>)}");
}
}
Aggregation:
int sum = numbers.Sum();
double average = numbers.Average();
int max = numbers.Max();
int min = numbers.Min();
int count = numbers.Count();
bool any = numbers.Any(n => n > 5);
bool all = numbers.All(n => n > 0);
// Custom aggregation
int product = numbers.Aggregate((acc, n) => acc * n);
Joining:
var students = GetStudents();
var courses = GetCourses();
// Inner join
var enrolled = students.Join(
courses,
student => student.CourseId,
course => [course.Id](<http://course.Id>),
(student, course) => new
{
StudentName = [student.Name](<http://student.Name>),
CourseName = course.Title
});
// Group join
var grouped = courses.GroupJoin(
students,
course => [course.Id](<http://course.Id>),
student => student.CourseId,
(course, studentGroup) => new
{
Course = course.Title,
Students = studentGroup
});
Set Operations:
var list1 = new[] { 1, 2, 3, 4 };
var list2 = new[] { 3, 4, 5, 6 };
var distinct = list1.Concat(list2).Distinct(); // { 1, 2, 3, 4, 5, 6 }
var union = list1.Union(list2); // { 1, 2, 3, 4, 5, 6 }
var intersect = list1.Intersect(list2); // { 3, 4 }
var except = list1.Except(list2); // { 1, 2 }
Partitioning:
var first3 = numbers.Take(3);
var last3 = numbers.TakeLast(3);
var skip3 = numbers.Skip(3);
var takeWhile = numbers.TakeWhile(n => n < 5);
var skipWhile = numbers.SkipWhile(n => n < 5);
Complex LINQ Example
public class Product
{
public string Name { get; set; }
public string Category { get; set; }
public decimal Price { get; set; }
public int Stock { get; set; }
}
var products = GetProducts();
// Complex query
var result = products
.Where(p => p.Stock > 0)
.Where(p => p.Price < 100)
.GroupBy(p => p.Category)
.Select(g => new
{
Category = g.Key,
Count = g.Count(),
AveragePrice = g.Average(p => p.Price),
Products = g.OrderBy(p => p.Price).ToList()
})
.OrderByDescending(x => x.Count);
foreach (var category in result)
{
Console.WriteLine($"{category.Category}: {category.Count} products, " +
$"avg price: ${category.AveragePrice:F2}");
}
Practice Projects
✅ Product filtering and reporting system
✅ Student grade analyzer with LINQ
✅ Log file analyzer using LINQ
✅ Data transformation pipelines
Resources
⚠️ Step 4.3 – Exception Handling & Debugging
Try-Catch-Finally
try
{
// Code that might throw exception
int result = 10 / 0;
}
catch (DivideByZeroException ex)
{
// Handle specific exception
Console.WriteLine($"Error: {ex.Message}");
}
catch (Exception ex)
{
// Handle any other exception
Console.WriteLine($"Unexpected error: {ex.Message}");
throw; // Re-throw to preserve stack trace
}
finally
{
// Always executes (cleanup code)
Console.WriteLine("Cleanup completed");
}
Common Exception Types
// ArgumentException - invalid argument
public void SetAge(int age)
{
if (age < 0 || age > 150)
throw new ArgumentException("Age must be between 0 and 150", nameof(age));
}
// ArgumentNullException - null argument
public void ProcessName(string name)
{
if (name == null)
throw new ArgumentNullException(nameof(name));
}
// InvalidOperationException - invalid state
public void Withdraw(decimal amount)
{
if (_balance < amount)
throw new InvalidOperationException("Insufficient funds");
}
// NotImplementedException - placeholder
public void FutureFeature()
{
throw new NotImplementedException("This feature is coming soon");
}
Custom Exceptions
// Define custom exception
public class InsufficientFundsException : Exception
{
public decimal Balance { get; }
public decimal RequestedAmount { get; }
public InsufficientFundsException(decimal balance, decimal requestedAmount)
: base($"Insufficient funds. Balance: {balance}, Requested: {requestedAmount}")
{
Balance = balance;
RequestedAmount = requestedAmount;
}
}
// Usage
public void Withdraw(decimal amount)
{
if (_balance < amount)
throw new InsufficientFundsException(_balance, amount);
_balance -= amount;
}
// Catch custom exception
try
{
account.Withdraw(1000);
}
catch (InsufficientFundsException ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine($"Shortfall: {ex.RequestedAmount - ex.Balance}");
}
Exception Best Practices
// ✅ Good - Specific exceptions
try
{
var data = File.ReadAllText("config.json");
}
catch (FileNotFoundException ex)
{
Console.WriteLine("Config file not found");
}
catch (UnauthorizedAccessException ex)
{
Console.WriteLine("Permission denied");
}
// ❌ Bad - Catching all exceptions
try
{
// Code
}
catch (Exception)
{
// Hiding all errors
}
// ✅ Good - Re-throwing with throw;
catch (Exception ex)
{
LogError(ex);
throw; // Preserves stack trace
}
// ❌ Bad - Re-throwing with throw ex;
catch (Exception ex)
{
LogError(ex);
throw ex; // Loses original stack trace
}
Exception Filters (C# 6+)
try
{
ProcessData();
}
catch (HttpException ex) when (ex.StatusCode == 404)
{
Console.WriteLine("Resource not found");
}
catch (HttpException ex) when (ex.StatusCode == 500)
{
Console.WriteLine("Server error");
}
Debugging in Visual Studio
Breakpoints:
Click in left margin to set breakpoint
F9 to toggle breakpoint
Conditional breakpoints (right-click breakpoint)
Debug Controls:
F5 - Start debugging
F10 - Step over (execute current line)
F11 - Step into (enter method)
Shift+F11 - Step out (exit method)
F5 - Continue to next breakpoint
Watch & Inspect:
Hover over variables to see values
Watch window - monitor specific variables
Locals window - see all local variables
Call stack - see method call hierarchy
Immediate window - execute code during debugging
Debug Tips:
// Use Debug class for output
Debug.WriteLine("Debug message");
Debug.Assert(value > 0, "Value must be positive");
// Conditional compilation
#if DEBUG
Console.WriteLine("Debug mode");
#endif
// Debugger attributes
[DebuggerDisplay("Name = {Name}, Age = {Age}")]
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
Practice Tasks
✅ Add exception handling to all file operations
✅ Create custom exceptions for business logic
✅ Practice debugging with breakpoints and watches
✅ Use try-catch in database operations
Resources
📁 Step 4.4 – File & Streams
File Operations
using [System.IO](<http://System.IO>);
// Check if file exists
bool exists = File.Exists("data.txt");
// Read entire file
string content = File.ReadAllText("data.txt");
string[] lines = File.ReadAllLines("data.txt");
byte[] bytes = File.ReadAllBytes("data.bin");
// Write to file
File.WriteAllText("output.txt", "Hello World");
File.WriteAllLines("output.txt", new[] { "Line 1", "Line 2" });
File.WriteAllBytes("output.bin", bytes);
// Append to file
File.AppendAllText("log.txt", "New log entry\\n");
File.AppendAllLines("log.txt", new[] { "Entry 1", "Entry 2" });
// Copy, move, delete
File.Copy("source.txt", "destination.txt");
File.Move("old.txt", "new.txt");
File.Delete("temp.txt");
// Get file info
FileInfo fileInfo = new FileInfo("data.txt");
long size = fileInfo.Length;
DateTime created = fileInfo.CreationTime;
DateTime modified = fileInfo.LastWriteTime;
bool isReadOnly = fileInfo.IsReadOnly;
Directory Operations
// Check if directory exists
bool exists = Directory.Exists("MyFolder");
// Create directory
Directory.CreateDirectory("MyFolder/SubFolder");
// Get files and directories
string[] files = Directory.GetFiles("MyFolder");
string[] directories = Directory.GetDirectories("MyFolder");
string[] allFiles = Directory.GetFiles("MyFolder", "*.*", SearchOption.AllDirectories);
// Search patterns
string[] txtFiles = Directory.GetFiles("MyFolder", "*.txt");
string[] csvFiles = Directory.GetFiles("MyFolder", "*.csv");
// Move and delete
Directory.Move("OldFolder", "NewFolder");
Directory.Delete("TempFolder", recursive: true);
// Get directory info
DirectoryInfo dirInfo = new DirectoryInfo("MyFolder");
FileInfo[] files = dirInfo.GetFiles();
DirectoryInfo[] subdirs = dirInfo.GetDirectories();
Streams - StreamReader & StreamWriter
// Write with StreamWriter
using (StreamWriter writer = new StreamWriter("output.txt"))
{
writer.WriteLine("Line 1");
writer.WriteLine("Line 2");
writer.Write("No newline");
}
// Read with StreamReader
using (StreamReader reader = new StreamReader("input.txt"))
{
string line;
while ((line = reader.ReadLine()) != null)
{
Console.WriteLine(line);
}
}
// Or read all
using (StreamReader reader = new StreamReader("input.txt"))
{
string content = reader.ReadToEnd();
Console.WriteLine(content);
}
FileStream
// Write binary data
using (FileStream fs = new FileStream("data.bin", FileMode.Create))
{
byte[] data = { 1, 2, 3, 4, 5 };
fs.Write(data, 0, data.Length);
}
// Read binary data
using (FileStream fs = new FileStream("data.bin", [FileMode.Open](<http://FileMode.Open>)))
{
byte[] buffer = new byte[fs.Length];
[fs.Read](<http://fs.Read>)(buffer, 0, buffer.Length);
}
// FileMode options:
// - Create: Create new (overwrites existing)
// - CreateNew: Create new (fails if exists)
// - Open: Open existing (fails if not exists)
// - OpenOrCreate: Open or create
// - Append: Open and seek to end
// - Truncate: Open and clear content
Using Statement & IDisposable
// Traditional using
using (var reader = new StreamReader("file.txt"))
{
// Use reader
} // Automatically disposes
// C# 8+ using declaration
using var reader = new StreamReader("file.txt");
// Use reader
// Automatically disposes at end of scope
// Multiple resources
using (var reader = new StreamReader("input.txt"))
using (var writer = new StreamWriter("output.txt"))
{
string line;
while ((line = reader.ReadLine()) != null)
{
writer.WriteLine(line.ToUpper());
}
}
Path Helper
// Combine paths safely
string path = Path.Combine("folder", "subfolder", "file.txt");
// Get parts of path
string directory = Path.GetDirectoryName(path);
string filename = Path.GetFileName(path);
string filenameNoExt = Path.GetFileNameWithoutExtension(path);
string extension = Path.GetExtension(path);
// Temporary files
string tempPath = Path.GetTempPath();
string tempFile = Path.GetTempFileName();
// Special folders
string desktop = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
string documents = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
Working with JSON
using System.Text.Json;
// Object to serialize
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
// Serialize to JSON
var person = new Person { Name = "John", Age = 30 };
string json = JsonSerializer.Serialize(person);
File.WriteAllText("person.json", json);
// Serialize with formatting
var options = new JsonSerializerOptions { WriteIndented = true };
string jsonFormatted = JsonSerializer.Serialize(person, options);
// Deserialize from JSON
string json = File.ReadAllText("person.json");
Person person = JsonSerializer.Deserialize<Person>(json);
Practice Projects
✅ Note-taking console app (save/load notes from files)
✅ Log file analyzer
✅ File backup utility
✅ Configuration manager (JSON settings)
✅ CSV reader/writer
Resources
⚡ Step 4.5 – Async Programming & Multithreading
Why Async?
Keep UI responsive
Efficient I/O operations (file, network, database)
Better resource utilization
Don't block threads while waiting
Async/Await Basics
// Async method returns Task or Task<T>
public async Task<string> DownloadDataAsync(string url)
{
using (HttpClient client = new HttpClient())
{
// await suspends execution until operation completes
string data = await client.GetStringAsync(url);
return data;
}
}
// Calling async method
public async Task ProcessDataAsync()
{
string data = await DownloadDataAsync("<https://api.example.com>");
Console.WriteLine(data);
}
// Async void - only for event handlers
private async void Button_Click(object sender, EventArgs e)
{
await ProcessDataAsync();
}
Task Parallel Library
// Create and run tasks
Task task1 = [Task.Run](<http://Task.Run>)(() =>
{
Console.WriteLine("Task 1");
});
Task<int> task2 = [Task.Run](<http://Task.Run>)(() =>
{
return 42;
});
// Wait for task
task1.Wait();
int result = task2.Result; // Blocks until complete
// Async file operations
string content = await File.ReadAllTextAsync("file.txt");
await File.WriteAllTextAsync("file.txt", "content");
// Delay
await Task.Delay(1000); // Wait 1 second (non-blocking)
Multiple Async Operations
// Run tasks in parallel
List<string> urls = GetUrls();
List<Task<string>> tasks = new List<Task<string>>();
foreach (string url in urls)
{
tasks.Add(DownloadDataAsync(url));
}
// Wait for all
string[] results = await Task.WhenAll(tasks);
// Wait for any
Task<string> completedTask = await Task.WhenAny(tasks);
string result = await completedTask;
// Parallel processing with results
var results = await Task.WhenAll(
DownloadAsync("url1"),
DownloadAsync("url2"),
DownloadAsync("url3")
);
ConfigureAwait
// In library code, use ConfigureAwait(false)
public async Task<string> GetDataAsync()
{
using (HttpClient client = new HttpClient())
{
// Don't capture synchronization context
string data = await client.GetStringAsync(url)
.ConfigureAwait(false);
return data;
}
}
// In UI code, don't use ConfigureAwait (or use ConfigureAwait(true))
private async void Button_Click(object sender, EventArgs e)
{
string data = await GetDataAsync();
textBox.Text = data; // Must run on UI thread
}
Threading Basics
// Create thread (old way)
Thread thread = new Thread(() =>
{
Console.WriteLine("Thread running");
});
thread.Start();
thread.Join(); // Wait for completion
// Thread pool (better)
ThreadPool.QueueUserWorkItem(state =>
{
Console.WriteLine("Thread pool work");
});
// Task (best for most scenarios)
await [Task.Run](<http://Task.Run>)(() =>
{
// CPU-intensive work
ComputeSomething();
});
Cancellation
public async Task DownloadWithCancellationAsync(string url,
CancellationToken cancellationToken)
{
using (HttpClient client = new HttpClient())
{
string data = await client.GetStringAsync(url, cancellationToken);
// Check for cancellation
cancellationToken.ThrowIfCancellationRequeste
d();
return data;
}
}
// Usage
CancellationTokenSource cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(5)); // Cancel after 5 seconds
try
{
string data = await DownloadWithCancellationAsync(url, cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("Operation was cancelled");
}
Practice Projects
✅ URL downloader with multiple parallel downloads
✅ File processor with async I/O
✅ Web scraper with cancellation support
Resources
🔬 Step 4.6 – Reflection, Indexers & Extensions
Indexers
public class WeeklySchedule
{
private string[] days = new string[7];
// Indexer
public string this[int index]
{
get { return days[index]; }
set { days[index] = value; }
}
// String indexer
public string this[string dayName]
{
get
{
return dayName switch
{
"Monday" => days[0],
"Tuesday" => days[1],
_ => throw new ArgumentException("Invalid day")
};
}
set
{
switch (dayName)
{
case "Monday": days[0] = value; break;
case "Tuesday": days[1] = value; break;
// ... etc
}
}
}
}
// Usage
var schedule = new WeeklySchedule();
schedule[0] = "Meeting at 9 AM";
schedule["Monday"] = "Meeting at 9 AM";
Reflection
using System.Reflection;
// Get type information
Type type = typeof(Person);
// Or from instance
Person person = new Person();
Type type2 = person.GetType();
// Get properties
PropertyInfo[] properties = type.GetProperties();
foreach (var prop in properties)
{
Console.WriteLine($"{[prop.Name](<http://prop.Name>)}: {prop.PropertyType}");
}
// Get and set property values
object instance = Activator.CreateInstance(type);
PropertyInfo nameProp = type.GetProperty("Name");
nameProp.SetValue(instance, "John");
object value = nameProp.GetValue(instance);
// Get methods
MethodInfo[] methods = type.GetMethods();
MethodInfo method = type.GetMethod("MethodName");
object result = method.Invoke(instance, new object[] { param1, param2 });
// Get custom attributes
var attributes = type.GetCustomAttributes();
Extension Methods
// Must be in static class
public static class StringExtensions
{
// First parameter with 'this' keyword
public static bool IsValidEmail(this string email)
{
return email.Contains("@") && email.Contains(".");
}
public static string Truncate(this string str, int maxLength)
{
if (string.IsNullOrEmpty(str) || str.Length <= maxLength)
return str;
return str.Substring(0, maxLength) + "...";
}
public static int WordCount(this string str)
{
return str.Split(new[] { ' ', '\\t', '\\n' },
StringSplitOptions.RemoveEmptyEntries).Length;
}
}
// Usage
string email = "[user@example.com](<mailto:user@example.com>)";
bool valid = email.IsValidEmail(); // Called as if it's a method of string
string text = "This is a very long text";
string truncated = text.Truncate(10); // "This is a ..."
Expression Trees (Basic)
using System.Linq.Expressions;
// Create expression: x => x * x
ParameterExpression param = Expression.Parameter(typeof(int), "x");
BinaryExpression multiply = Expression.Multiply(param, param);
Expression<Func<int, int>> lambda = Expression.Lambda<Func<int, int>>(multiply, param);
// Compile and execute
Func<int, int> square = lambda.Compile();
int result = square(5); // 25
Practice Projects
✅ Utility extension methods for common types
✅ Simple plugin loader using reflection
✅ Object mapper using reflection
Resources
🗄️ LEVEL 5 – Data Access & Databases
🗃️ Step 5.1 – SQL Server Basics
Install SQL Server
Options:
SQL Server Express (free, full-featured)
SQL Server LocalDB (lightweight, developer-focused)
SQL Server Developer Edition (free for development)
Management Tools:
SQL Server Management Studio (SSMS)
Azure Data Studio (cross-platform)
Database Fundamentals
Key Concepts:
Database: Container for tables and other objects
Table: Stores data in rows and columns
Primary Key: Unique identifier for each row
Foreign Key: References primary key in another table
Index: Improves query performance
Basic SQL Commands
CREATE TABLE:
CREATE TABLE Students (
StudentId INT PRIMARY KEY IDENTITY(1,1),
FirstName NVARCHAR(50) NOT NULL,
LastName NVARCHAR(50) NOT NULL,
Email NVARCHAR(100) UNIQUE,
BirthDate DATE,
EnrollmentDate DATETIME DEFAULT GETDATE()
);
CREATE TABLE Courses (
CourseId INT PRIMARY KEY IDENTITY(1,1),
CourseName NVARCHAR(100) NOT NULL,
Credits INT
);
CREATE TABLE Enrollments (
EnrollmentId INT PRIMARY KEY IDENTITY(1,1),
StudentId INT FOREIGN KEY REFERENCES Students(StudentId),
CourseId INT FOREIGN KEY REFERENCES Courses(CourseId),
Grade DECIMAL(3,2)
);
INSERT:
INSERT INTO Students (FirstName, LastName, Email, BirthDate)
VALUES ('John', 'Doe', '[john@example.com](<mailto:john@example.com>)', '2000-01-15');
-- Multiple rows
INSERT INTO Students (FirstName, LastName, Email)
VALUES
('Alice', 'Smith', '[alice@example.com](<mailto:alice@example.com>)'),
('Bob', 'Johnson', '[bob@example.com](<mailto:bob@example.com>)');
SELECT:
-- Select all
SELECT * FROM Students;
-- Select specific columns
SELECT FirstName, LastName, Email FROM Students;
-- With WHERE clause
SELECT * FROM Students WHERE EnrollmentDate > '2024-01-01';
-- With ORDER BY
SELECT * FROM Students ORDER BY LastName, FirstName;
-- Top N
SELECT TOP 10 * FROM Students ORDER BY EnrollmentDate DESC;
UPDATE:
UPDATE Students
SET Email = '[newemail@example.com](<mailto:newemail@example.com>)'
WHERE StudentId = 1;
-- Update multiple columns
UPDATE Students
SET FirstName = 'Jonathan', LastName = 'Doe'
WHERE StudentId = 1;
DELETE:
DELETE FROM Students WHERE StudentId = 5;
-- Delete all (careful!)
DELETE FROM Students;
JOINS:
-- Inner join
SELECT s.FirstName, s.LastName, c.CourseName, e.Grade
FROM Students s
INNER JOIN Enrollments e ON s.StudentId = e.StudentId
INNER JOIN Courses c ON e.CourseId = c.CourseId;
-- Left join
SELECT s.FirstName, s.LastName, c.CourseName
FROM Students s
LEFT JOIN Enrollments e ON s.StudentId = e.StudentId
LEFT JOIN Courses c ON e.CourseId = c.CourseId;
Aggregate Functions:
SELECT COUNT(*) FROM Students;
SELECT AVG(Grade) FROM Enrollments;
SELECT MAX(Grade), MIN(Grade) FROM Enrollments;
-- Group by
SELECT CourseId, AVG(Grade) as AverageGrade
FROM Enrollments
GROUP BY CourseId
HAVING AVG(Grade) > 75;
Practice Tasks
✅ Design a simple database schema (Students, Courses, Enrollments)
✅ Write CRUD queries for all tables
✅ Practice joins and aggregate queries
🔌 Step 5.2 – ADO.NET
Connection String
// SQL Server connection string
string connectionString =
"Server=[localhost](<http://localhost>);Database=SchoolDB;Integrated Security=true;";
// Or with username/password
string connectionString2 =
"Server=[localhost](<http://localhost>);Database=SchoolDB;User Id=sa;Password=yourpassword;";
Basic ADO.NET Operations
Opening Connection:
using [System.Data](<http://System.Data>).SqlClient;
using (SqlConnection connection = new SqlConnection(connectionString))
{
[connection.Open](<http://connection.Open>)();
Console.WriteLine("Connected!");
// Work with database
} // Connection automatically closed
Execute Non-Query (INSERT, UPDATE, DELETE):
using (SqlConnection connection = new SqlConnection(connectionString))
{
[connection.Open](<http://connection.Open>)();
string sql = "INSERT INTO Students (FirstName, LastName, Email) " +
"VALUES (@FirstName, @LastName, @Email)";
using (SqlCommand command = new SqlCommand(sql, connection))
{
// Use parameters to prevent SQL injection
command.Parameters.AddWithValue("@FirstName", "John");
command.Parameters.AddWithValue("@LastName", "Doe");
command.Parameters.AddWithValue("@Email", "[john@example.com](<mailto:john@example.com>)");
int rowsAffected = command.ExecuteNonQuery();
Console.WriteLine($"{rowsAffected} row(s) inserted");
}
}
Execute Scalar (Get Single Value):
using (SqlConnection connection = new SqlConnection(connectionString))
{
[connection.Open](<http://connection.Open>)();
string sql = "SELECT COUNT(*) FROM Students";
using (SqlCommand command = new SqlCommand(sql, connection))
{
int count = (int)command.ExecuteScalar();
Console.WriteLine($"Total students: {count}");
}
}
Execute Reader (Get Multiple Rows):
using (SqlConnection connection = new SqlConnection(connectionString))
{
[connection.Open](<http://connection.Open>)();
string sql = "SELECT StudentId, FirstName, LastName, Email FROM Students";
using (SqlCommand command = new SqlCommand(sql, connection))
using (SqlDataReader reader = command.ExecuteReader())
{
while ([reader.Read](<http://reader.Read>)())
{
int id = reader.GetInt32(0);
string firstName = reader.GetString(1);
string lastName = reader.GetString(2);
string email = reader.GetString(3);
Console.WriteLine($"{id}: {firstName} {lastName} ({email})");
}
}
}
Safe Parameter Usage:
// ❌ Bad - SQL Injection vulnerable
string sql = $"SELECT * FROM Users WHERE Username = '{username}'";
// ✅ Good - Parameterized query
string sql = "SELECT * FROM Users WHERE Username = @Username";
command.Parameters.AddWithValue("@Username", username);
Transactions:
using (SqlConnection connection = new SqlConnection(connectionString))
{
[connection.Open](<http://connection.Open>)();
SqlTransaction transaction = connection.BeginTransaction();
try
{
// First operation
string sql1 = "INSERT INTO Accounts (Name, Balance) VALUES (@Name, @Balance)";
using (SqlCommand cmd = new SqlCommand(sql1, connection, transaction))
{
cmd.Parameters.AddWithValue("@Name", "Account1");
cmd.Parameters.AddWithValue("@Balance", 1000);
cmd.ExecuteNonQuery();
}
// Second operation
string sql2 = "UPDATE Accounts SET Balance = Balance - 100 WHERE Name = 'Account2'";
using (SqlCommand cmd = new SqlCommand(sql2, connection, transaction))
{
cmd.ExecuteNonQuery();
}
// Commit if all successful
transaction.Commit();
Console.WriteLine("Transaction completed");
}
catch (Exception ex)
{
// Rollback on error
transaction.Rollback();
Console.WriteLine($"Transaction failed: {ex.Message}");
}
}
Practice Projects
✅ CRUD console app for Students using ADO.NET
✅ Simple inventory management system
✅ Banking transaction simulator with proper transactions
Resources
🚀 Step 5.3 – Entity Framework Core
What is EF Core?
Entity Framework Core is an Object-Relational Mapper (ORM) that:
Maps C# classes to database tables
Generates SQL queries automatically
Tracks changes to objects
Handles relationships between entities
Installation
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package [Microsoft.EntityFrameworkCore.Tools](<http://Microsoft.EntityFrameworkCore.Tools>)
Define Entity Classes
public class Student
{
public int StudentId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public DateTime EnrollmentDate { get; set; }
// Navigation property
public List<Enrollment> Enrollments { get; set; }
}
public class Course
{
public int CourseId { get; set; }
public string CourseName { get; set; }
public int Credits { get; set; }
public List<Enrollment> Enrollments { get; set; }
}
public class Enrollment
{
public int EnrollmentId { get; set; }
public int StudentId { get; set; }
public int CourseId { get; set; }
public decimal? Grade { get; set; }
// Navigation properties
public Student Student { get; set; }
public Course Course { get; set; }
}
DbContext
using Microsoft.EntityFrameworkCore;
public class SchoolContext : DbContext
{
public DbSet<Student> Students { get; set; }
public DbSet<Course> Courses { get; set; }
public DbSet<Enrollment> Enrollments { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(
"Server=[localhost](<http://localhost>);Database=SchoolDB;Integrated Security=true;");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Fluent API configuration
modelBuilder.Entity<Student>()
.Property(s => [s.Email](<http://s.Email>))
.IsRequired()
.HasMaxLength(100);
modelBuilder.Entity<Enrollment>()
.HasOne(e => e.Student)
.WithMany(s => s.Enrollments)
.HasForeignKey(e => e.StudentId);
modelBuilder.Entity<Enrollment>()
.HasOne(e => e.Course)
.WithMany(c => c.Enrollments)
.HasForeignKey(e => e.CourseId);
}
}
Data Annotations
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
public class Student
{
[Key]
public int StudentId { get; set; }
[Required]
[MaxLength(50)]
public string FirstName { get; set; }
[Required]
[MaxLength(50)]
public string LastName { get; set; }
[EmailAddress]
[MaxLength(100)]
public string Email { get; set; }
[Column(TypeName = "date")]
public DateTime BirthDate { get; set; }
}
Migrations
# Create initial migration
dotnet ef migrations add InitialCreate
# Apply migration to database
dotnet ef database update
# Add new migration after model changes
dotnet ef migrations add AddPhoneNumber
# Remove last migration (if not applied)
dotnet ef migrations remove
# Generate SQL script
dotnet ef migrations script
CRUD Operations with EF Core
Create:
using (var context = new SchoolContext())
{
var student = new Student
{
FirstName = "John",
LastName = "Doe",
Email = "[john@example.com](<mailto:john@example.com>)",
EnrollmentDate = [DateTime.Now](<http://DateTime.Now>)
};
context.Students.Add(student);
context.SaveChanges();
Console.WriteLine($"Student added with ID: {student.StudentId}");
}
Read:
using (var context = new SchoolContext())
{
// Get all
var allStudents = context.Students.ToList();
// Get by ID
var student = context.Students.Find(1);
// Query with LINQ
var filteredStudents = context.Students
.Where(s => s.EnrollmentDate > [DateTime.Now](<http://DateTime.Now>).AddYears(-1))
.OrderBy(s => s.LastName)
.ToList();
// First or default
var firstStudent = context.Students
.FirstOrDefault(s => [s.Email](<http://s.Email>) == "[john@example.com](<mailto:john@example.com>)");
}
Update:
using (var context = new SchoolContext())
{
var student = context.Students.Find(1);
if (student != null)
{
[student.Email](<http://student.Email>) = "[newemail@example.com](<mailto:newemail@example.com>)";
context.SaveChanges();
}
}
Delete:
using (var context = new SchoolContext())
{
var student = context.Students.Find(1);
if (student != null)
{
context.Students.Remove(student);
context.SaveChanges();
}
}
Loading Related Data
Eager Loading:
// Load students with their enrollments
var students = context.Students
.Include(s => s.Enrollments)
.ThenInclude(e => e.Course)
.ToList();
Explicit Loading:
var student = context.Students.Find(1);
context.Entry(student)
.Collection(s => s.Enrollments)
.Load();
Lazy Loading:
// Install: Microsoft.EntityFrameworkCore.Proxies
// In OnConfiguring:
optionsBuilder.UseLazyLoadingProxies();
// Make navigation properties virtual
public virtual List<Enrollment> Enrollments { get; set; }
Practice Projects
✅ Student management system with EF Core
✅ Blog platform (Posts, Comments, Users)
✅ E-commerce product catalog
Resources
🎓 LEVEL 6 – Professional Development Skills
✅ Step 6.1 – Unit Testing
Why Unit Testing?
Catch bugs early
Enable refactoring with confidence
Document code behavior
Improve code design
Setup xUnit
# Create test project
dotnet new xunit -n MyProject.Tests
# Add reference to main project
dotnet add reference ../MyProject/MyProject.csproj
# Install packages
dotnet add package Moq # For mocking
Basic Test Structure
using Xunit;
public class CalculatorTests
{
[Fact]
public void Add_TwoPositiveNumbers_ReturnsSum()
{
// Arrange
var calculator = new Calculator();
// Act
int result = calculator.Add(5, 3);
// Assert
Assert.Equal(8, result);
}
[Theory]
[InlineData(2, 3, 5)]
[InlineData(0, 0, 0)]
[InlineData(-2, 3, 1)]
public void Add_VariousInputs_ReturnsCorrectSum(int a, int b, int expected)
{
var calculator = new Calculator();
int result = calculator.Add(a, b);
Assert.Equal(expected, result);
}
}
Common Assertions
// Equality
Assert.Equal(expected, actual);
Assert.NotEqual(expected, actual);
// Boolean
Assert.True(condition);
Assert.False(condition);
// Null checks
Assert.Null(obj);
Assert.NotNull(obj);
// Exceptions
Assert.Throws<ArgumentException>(() => method());
// Collections
Assert.Empty(collection);
Assert.NotEmpty(collection);
Assert.Contains(item, collection);
Assert.DoesNotContain(item, collection);
// Strings
Assert.StartsWith("prefix", text);
Assert.EndsWith("suffix", text);
Assert.Contains("substring", text);
// Ranges
Assert.InRange(actual, low, high);
Mocking with Moq
using Moq;
public interface IEmailService
{
void SendEmail(string to, string subject, string body);
bool IsEmailValid(string email);
}
public class UserService
{
private readonly IEmailService _emailService;
public UserService(IEmailService emailService)
{
_emailService = emailService;
}
public bool RegisterUser(string email)
{
if (!_emailService.IsEmailValid(email))
return false;
_emailService.SendEmail(email, "Welcome", "Thanks for registering");
return true;
}
}
// Test with mock
public class UserServiceTests
{
[Fact]
public void RegisterUser_ValidEmail_SendsWelcomeEmail()
{
// Arrange
var mockEmailService = new Mock<IEmailService>();
mockEmailService.Setup(e => e.IsEmailValid(It.IsAny<string>()))
.Returns(true);
var userService = new UserService(mockEmailService.Object);
// Act
bool result = userService.RegisterUser("[test@example.com](<mailto:test@example.com>)");
// Assert
Assert.True(result);
mockEmailService.Verify(e =>
e.SendEmail("[test@example.com](<mailto:test@example.com>)", "Welcome", "Thanks for registering"),
Times.Once);
}
}
Test-Driven Development (TDD)
Red-Green-Refactor Cycle:
Red: Write a failing test
Green: Write minimal code to pass
Refactor: Improve code while keeping tests green
Practice Tasks
✅ Write tests for calculator class
✅ Test LINQ queries and data transformations
✅ Mock dependencies in business logic
✅ Achieve >80% code coverage on a project
🏗️ Step 6.2 – Design Patterns
Creational Patterns
Singleton:
public class Logger
{
private static Logger _instance;
private static readonly object _lock = new object();
private Logger() { }
public static Logger Instance
{
get
{
if (_instance == null)
{
lock (_lock)
{
if (_instance == null)
_instance = new Logger();
}
}
return _instance;
}
}
public void Log(string message)
{
Console.WriteLine($"[{[DateTime.Now](<http://DateTime.Now>)}] {message}");
}
}
// Usage
Logger.Instance.Log("Application started");
Factory:
public interface IPayment
{
void ProcessPayment(decimal amount);
}
public class CreditCardPayment : IPayment
{
public void ProcessPayment(decimal amount)
{
Console.WriteLine($"Processing ${amount} via Credit Card");
}
}
public class PayPalPayment : IPayment
{
public void ProcessPayment(decimal amount)
{
Console.WriteLine($"Processing ${amount} via PayPal");
}
}
public class PaymentFactory
{
public static IPayment CreatePayment(string type)
{
return type.ToLower() switch
{
"creditcard" => new CreditCardPayment(),
"paypal" => new PayPalPayment(),
_ => throw new ArgumentException("Invalid payment type")
};
}
}
// Usage
IPayment payment = PaymentFactory.CreatePayment("creditcard");
payment.ProcessPayment(100);
Builder:
public class Pizza
{
public string Size { get; set; }
public bool Cheese { get; set; }
public bool Pepperoni { get; set; }
public bool Mushrooms { get; set; }
}
public class PizzaBuilder
{
private Pizza _pizza = new Pizza();
public PizzaBuilder SetSize(string size)
{
_pizza.Size = size;
return this;
}
public PizzaBuilder AddCheese()
{
_pizza.Cheese = true;
return this;
}
public PizzaBuilder AddPepperoni()
{
_pizza.Pepperoni = true;
return this;
}
public PizzaBuilder AddMushrooms()
{
_pizza.Mushrooms = true;
return this;
}
public Pizza Build() => _pizza;
}
// Usage
var pizza = new PizzaBuilder()
.SetSize("Large")
.AddCheese()
.AddPepperoni()
.Build();
Structural Patterns
Adapter:
// Legacy class we can't modify
public class LegacyPrinter
{
public void PrintOldWay(string text)
{
Console.WriteLine($"OLD: {text}");
}
}
// New interface we want to use
public interface IPrinter
{
void Print(string document);
}
// Adapter
public class PrinterAdapter : IPrinter
{
private LegacyPrinter _legacyPrinter;
public PrinterAdapter(LegacyPrinter legacyPrinter)
{
_legacyPrinter = legacyPrinter;
}
public void Print(string document)
{
_legacyPrinter.PrintOldWay(document);
}
}
Decorator:
public interface ICoffee
{
string GetDescription();
decimal GetCost();
}
public class SimpleCoffee : ICoffee
{
public string GetDescription() => "Simple coffee";
public decimal GetCost() => 2.00m;
}
public abstract class CoffeeDecorator : ICoffee
{
protected ICoffee _coffee;
public CoffeeDecorator(ICoffee coffee)
{
_coffee = coffee;
}
public virtual string GetDescription() => _coffee.GetDescription();
public virtual decimal GetCost() => _coffee.GetCost();
}
public class MilkDecorator : CoffeeDecorator
{
public MilkDecorator(ICoffee coffee) : base(coffee) { }
public override string GetDescription() => _coffee.GetDescription() + ", Milk";
public override decimal GetCost() => _coffee.GetCost() + 0.50m;
}
// Usage
ICoffee coffee = new SimpleCoffee();
coffee = new MilkDecorator(coffee);
Console.WriteLine($"{coffee.GetDescription()}: ${coffee.GetCost()}");
Behavioral Patterns
Strategy:
public interface IDiscountStrategy
{
decimal ApplyDiscount(decimal price);
}
public class NoDiscount : IDiscountStrategy
{
public decimal ApplyDiscount(decimal price) => price;
}
public class PercentageDiscount : IDiscountStrategy
{
private decimal _percentage;
public PercentageDiscount(decimal percentage)
{
_percentage = percentage;
}
public decimal ApplyDiscount(decimal price)
{
return price * (1 - _percentage / 100);
}
}
public class ShoppingCart
{
private IDiscountStrategy _discountStrategy;
public ShoppingCart(IDiscountStrategy discountStrategy)
{
_discountStrategy = discountStrategy;
}
public decimal CalculateTotal(decimal price)
{
return _discountStrategy.ApplyDiscount(price);
}
}
Observer:
public interface IObserver
{
void Update(string message);
}
public interface ISubject
{
void Attach(IObserver observer);
void Detach(IObserver observer);
void Notify(string message);
}
public class NewsAgency : ISubject
{
private List<IObserver> _observers = new List<IObserver>();
public void Attach(IObserver observer)
{
_observers.Add(observer);
}
public void Detach(IObserver observer)
{
_observers.Remove(observer);
}
public void Notify(string message)
{
foreach (var observer in _observers)
{
observer.Update(message);
}
}
public void PublishNews(string news)
{
Console.WriteLine($"Breaking news: {news}");
Notify(news);
}
}
public class NewsSubscriber : IObserver
{
private string _name;
public NewsSubscriber(string name)
{
_name = name;
}
public void Update(string message)
{
Console.WriteLine($"{_name} received: {message}");
}
}
Practice Projects
✅ Implement discount system using Strategy pattern
✅ Create notification system with Observer pattern
✅ Build document export system with Factory pattern
🏛️ Step 6.3 – Clean Architecture & Dependency Injection
Layered Architecture
📁 MyApp.Domain // Core business logic
├── Entities
├── Interfaces
└── Services
📁 MyApp.Application // Use cases
├── DTOs
├── Commands
└── Queries
📁 MyApp.Infrastructure // External concerns
├── Data (EF Core)
├── Email
└── FileStorage
📁 MyApp.API/UI // Presentation
├── Controllers
└── Views
Dependency Injection in .NET
Configure Services:
using Microsoft.Extensions.DependencyInjection;
var services = new ServiceCollection();
// Register services
services.AddTransient<IEmailService, EmailService>(); // New instance each time
services.AddScoped<IRepository, Repository>(); // One per request
services.AddSingleton<ILogger, Logger>(); // One for entire app
// Build service provider
var serviceProvider = services.BuildServiceProvider();
// Resolve service
var emailService = serviceProvider.GetService<IEmailService>();
Constructor Injection:
public class UserService
{
private readonly IUserRepository _repository;
private readonly IEmailService _emailService;
private readonly ILogger _logger;
public UserService(
IUserRepository repository,
IEmailService emailService,
ILogger logger)
{
_repository = repository;
_emailService = emailService;
_logger = logger;
}
public async Task RegisterUser(User user)
{
await _repository.AddAsync(user);
await _emailService.SendWelcomeEmail([user.Email](<http://user.Email>));
_logger.Log($"User {[user.Email](<http://user.Email>)} registered");
}
}
Clean Code Principles
Meaningful Names:
// ❌ Bad
int d; // elapsed time in days
List<int[]> list1;
// ✅ Good
int elapsedTimeInDays;
List<Account> activeAccounts;
Small Functions:
// ❌ Bad - Function does too much
public void ProcessOrder(Order order)
{
// Validate
if (order == null) throw new ArgumentNullException();
if (order.Items.Count == 0) throw new InvalidOperationException();
// Calculate total
decimal total = 0;
foreach (var item in order.Items)
total += item.Price * item.Quantity;
// Apply discount
if (order.Customer.IsPremium)
total *= 0.9m;
// Process payment
// Send email
// Update inventory
}
// ✅ Good - Single Responsibility
public void ProcessOrder(Order order)
{
ValidateOrder(order);
decimal total = CalculateTotal(order);
total = ApplyDiscount(total, order.Customer);
ProcessPayment(order, total);
SendConfirmationEmail(order);
UpdateInventory(order);
}
Practice Projects
✅ Refactor existing project into layers
✅ Implement repository pattern with DI
✅ Create clean architecture template
🚀 Step 6.4 – Portfolio Projects
Project 1: Library Management System
Features:
Book CRUD operations
Member management
Book lending/returning
Late fee calculation
Search and filtering
Reports (most borrowed books, overdue items)
Tech Stack:
C# Console or Minimal API
EF Core with SQL Server
LINQ for queries
Unit tests
Project 2: Personal Finance Tracker
Features:
Income/expense tracking
Category management
Monthly/yearly reports
Budget planning
Charts and visualizations
Import/export CSV
Tech Stack:
C# with EF Core
Chart library for visualizations
Async file operations
Project 3: Task Management System
Features:
Create, edit, delete tasks
Priority levels
Due dates with reminders
Tags/categories
Search and filter
Mark complete
Tech Stack:
C# with modern features
SQLite or SQL Server
Async/await
SOLID principles
For Each Project:
✅ Clean code and architecture
✅ Unit tests (>70% coverage)
✅ GitHub repository with README
✅ Documentation
✅ Use SOLID principles
✅ Proper exception handling
🌟 LEVEL 7 – Master C# 14: The Future of C# Development
🎉 C# 14 ships with .NET 10 (LTS) - Released November 2025, Mastered in 2026
⚡ Why C# 14 is Revolutionary: Extension Members,
fieldkeyword, and enhanced pattern matching are the biggest language improvements since LINQ. This level focuses intensively on these game-changing features that will dominate C# development in 2026 and beyond.
⚡ Step 7.1 – C# 14 New Features
1. Extension Members (Extension Blocks)
The Headline Feature of C# 14[1]
Extension members dramatically expand what you can extend beyond just methods.
Old Way (C# 1-13):
public static class IntegerExtensions
{
public static bool IsEven(this int value) => value % 2 == 0;
public static bool IsOdd(this int value) => value % 2 != 0;
public static bool IsDefault(this int value) => value == 0;
}
New Way (C# 14) - Extension Blocks:
public static class IntegerExtensions
{
// Extension block with receiver name
extension(int value)
{
// Extension methods
public bool IsEven() => value % 2 == 0;
public bool IsOdd() => value % 2 != 0;
// Extension properties!
public bool IsDefault => value == 0;
public bool IsPositive => value > 0;
public bool IsNegative => value < 0;
// Extension operators!
public static int operator ++(int x) => x + 1;
// Static extensions
public static int Parse(string s) => int.Parse(s);
}
}
// Usage
int number = 10;
bool even = number.IsEven(); // Method
bool positive = number.IsPositive; // Property!
Extension Properties for Collections:
public static class ListExtensions
{
extension<T>(List<T> list)
{
// Extension property
public bool IsEmpty => list.Count == 0;
public T FirstItem => list.Count > 0 ? list[0] : default;
public T LastItem => list.Count > 0 ? list[^1] : default;
// Extension indexer!
public T this[Index index] => list[index];
}
}
// Usage
var names = new List<string> { "Alice", "Bob", "Charlie" };
bool empty = names.IsEmpty; // false
string first = names.FirstItem; // "Alice"
2. The field Keyword (Semi-Auto Properties)
Simplify property implementations with direct backing field access.[2]
Old Way:
public class Person
{
private string _name;
public string Name
{
get => _name;
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("Name cannot be empty");
_name = value;
}
}
}
New Way with field keyword:
public class Person
{
public string Name
{
get => field; // 'field' refers to compiler-generated backing field
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("Name cannot be empty");
field = value;
}
}
// Even more concise
public int Age
{
get => field;
set => field = value < 0 ? 0 : value;
}
}
3. Null-Conditional Assignment Operator ??=
Cleaner null-checking before assignment.[[3]](https://dev.to/thecodewrapper/the-3-most-practical-features-of-c-14-for-everyday-developers-3hm7#:~:text=C%23 14 introduces a new,types you don\'t own.)
Old Way:
if (config == null)
{
config = LoadDefaultConfig();
}
// Or
config = config ?? LoadDefaultConfig();
New Way (C# 14 enhanced):
// Assign only if null
config ??= LoadDefaultConfig();
// Works with properties
user.Settings ??= new Settings();
// Chain multiple
cache ??= database.Get() ??= CreateDefault();
4. Partial Constructors and Events
Split constructor logic across partial classes.[2]
// File1.cs
public partial class UserService
{
private readonly ILogger _logger;
partial void OnConstructing();
public partial UserService(ILogger logger)
{
_logger = logger;
OnConstructing(); // Hook for other partial class
}
}
// File2.cs
public partial class UserService
{
private ICache _cache;
partial void OnConstructing()
{
_cache = new MemoryCache();
}
}
5. Overload Compound Assignment Operators
Custom behavior for +=, -=, *=, etc.[2]
public class Counter
{
public int Value { get; set; }
// Now you can overload +=
public static Counter operator +=(Counter counter, int value)
{
counter.Value += value;
Console.WriteLine($"Added {value}, new value: {counter.Value}");
return counter;
}
}
// Usage
var counter = new Counter { Value = 10 };
counter += 5; // "Added 5, new value: 15"
6. Lambda Improvements: ref, in, out without Types
Lambdas now support parameter modifiers without explicit types.[2]
Old Way:
Func<int, int, bool> tryParse = (string input, out int result) =>
int.TryParse(input, out result);
New Way:
// Type inference with ref/out/in
var modifier = (ref int x) => x *= 2;
var reader = (in int x) => x * 2; // Read-only
var output = (out int x) => x = 42;
int num = 10;
modifier(ref num); // num is now 20
7. nameof for Unbounded Generics
Get names of generic types without type parameters.[2]
// Old - required type parameter
string name1 = nameof(List<int>); // Error in older versions
// New - works with unbounded generics
string name2 = nameof(List<>); // "List"
string name3 = nameof(Dictionary<,>); // "Dictionary"
// Useful for logging and reflection
void LogGenericType<T>()
{
Console.WriteLine($"Processing {nameof(T)}");
}
8. Implicit Conversions for Span<T>
Easier span creation and usage.[2]
// Implicit conversion from arrays
Span<int> span = new int[] { 1, 2, 3, 4, 5 };
// Implicit from string
ReadOnlySpan<char> chars = "Hello";
// More natural span slicing
var slice = span[1..3]; // Even cleaner syntax
9. Switch Expression Enhancements
More powerful pattern matching in switch expressions.
// Combining patterns
string Describe(object obj) => obj switch
{
int n when n > 0 => "Positive integer",
int n when n < 0 => "Negative integer",
string { Length: > 10 } => "Long string",
string => "Short string",
List<int> { Count: 0 } => "Empty list",
_ => "Something else"
};
10. Collection Expressions (Enhanced)
Even more natural collection initialization.
// Spread operator in collections
int[] numbers1 = [1, 2, 3];
int[] numbers2 = [4, 5, 6];
int[] combined = [..numbers1, ..numbers2]; // [1, 2, 3, 4, 5, 6]
// Works with different collection types
List<string> list = ["a", "b", "c"];
HashSet<int> set = [1, 2, 2, 3]; // {1, 2, 3}
📋 C# 14 Features Summary
Feature Description Impact Extension Members Extend with properties, operators, static members ⭐⭐⭐⭐⭐ field keyword Simpler property backing fields ⭐⭐⭐⭐ Null-conditional ??= Cleaner null assignment ⭐⭐⭐⭐ Partial Constructors Split constructor logic ⭐⭐⭐ Compound Assignment Overloads Custom +=, -= behavior ⭐⭐⭐ Lambda ref/in/out Parameter modifiers in lambdas ⭐⭐⭐ nameof Unbounded Generic type names ⭐⭐⭐ Span Conversions Easier span usage ⭐⭐⭐⭐
🎯 Why C# 14 is a Game-Changer for 2026
C# 14 represents the most significant evolution in C# since the introduction of LINQ. Here's why these features will dominate professional C# development in 2026:
🚀 Extension Members: The #1 Feature
Why it matters:
Eliminates repetitive code: No more static helper classes with awkward
thisparametersExtension Properties: Finally! Extend types with computed properties, not just methods
Extension Operators: Custom operators on existing types
Better IntelliSense: Properties and operators show up naturally in IDE suggestions
Real-world impact:
// OLD WAY (pre-C# 14) - Clunky
StringExtensions.IsEmpty(myString)
StringExtensions.WordCount(myString)
// NEW WAY (C# 14) - Natural & Intuitive
myString.IsEmpty // Property!
myString.WordCount // Property!
2026 Industry Adoption: Expected to be used in 80%+ of new C# codebases by mid-2026.
⚡ field Keyword: Clean Property Code
Why it matters:
Less boilerplate: No more manual backing fields
Cleaner validation: Inline property validation without extra fields
Better readability: Property logic stays in the property
Real-world impact:
// OLD: 5 lines of boilerplate
private string _name;
public string Name {
get => _name;
set => _name = string.IsNullOrEmpty(value) ? "Unknown" : value;
}
// NEW: 1 clean property with field keyword
public string Name {
get => field;
set => field = string.IsNullOrEmpty(value) ? "Unknown" : value;
}
🎓 Learning Path for C# 14 Mastery in 2026
Week 1-2: Foundation
Understand Extension Members concept
Practice converting old extension methods to extension blocks
Master
fieldkeyword in properties
Week 3-4: Intermediate
Build Extension Properties for common types (string, List<T>, DateTime)
Implement Extension Operators
Combine Extension Members with LINQ
Week 5-6: Advanced
Refactor real-world codebase to use C# 14
Build utility library with Extension Blocks
Master performance implications
Week 7-8: Mastery
Contribute to open-source with C# 14 features
Build production application using modern patterns
Teach others about C# 14
📋 C# 14 Features Summary
🎯 Practice with C# 14 Features
Exercise 1: Extension Members
Create extension blocks for:
stringwith properties likeIsEmpty,WordCountList<T>with properties likeIsEmpty,SecondItemDateTimewith properties likeIsWeekend,IsToday
Exercise 2: Modern Patterns
Refactor old code to use:
fieldkeyword in propertiesExtension blocks instead of extension methods
Null-conditional assignment
Exercise 3: Build Something New
Create a small utility library using C# 14 features:
Collection extension blocks
Custom operators
Modern syntax throughout
🎓 Next Steps After Mastery
Choose Your Path:
1. ASP.NET Core Web Development
MVC / Razor Pages
Web APIs
Blazor (WebAssembly/Server)
SignalR for real-time
2. Desktop Development
WPF (Windows Presentation Foundation)
WinForms
MAUI (Cross-platform)
3. Cloud & Microservices
Azure Functions
Docker & Kubernetes
Microservices architecture
Message queues (RabbitMQ, Azure Service Bus)
4. Game Development
Unity (C# scripting)
Game design patterns
5. Mobile Development
.NET MAUI
Xamarin
📚 Additional Resources
Official Documentation
Practice Platforms
LeetCode
HackerRank
Codewars
Exercism
YouTube Channels
IAmTimCorey
Nick Chapsas
Raw Coding
tutorialEU
✅ Completion Checklist
Level 0-1: Foundation
[ ] Solve 50+ basic programming problems
[ ] Master Git basics
[ ] Create GitHub profile with projects
Level 2: C# Basics
[ ] Complete all data types exercises
[ ] Build 5+ console applications
[ ] Master LINQ basics
Level 3: OOP
[ ] Implement all SOLID principles
[ ] Create class hierarchies
[ ] Use interfaces effectively
Level 4: Advanced
[ ] Write async/await code
[ ] Master LINQ complex queries
[ ] Handle exceptions properly
Level 5: Data
[ ] Build CRUD with ADO.NET
[ ] Master EF Core
[ ] Design normalized databases
Level 6: Professional
[ ] Write unit tests (>80% coverage)
[ ] Implement 5+ design patterns
[ ] Build 3 portfolio projects
Level 7: Master C# 14 (2026 Focus)
[ ] Master C# 14 Extension Members (Extension Blocks)
[ ] Apply
fieldkeyword in all properties[ ] Implement Extension Properties & Operators
[ ] Use modern collection expressions
[ ] Refactor legacy code to C# 14 patterns
[ ] Build production app using C# 14 features
🎊 Congratulations!
Once you complete this roadmap, you'll be a proficient C# developer ready for:
Junior/Mid-level developer positions
Contributing to open-source projects
Building production applications
Specializing in your chosen path
Keep learning, keep coding, and never stop improving! 🚀