What is C#?
C# is a modern, object-oriented programming language developed by Microsoft. It runs on the .NET platform and is used for building web apps, desktop apps, games (Unity), cloud services, and more.
Type-Safe
Strong static typing catches errors at compile time. Nullable reference types prevent null reference exceptions.
Cross-Platform
.NET runs on Windows, macOS, and Linux. Build once, deploy anywhere.
Modern Features
Pattern matching, records, async/await, LINQ, and more. C# evolves rapidly with yearly releases.
Rich Ecosystem
NuGet packages, Visual Studio/VS Code tooling, and extensive standard library.
Basics
Hello World
// Minimal (C# 9+ top-level statements) Console.WriteLine("Hello, World!"); // Traditional namespace MyApp; class Program { static void Main(string[] args) { Console.WriteLine("Hello, World!"); } }
Variables
// Explicit typing int age = 25; string name = "Alice"; bool isActive = true; // Type inference with var var count = 42; // int var message = "Hello"; // string // Constants const double Pi = 3.14159; // Nullable types int? maybeNull = null; string? nullableString = null;
Methods
// Basic method int Add(int a, int b) { return a + b; } // Expression-bodied method int Multiply(int a, int b) => a * b; // Default parameters void Greet(string name = "World") => Console.WriteLine($"Hello, {name}!"); // Named arguments Greet(name: "Alice"); // Out parameters bool TryParse(string s, out int result) { ... } if (int.TryParse("42", out var value)) { ... } // Ref parameters (pass by reference) void Swap(ref int a, ref int b) => (a, b) = (b, a);
String Interpolation
var name = "Alice"; var age = 30; // Interpolated string var greeting = $"Hello, {name}! You are {age} years old."; // Formatting var price = 19.99m; Console.WriteLine($"Price: {price:C}"); // Price: $19.99 Console.WriteLine($"Date: {DateTime.Now:yyyy-MM-dd}"); // Raw string literals (C# 11+) var json = """ { "name": "Alice", "age": 30 } """;
Types
Value Types
| Category | Types | Notes |
|---|---|---|
| Integers | byte, short, int, long |
8, 16, 32, 64-bit signed |
| Unsigned | sbyte, ushort, uint, ulong |
Unsigned variants |
| Floating | float, double, decimal |
32, 64-bit; decimal for money |
| Boolean | bool |
true or false |
| Character | char |
Unicode character |
| Struct | struct |
Custom value types |
| Enum | enum |
Named constants |
Reference Types
// String (immutable) string s = "Hello"; // Object (base of all types) object obj = 42; // boxing int n = (int)obj; // unboxing // Dynamic (runtime typing) dynamic dyn = "Hello"; dyn = 42; // OK at runtime // Arrays int[] numbers = { 1, 2, 3 }; int[] zeros = new int[10]; int[,] matrix = new int[3, 3]; // 2D array
Structs
public struct Point { public double X { get; set; } public double Y { get; set; } public Point(double x, double y) => (X, Y) = (x, y); public double Distance(Point other) => Math.Sqrt(Math.Pow(X - other.X, 2) + Math.Pow(Y - other.Y, 2)); } var p = new Point(3, 4);
Enums
public enum Status { Pending, // 0 Active, // 1 Completed, // 2 Cancelled // 3 } // With explicit values public enum HttpStatus { OK = 200, NotFound = 404, ServerError = 500 } // Flags enum [Flags] public enum Permissions { None = 0, Read = 1, Write = 2, Execute = 4, All = Read | Write | Execute } var perms = Permissions.Read | Permissions.Write;
Tuples
// Unnamed tuple (int, string) pair = (1, "one"); Console.WriteLine(pair.Item1); // 1 // Named tuple (int Id, string Name) person = (1, "Alice"); Console.WriteLine(person.Name); // Alice // Return multiple values (int Min, int Max) GetRange(int[] arr) => (arr.Min(), arr.Max()); // Deconstruction var (min, max) = GetRange(numbers);
Control Flow
If / Else
if (age >= 18) { Console.WriteLine("Adult"); } else if (age >= 13) { Console.WriteLine("Teenager"); } else { Console.WriteLine("Child"); } // Ternary operator var status = age >= 18 ? "Adult" : "Minor"; // Null coalescing var value = nullableValue ?? "default"; nullableValue ??= "assign if null";
Switch
// Statement switch (day) { case "Monday": case "Tuesday": Console.WriteLine("Weekday"); break; case "Saturday": case "Sunday": Console.WriteLine("Weekend"); break; default: Console.WriteLine("Unknown"); break; } // Expression (C# 8+) var message = day switch { "Monday" or "Tuesday" => "Weekday", "Saturday" or "Sunday" => "Weekend", _ => "Unknown" };
Loops
// For loop for (int i = 0; i < 10; i++) { Console.WriteLine(i); } // Foreach foreach (var item in collection) { Console.WriteLine(item); } // While while (condition) { // ... } // Do-while do { // executes at least once } while (condition); // Break and continue for (int i = 0; i < 100; i++) { if (i == 50) break; // exit loop if (i % 2 == 0) continue; // skip even Console.WriteLine(i); }
Object-Oriented Programming
Classes
public class Person { // Fields private string _name; // Properties public string Name { get => _name; set => _name = value ?? throw new ArgumentNullException(); } // Auto-property public int Age { get; set; } // Read-only property public bool IsAdult => Age >= 18; // Init-only property (C# 9+) public string Id { get; init; } // Constructor public Person(string name, int age) { Name = name; Age = age; } // Method public void Greet() => Console.WriteLine($"Hi, I'm {Name}"); } // Instantiation var person = new Person("Alice", 30); var person2 = new Person("Bob", 25) { Id = "123" };
Primary Constructors (C# 12+)
// Parameters available throughout the class public class Person(string name, int age) { public string Name => name; public int Age => age; public bool IsAdult => age >= 18; }
Inheritance
public class Animal { public string Name { get; set; } public virtual void Speak() => Console.WriteLine("..."); } public class Dog : Animal { public override void Speak() => Console.WriteLine("Woof!"); // Call base method public void SpeakQuietly() { base.Speak(); } } // Sealed prevents inheritance public sealed class FinalClass { }
Interfaces
public interface IShape { double Area { get; } double Perimeter(); // Default implementation (C# 8+) void Print() => Console.WriteLine($"Area: {Area}"); } public class Circle : IShape { public double Radius { get; set; } public double Area => Math.PI * Radius * Radius; public double Perimeter() => 2 * Math.PI * Radius; }
Abstract Classes
public abstract class Shape { public abstract double Area { get; } // Can have concrete methods too public void PrintArea() => Console.WriteLine($"Area: {Area}"); } public class Rectangle : Shape { public double Width { get; set; } public double Height { get; set; } public override double Area => Width * Height; }
Records (C# 9+)
// Immutable reference type with value semantics public record Person(string Name, int Age); var alice = new Person("Alice", 30); var bob = alice with { Name = "Bob" }; // Non-destructive mutation // Value equality var alice2 = new Person("Alice", 30); Console.WriteLine(alice == alice2); // True // Record struct (C# 10+) public record struct Point(double X, double Y);
Collections
Arrays
// Declaration and initialization int[] numbers = { 1, 2, 3, 4, 5 }; int[] zeros = new int[10]; string[] names = new[] { "Alice", "Bob" }; // Access var first = numbers[0]; var last = numbers[^1]; // Index from end var slice = numbers[1..4]; // Range: {2, 3, 4} // 2D array int[,] matrix = new int[3, 3]; matrix[0, 0] = 1;
List<T>
var list = new List<string> { "Alice", "Bob" }; // Collection expression (C# 12+) List<int> nums = [1, 2, 3, 4, 5]; // Operations list.Add("Charlie"); list.AddRange(["Dave", "Eve"]); list.Remove("Bob"); list.RemoveAt(0); list.Insert(0, "First"); // Query var contains = list.Contains("Alice"); var index = list.IndexOf("Charlie"); var count = list.Count;
Dictionary<K, V>
var dict = new Dictionary<string, int> { ["Alice"] = 30, ["Bob"] = 25 }; // Add/update dict["Charlie"] = 35; dict.TryAdd("Dave", 28); // Only if not exists // Access if (dict.TryGetValue("Alice", out var age)) { Console.WriteLine(age); } // Iterate foreach (var (key, value) in dict) { Console.WriteLine($"{key}: {value}"); }
Other Collections
// HashSet - unique elements var set = new HashSet<int> { 1, 2, 3 }; set.Add(3); // Ignored, already exists // Queue - FIFO var queue = new Queue<string>(); queue.Enqueue("first"); var item = queue.Dequeue(); // Stack - LIFO var stack = new Stack<int>(); stack.Push(1); var top = stack.Pop(); // SortedDictionary, SortedSet - ordered var sorted = new SortedDictionary<string, int>();
Span<T> and Memory<T>
// Stack-allocated, no heap allocation Span<int> span = stackalloc int[] { 1, 2, 3 }; // Slice of array without copying int[] arr = { 1, 2, 3, 4, 5 }; Span<int> slice = arr.AsSpan(1, 3); // {2, 3, 4} // ReadOnlySpan for strings ReadOnlySpan<char> text = "Hello, World!".AsSpan(); var hello = text[..5]; // "Hello"
LINQ
Language Integrated Query — declarative data querying that works with any IEnumerable.
Query Syntax
var numbers = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; var evens = from n in numbers where n % 2 == 0 orderby n descending select n * 2; // With grouping var grouped = from p in people group p by p.City into g select new { City = g.Key, Count = g.Count() };
Method Syntax
// Filtering var adults = people.Where(p => p.Age >= 18); // Projection var names = people.Select(p => p.Name); // Sorting var sorted = people.OrderBy(p => p.Name) .ThenByDescending(p => p.Age); // Aggregation var count = numbers.Count(); var sum = numbers.Sum(); var avg = numbers.Average(); var max = numbers.Max(); // First/Last var first = numbers.First(); var firstOrNull = numbers.FirstOrDefault(); var single = numbers.Single(n => n == 5); // Any/All var hasAdults = people.Any(p => p.Age >= 18); var allAdults = people.All(p => p.Age >= 18);
More LINQ Operations
// Skip/Take var page = items.Skip(20).Take(10); // Distinct var unique = items.Distinct(); // GroupBy var byCity = people.GroupBy(p => p.City); // Join var joined = orders.Join(customers, o => o.CustomerId, c => c.Id, (o, c) => new { Order = o, Customer = c }); // Flatten nested collections var allOrders = customers.SelectMany(c => c.Orders); // ToList/ToArray/ToDictionary var list = query.ToList(); var dict = people.ToDictionary(p => p.Id); // Chunk (C# 13) var batches = items.Chunk(100); // IEnumerable of 100-item arrays
ToList().
Async/Await
Write asynchronous code that looks synchronous. Non-blocking I/O without callback hell.
Basic Async
// Async method public async Task<string> GetDataAsync() { using var client = new HttpClient(); var response = await client.GetStringAsync("https://api.example.com"); return response; } // Async void (only for event handlers!) private async void Button_Click(object sender, EventArgs e) { await DoSomethingAsync(); } // ValueTask for hot paths (avoids allocation when sync) public async ValueTask<int> GetCachedValueAsync() { if (_cache.TryGetValue(key, out var value)) return value; // No allocation return await FetchFromDbAsync(key); }
Parallel Operations
// Wait for all var task1 = GetUserAsync(id1); var task2 = GetUserAsync(id2); var task3 = GetUserAsync(id3); var users = await Task.WhenAll(task1, task2, task3); // Wait for first to complete var first = await Task.WhenAny(task1, task2, task3); // Process as they complete var tasks = urls.Select(url => FetchAsync(url)).ToList(); while (tasks.Count > 0) { var completed = await Task.WhenAny(tasks); tasks.Remove(completed); var result = await completed; Process(result); }
Async Streams (C# 8+)
// Producer public async IAsyncEnumerable<int> GenerateAsync() { for (int i = 0; i < 100; i++) { await Task.Delay(100); yield return i; } } // Consumer await foreach (var item in GenerateAsync()) { Console.WriteLine(item); }
Cancellation
public async Task ProcessAsync(CancellationToken ct) { while (!ct.IsCancellationRequested) { await DoWorkAsync(ct); } } // Usage var cts = new CancellationTokenSource(); cts.CancelAfter(TimeSpan.FromSeconds(30)); try { await ProcessAsync(cts.Token); } catch (OperationCanceledException) { Console.WriteLine("Cancelled"); }
Error Handling
Try/Catch/Finally
try { var result = RiskyOperation(); } catch (FileNotFoundException ex) { Console.WriteLine($"File not found: {ex.FileName}"); } catch (IOException ex) when (ex.HResult == -2147024816) { // Exception filter Console.WriteLine("Disk full"); } catch (Exception ex) { Console.WriteLine($"Error: {ex.Message}"); throw; // Re-throw preserving stack trace } finally { // Always runs (cleanup) Cleanup(); }
Throwing Exceptions
// Throw new exception throw new ArgumentException("Invalid argument", nameof(param)); // Common exception types throw new ArgumentNullException(nameof(param)); throw new InvalidOperationException("Cannot do that now"); throw new NotImplementedException(); throw new NotSupportedException(); // Guard clauses ArgumentNullException.ThrowIfNull(param); ArgumentException.ThrowIfNullOrEmpty(str);
Custom Exceptions
public class OrderNotFoundException : Exception { public int OrderId { get; } public OrderNotFoundException(int orderId) : base($"Order {orderId} not found") { OrderId = orderId; } }
Using Statement (IDisposable)
// Classic using block using (var file = File.OpenRead("data.txt")) { // file is disposed at end of block } // Using declaration (C# 8+) using var file = File.OpenRead("data.txt"); // file is disposed at end of scope // Multiple resources using var reader = new StreamReader(path); using var writer = new StreamWriter(outPath); // Async disposal await using var conn = new SqlConnection(connStr);
Pattern Matching
Powerful conditional logic with concise syntax. Progressively enhanced from C# 7 through C# 11+.
Type Patterns
// Type check and cast if (obj is string s) { Console.WriteLine(s.Length); } // Negation if (obj is not null) { // ... } // Switch expression var description = obj switch { int n => $"Integer: {n}", string s => $"String: {s}", null => "Nothing", _ => "Unknown" };
Property Patterns
var message = person switch { { Age: >= 18, Name: "Admin" } => "Admin access", { Age: >= 18 } => "Adult access", { Age: < 18 } => "Minor access", _ => "Unknown" }; // Nested properties if (order is { Customer: { Address: { City: "NYC" } } }) { // order.Customer.Address.City == "NYC" }
Relational Patterns
var grade = score switch { >= 90 => "A", >= 80 and < 90 => "B", >= 70 and < 80 => "C", >= 60 and < 70 => "D", _ => "F" };
List Patterns (C# 11+)
int[] nums = { 1, 2, 3 }; var result = nums switch { [] => "Empty", [var single] => $"Single: {single}", [var first, ..] => $"Starts with: {first}", [.., var last] => $"Ends with: {last}", [1, 2, 3] => "Exact match", [1, .. var rest] => $"Starts with 1, rest: {rest.Length}" };
Deconstruction Patterns
var (x, y) = GetPoint(); // Deconstruct tuple // In switch var quadrant = point switch { (0, 0) => "Origin", (> 0, > 0) => "Q1", (< 0, > 0) => "Q2", (< 0, < 0) => "Q3", (> 0, < 0) => "Q4", _ => "On axis" };
Modern C# Features
Nullable Reference Types (C# 8+)
// Enable in project: <Nullable>enable</Nullable> string name = "Alice"; // Cannot be null string? maybe = null; // Explicitly nullable // Null-forgiving operator (trust me, it's not null) string s = maybe!; // Null-conditional operators var len = maybe?.Length; // int? var first = list?[0]; // Access if not null maybe?.ToUpper(); // Call if not null // Null coalescing var value = maybe ?? "default"; maybe ??= "assign if null";
File-Scoped Namespaces (C# 10+)
// Instead of: namespace MyApp { class Foo { } } // Write: namespace MyApp; class Foo { }
Global Usings (C# 10+)
// GlobalUsings.cs or in .csproj global using System; global using System.Collections.Generic; global using System.Linq; // Implicit usings (SDK sets common ones) // In .csproj: <ImplicitUsings>enable</ImplicitUsings>
Required Members (C# 11+)
public class Person { public required string Name { get; init; } public required int Age { get; init; } } // Must initialize required members var p = new Person { Name = "Alice", Age = 30 };
Collection Expressions (C# 12+)
// Unified syntax for all collection types int[] arr = [1, 2, 3]; List<int> list = [1, 2, 3]; Span<int> span = [1, 2, 3]; // Spread operator int[] combined = [..first, ..second, 99]; // Empty collection List<string> empty = [];
params Collections (C# 13+)
// params with any collection type void PrintAll(params ReadOnlySpan<string> items) { foreach (var item in items) Console.WriteLine(item); } PrintAll("a", "b", "c");
Generics
// Generic class public class Box<T> { public T Value { get; set; } } // Generic method public T Max<T>(T a, T b) where T : IComparable<T> { return a.CompareTo(b) > 0 ? a : b; } // Constraints where T : class // Reference type where T : struct // Value type where T : new() // Has parameterless constructor where T : BaseClass // Inherits from where T : IInterface // Implements interface where T : notnull // Non-nullable
Attributes
// Using attributes [Obsolete("Use NewMethod instead")] public void OldMethod() { } [Serializable] public class MyClass { } // Custom attribute [AttributeUsage(AttributeTargets.Method)] public class LogAttribute : Attribute { public string Message { get; set; } }
CLI & Project Setup
.NET CLI Commands
# Create new project dotnet new console -n MyApp # Console app dotnet new webapi -n MyApi # Web API dotnet new classlib -n MyLib # Class library # Build and run dotnet build dotnet run dotnet watch run # Hot reload # Dependencies dotnet add package Newtonsoft.Json dotnet remove package Newtonsoft.Json dotnet restore # Testing dotnet new xunit -n MyApp.Tests dotnet test dotnet test --filter "Category=Unit" # Publish dotnet publish -c Release dotnet publish -c Release -r win-x64 --self-contained
Project File (.csproj)
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net9.0</TargetFramework> <OutputType>Exe</OutputType> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> </PropertyGroup> <ItemGroup> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> </ItemGroup> </Project>
Configuration
// appsettings.json { "ConnectionStrings": { "Default": "Server=localhost;Database=mydb" }, "Logging": { "LogLevel": { "Default": "Information" } } } // Reading configuration var builder = new ConfigurationBuilder() .AddJsonFile("appsettings.json") .AddEnvironmentVariables(); var config = builder.Build(); var connStr = config.GetConnectionString("Default");
Dependency Injection
// Register services var services = new ServiceCollection(); services.AddSingleton<ILogger, ConsoleLogger>(); services.AddScoped<IUserService, UserService>(); services.AddTransient<IEmailSender, SmtpEmailSender>(); var provider = services.BuildServiceProvider(); // Resolve var logger = provider.GetRequiredService<ILogger>(); // In ASP.NET Core public class MyController(IUserService users) { // Primary constructor injection }