C#
App DevelopmentOverview
Modern, object-oriented language by Microsoft. Used for desktop apps, games (Unity), web backends (ASP.NET), and cross-platform mobile apps (MAUI).
Resources
Popular learning and reference links:
Installation & Getting Started
Install the .NET SDK, which includes the C# compiler, runtime, and CLI tools.
# macOS (Homebrew)
brew install dotnet
# Linux (Ubuntu/Debian)
sudo apt install dotnet-sdk-8.0
# Windows — download from https://dotnet.microsoft.com/download
# Or use the install script
curl -sSL https://dot.net/v1/dotnet-install.sh | bash
# Verify installation
dotnet --version
# REPL — C# Interactive (csi)
dotnet tool install -g dotnet-script
dotnet script # Interactive REPL
# Or use C# Interactive in Visual Studio
# Tools > C# Interactive
# .NET Fiddle — try in the browser
# https://dotnetfiddle.net/
# Run a project
dotnet run
# Run a single file script (.csx)
dotnet script hello.csx
# Quick one-liner
dotnet script eval "Console.WriteLine(\"Hello!\")" Project Scaffolding
The dotnet CLI creates projects from built-in templates.
# Console app
dotnet new console -n MyApp
cd MyApp
# Web API
dotnet new webapi -n MyApi
# Class library
dotnet new classlib -n MyLib
# List available templates
dotnet new list
# Run the project
dotnet run
# Build for release
dotnet build -c Release
# Publish self-contained
dotnet publish -c Release --self-contained Package Management
NuGet is the package manager for .NET. Managed via the dotnet CLI or Visual Studio.
# Add a package
dotnet add package Newtonsoft.Json
# Add a specific version
dotnet add package Serilog --version 3.1.0
# Remove a package
dotnet remove package Newtonsoft.Json
# Restore packages
dotnet restore
# List installed packages
dotnet list package
# Update packages
dotnet outdated # requires dotnet-outdated tool Tooling & Formatter/Linter
C# uses .editorconfig for style rules and Roslyn analyzers for linting. dotnet format is the built-in formatter.
# dotnet format — built-in formatter
dotnet format # Format entire solution
dotnet format --verify-no-changes # CI check
# Roslyn analyzers (NuGet packages)
dotnet add package Microsoft.CodeAnalysis.NetAnalyzers
dotnet add package StyleCop.Analyzers
# CSharpier — Prettier-inspired opinionated formatter
dotnet tool install csharpier -g
dotnet csharpier .
# .editorconfig
[*.cs]
indent_style = space
indent_size = 4
dotnet_sort_system_directives_first = true
csharp_new_line_before_open_brace = all
csharp_prefer_braces = true
dotnet_diagnostic.CA1062.severity = warning
<!-- Directory.Build.props — enable analyzers project-wide -->
<Project>
<PropertyGroup>
<AnalysisLevel>latest-recommended</AnalysisLevel>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
</Project> Build & Compile Model
Compiled to IL (Intermediate Language), then JIT or AOT compiled. C# targets the .NET runtime (CLR).
# Build (compiles to IL in .dll)
dotnet build
dotnet build -c Release
# Run (JIT compiles IL to native at runtime)
dotnet run
# Publish — framework-dependent (needs .NET installed)
dotnet publish -c Release
# Publish — self-contained (includes runtime)
dotnet publish -c Release --self-contained -r linux-x64
# Native AOT (ahead-of-time, .NET 8+)
dotnet publish -c Release -r linux-x64 /p:PublishAot=true
# Output: single native binary, no .NET runtime needed
Execution model:
- Source → Roslyn compiler → IL (.dll) → JIT (RyuJIT) → Machine code
- JIT compilation happens at runtime, method by method
- Native AOT eliminates JIT — compiles directly to native binary
- Tiered compilation: quick JIT first, re-optimize hot paths
- ReadyToRun (R2R) pre-compiles IL for faster startup
Libraries & Frameworks
C# has a mature ecosystem centered around .NET and NuGet. Strong in enterprise, web, games, and desktop.
Web Frameworks - ASP.NET Core, Minimal APIs, Blazor (WASM), Carter
Desktop / UI - WPF, WinUI 3, MAUI, Avalonia, WinForms
Game Development - Unity, Godot (C# support), MonoGame, Stride
ORM / Data - Entity Framework Core, Dapper, LINQ to DB, NHibernate
API / Communication - gRPC, SignalR, MassTransit, MediatR, Refit
Serialization - System.Text.Json, Newtonsoft.Json, MessagePack
Logging - Serilog, NLog, Microsoft.Extensions.Logging
Testing - xUnit, NUnit, FluentAssertions, Moq, NSubstitute, Bogus
Cloud - Azure SDK, AWS SDK, Pulumi
CLI - System.CommandLine, Spectre.Console, Cocona
DI / IoC - Microsoft.Extensions.DependencyInjection, Autofac
Testing
Popular frameworks: xUnit (most popular), NUnit, and MSTest. FluentAssertions improves readability. Built-in dotnet test runner.
// xUnit (recommended)
using Xunit;
public class MathTests
{
[Fact]
public void Add_ReturnsCorrectSum()
{
Assert.Equal(3, 1 + 2);
}
[Theory]
[InlineData(1, 2, 3)]
[InlineData(0, 0, 0)]
[InlineData(-1, 1, 0)]
public void Add_WithVariousInputs(int a, int b, int expected)
{
Assert.Equal(expected, a + b);
}
}
// FluentAssertions — readable assertions
using FluentAssertions;
[Fact]
public void List_Should_Contain_Item()
{
var list = new[] { 1, 2, 3 };
list.Should().Contain(2);
list.Should().HaveCount(3);
}
# Run tests
dotnet test # All tests
dotnet test --filter "Add" # Specific tests
dotnet test --collect:"XPlat Code Coverage" # With coverage
dotnet test --logger "console;verbosity=detailed" Debugging
Visual Studio and Rider have best-in-class debuggers. VS Code with C# extension also works well.
// Debug output
Console.WriteLine($"Value: {value}");
System.Diagnostics.Debug.WriteLine("debug only");
// Debugger.Break() — programmatic breakpoint
if (suspiciousCondition)
System.Diagnostics.Debugger.Break();
// Logging (Microsoft.Extensions.Logging)
ILogger<MyService> _logger;
_logger.LogInformation("Processing {Id}", item.Id);
_logger.LogError(ex, "Failed to process {Id}", item.Id);
_logger.LogDebug("Details: {@Item}", item); // Structured
# Visual Studio debugging
# F5: Start debugging
# F9: Toggle breakpoint
# F10: Step over, F11: Step into
# Features: conditional breakpoints, hit counts,
# data breakpoints, IntelliTrace, hot reload
# VS Code
# Install C# extension (ms-dotnettools.csharp)
# launch.json auto-generated for .NET projects
# F5 to start debugging
# Rider (JetBrains)
# Built-in debugger with decompilation
# dotnet-dump — analyze crash dumps
dotnet dump collect --process-id <pid>
dotnet dump analyze <dump-file>
# dumpheap, dumpobj, gcroot commands
# dotnet-sos — SOS debugging extension
dotnet tool install -g dotnet-sos
dotnet sos install
# Remote debugging
# Visual Studio: Debug > Attach to Process > SSH Variables
Use var for type inference or explicit types. const and readonly for immutability.
// Type inference
var name = "Alice";
var age = 30;
// Explicit types
string label = "hello";
int count = 0;
// Constants (compile-time)
const double Pi = 3.14159;
// Readonly (runtime)
readonly int maxSize = 100;
// Destructuring (tuples)
var (x, y) = (1, 2);
// Nullable types
string? maybeNull = null;
int? maybeInt = null; Types
Statically typed with value types, reference types, generics, and records.
// Value types
int n = 42;
double f = 3.14;
bool b = true;
char c = 'a';
// Reference types
string s = "hello";
int[] arr = { 1, 2, 3 };
// Classes
class User {
public string Name { get; set; }
public int Age { get; set; }
}
// Records (immutable by default)
record Point(int X, int Y);
// Enums
enum Color { Red, Green, Blue }
// Generics
List<int> nums = new() { 1, 2, 3 };
Dictionary<string, int> map = new() { ["a"] = 1 };
// Nullable reference types
string? nullable = null; Data Structures
Rich System.Collections.Generic namespace: List<T>, Dictionary<K,V>, HashSet<T>, Queue<T>, Stack<T>, and more.
// List<T> — dynamic array
var list = new List<int> { 1, 2, 3 };
list.Add(4);
list.Remove(2);
list.Contains(3); // true
list.Count; // 3
// Dictionary<TKey, TValue>
var dict = new Dictionary<string, int>
{
["alice"] = 95,
["bob"] = 87,
};
dict["charlie"] = 72;
dict.TryGetValue("alice", out var score);
dict.ContainsKey("bob");
// HashSet<T>
var set = new HashSet<int> { 1, 2, 3 };
set.Add(4);
set.Contains(2);
set.IntersectWith(new[] { 2, 3, 5 });
// Arrays — fixed size
int[] arr = { 1, 2, 3 };
int[,] matrix = { { 1, 2 }, { 3, 4 } };
// Queue and Stack
var queue = new Queue<string>();
queue.Enqueue("first");
var item = queue.Dequeue();
var stack = new Stack<int>();
stack.Push(1);
var top = stack.Pop();
// Immutable collections
using System.Collections.Immutable;
var iList = ImmutableList.Create(1, 2, 3);
var iDict = ImmutableDictionary<string, int>.Empty
.Add("a", 1);
// LINQ on any collection
var evens = list.Where(x => x % 2 == 0).ToList();
var grouped = items.GroupBy(x => x.Category);
// Span<T> — stack-allocated view (no allocation)
Span<int> span = stackalloc int[] { 1, 2, 3 }; Functions
Methods live inside classes or structs. C# also supports local functions and lambdas.
// Method
string Greet(string name) {
return $"Hello, {name}!";
}
// Expression-bodied method
int Add(int a, int b) => a + b;
// Default parameters
int Power(int baseVal, int exp = 2)
=> (int)Math.Pow(baseVal, exp);
// Lambda expressions
Func<int, int> double = x => x * 2;
Action<string> log = msg => Console.WriteLine(msg);
// Local function
void Outer() {
int Inner(int x) => x + 1;
Console.WriteLine(Inner(5));
}
// Generic method
T First<T>(List<T> items) => items[0]; Conditionals
if/else, switch expressions, and pattern matching.
// If / else
if (x > 0) {
Console.WriteLine("positive");
} else if (x == 0) {
Console.WriteLine("zero");
} else {
Console.WriteLine("negative");
}
// Ternary
var label = x > 0 ? "positive" : "non-positive";
// Switch expression (pattern matching)
var result = shape switch {
Circle c => Math.PI * c.Radius * c.Radius,
Rectangle r => r.Width * r.Height,
_ => 0
};
// Pattern matching with when
var category = age switch {
<= 12 => "child",
<= 17 => "teen",
<= 64 => "adult",
_ => "senior"
};
// Null-coalescing
var val = input ?? "default";
var city = user?.Address?.City; Loops
Standard for, foreach, while, and LINQ for functional iteration.
// For loop
for (int i = 0; i < 5; i++) {
Console.WriteLine(i);
}
// Foreach
foreach (var item in new[] { "a", "b", "c" }) {
Console.WriteLine(item);
}
// While
int n = 0;
while (n < 3) {
n++;
}
// Do-while
do {
n--;
} while (n > 0);
// LINQ
var squares = Enumerable.Range(0, 10)
.Select(x => x * x)
.ToList();
var evens = new[] { 1, 2, 3, 4 }
.Where(x => x % 2 == 0); Generics & Type System
Reified generics — type information preserved at runtime (unlike Java). Supports constraints, covariance, and contravariance.
// Generic method
T Identity<T>(T value) => value;
// Constraints
T Max<T>(T a, T b) where T : IComparable<T>
=> a.CompareTo(b) > 0 ? a : b;
// Multiple constraints
void Process<T>(T item)
where T : class, IDisposable, new()
{
// T must be: reference type, IDisposable, have parameterless constructor
}
// Generic class
class Stack<T>
{
private readonly List<T> _items = new();
public void Push(T item) => _items.Add(item);
public T Pop()
{
var item = _items[^1];
_items.RemoveAt(_items.Count - 1);
return item;
}
}
// Variance (on interfaces and delegates)
// Covariant (out) — can return T
interface IProducer<out T> { T Produce(); }
// Contravariant (in) — can accept T
interface IConsumer<in T> { void Consume(T item); }
// IEnumerable<out T> is covariant:
IEnumerable<object> objects = new List<string>(); // OK
// Generic interfaces
interface IRepository<T> where T : class
{
Task<T?> FindByIdAsync(int id);
Task SaveAsync(T entity);
}
// Nullable reference types (C# 8+)
string? nullable = null; // Allowed
string nonNull = "hello"; // Compiler warns if null assigned
// Pattern matching with types
object obj = 42;
if (obj is int n) Console.WriteLine(n * 2);
// Generic math (C# 11+)
T Add<T>(T a, T b) where T : INumber<T> => a + b; Inheritance & Composition
Single class inheritance with interfaces for multiple behavior contracts. Abstract classes, sealed classes, and extension methods.
// Class inheritance
class Animal {
public string Name { get; }
public Animal(string name) => Name = name;
public virtual string Speak() => $"{Name} makes a sound";
}
class Dog : Animal {
public Dog(string name) : base(name) {}
public override string Speak() => $"{Name} barks";
}
// Abstract classes
abstract class Shape {
public abstract double Area();
public string Describe() => $"Area: {Area()}";
}
// Interfaces (multiple)
interface ISerializable { string ToJson(); }
interface IPrintable { void Print(); }
class Report : ISerializable, IPrintable {
public string ToJson() => "{}";
public void Print() => Console.WriteLine(ToJson());
}
// Sealed (prevent further inheritance)
sealed class Singleton : Animal {
public Singleton() : base("single") {}
}
// Extension methods (add behavior without inheritance)
static class StringExt {
public static string Shout(this string s) => s.ToUpper() + "!";
}
// "hello".Shout() => "HELLO!" Functional Patterns
LINQ, lambdas, delegates, and expression-bodied members bring functional style to C#.
// Lambdas and delegates
Func<int, int> double = x => x * 2;
Func<int, int, int> add = (a, b) => a + b;
Action<string> log = msg => Console.WriteLine(msg);
// LINQ (functional collection processing)
var nums = new[] { 1, 2, 3, 4, 5 };
var squared = nums.Select(x => x * x);
var evens = nums.Where(x => x % 2 == 0);
var sum = nums.Aggregate(0, (acc, x) => acc + x);
// Chaining
var result = Enumerable.Range(0, 100)
.Where(x => x % 2 == 0)
.Select(x => x * x)
.Take(10)
.Sum();
// Higher-order functions
T Apply<T>(Func<T, T> fn, T value) => fn(value);
// Pattern matching as expression
string Describe(object obj) => obj switch {
int n when n > 0 => "positive",
int n => "non-positive",
string s => $"string: {s}",
_ => "unknown"
};
// Immutable records
record Point(int X, int Y);
var p1 = new Point(1, 2);
var p2 = p1 with { X = 10 }; // non-destructive mutation
// Local functions
int Factorial(int n) {
int Go(int x, int acc) => x <= 1 ? acc : Go(x - 1, acc * x);
return Go(n, 1);
} Concurrency
Task-based async/await with the Task Parallel Library (TPL). First-class async support throughout the framework.
// async/await — non-blocking I/O
async Task<string> FetchDataAsync(string url)
{
using var client = new HttpClient();
return await client.GetStringAsync(url);
}
// Concurrent tasks
var tasks = new[]
{
FetchDataAsync("/api/a"),
FetchDataAsync("/api/b"),
};
string[] results = await Task.WhenAll(tasks);
// Cancellation
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
await DoWorkAsync(cts.Token);
// Parallel — CPU parallelism
Parallel.ForEach(items, item =>
{
Process(item);
});
// PLINQ — parallel LINQ
var results = data
.AsParallel()
.Where(x => x > 0)
.Select(x => x * 2)
.ToList();
// Channels — producer/consumer
var channel = Channel.CreateBounded<int>(100);
// Producer
await channel.Writer.WriteAsync(42);
// Consumer
var item = await channel.Reader.ReadAsync();
// Lock / SemaphoreSlim
private readonly SemaphoreSlim _semaphore = new(1, 1);
await _semaphore.WaitAsync();
try { /* critical section */ }
finally { _semaphore.Release(); } Modules & Imports
C# uses namespaces to organize code and assemblies for distribution. using directives import namespaces. Projects produce DLLs or executables via MSBuild.
// Import namespaces
using System;
using System.Collections.Generic;
using System.Linq;
// Import with alias
using Json = System.Text.Json.JsonSerializer;
using static System.Math; // import static members
// Global usings (C# 10, apply to entire project)
global using System.Collections.Generic;
// File-scoped namespace (C# 10)
namespace MyApp.Models;
public class User {
public string Name { get; set; }
}
// Traditional namespace
namespace MyApp.Services {
public class UserService {
public User GetUser() => new();
}
}
// Internal vs public (assembly-level visibility)
public class PublicApi {} // visible to other assemblies
internal class InternalOnly {} // same assembly only
// Assemblies: each .csproj produces a DLL
// Reference other projects in .csproj:
// <ProjectReference Include="../MyLib/MyLib.csproj" /> Error Handling
Uses try/catch/finally with typed exceptions.
// Try / catch
try {
var result = int.Parse("not a number");
} catch (FormatException ex) {
Console.WriteLine($"Parse error: {ex.Message}");
} catch (Exception ex) {
Console.WriteLine($"Error: {ex.Message}");
} finally {
Console.WriteLine("always runs");
}
// Throwing exceptions
void Divide(int a, int b) {
if (b == 0)
throw new DivideByZeroException();
return a / b;
}
// Custom exceptions
class AppException : Exception {
public int Code { get; }
public AppException(string msg, int code)
: base(msg) => Code = code;
}
// Exception filters
catch (Exception ex) when (ex.Message.Contains("timeout")) {
// handle timeout specifically
} Memory Management
Generational garbage collector (Gen 0, 1, 2 + LOH). Supports IDisposable for deterministic cleanup and Span<T> for stack allocation.
// GC is automatic — no manual free
var list = new List<int> { 1, 2, 3 };
// GC collects when Gen 0 fills up
// IDisposable — deterministic cleanup
using var file = File.OpenRead("data.txt");
// file.Dispose() called at end of scope
// Classic using statement
using (var conn = new SqlConnection(connStr))
{
conn.Open();
// ...
} // Dispose() called here
// Implement IDisposable
class ManagedResource : IDisposable
{
private bool _disposed;
public void Dispose()
{
if (!_disposed)
{
// Release resources
_disposed = true;
}
}
}
// Stack allocation with Span<T> and stackalloc
Span<int> numbers = stackalloc int[100];
// No heap allocation, no GC pressure
// ArrayPool — reuse arrays
var pool = ArrayPool<byte>.Shared;
byte[] buffer = pool.Rent(1024);
// ... use buffer ...
pool.Return(buffer);
// WeakReference
var weak = new WeakReference<MyObject>(obj);
if (weak.TryGetTarget(out var target))
target.DoSomething();
// GC control
GC.Collect(); // Force collection
GC.GetTotalMemory(true); // Total managed memory
GC.TryStartNoGCRegion(1024); // Suppress GC temporarily
// Value types (struct) live on stack — no GC
struct Point { public int X, Y; }
// ref struct — guaranteed stack-only
ref struct StackOnly { public Span<int> Data; } Performance Profiling
BenchmarkDotNet for benchmarks, dotnet-trace/dotnet-counters for runtime profiling, Visual Studio Profiler for GUI.
// BenchmarkDotNet — micro-benchmarking
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
[MemoryDiagnoser]
public class SortBenchmark
{
private int[] _data = Enumerable.Range(0, 1000).Reverse().ToArray();
[Benchmark]
public void ArraySort() => Array.Sort((int[])_data.Clone());
[Benchmark]
public void LinqOrderBy() => _data.OrderBy(x => x).ToArray();
}
// Run: BenchmarkRunner.Run<SortBenchmark>();
# dotnet-counters — real-time metrics
dotnet tool install -g dotnet-counters
dotnet counters monitor --process-id 12345
# dotnet-trace — collect traces
dotnet tool install -g dotnet-trace
dotnet trace collect --process-id 12345
# Open .nettrace file in Visual Studio or PerfView
# dotnet-dump — memory analysis
dotnet tool install -g dotnet-dump
dotnet dump collect --process-id 12345
dotnet dump analyze dump.dmp
# dotnet-gcdump — GC heap analysis
dotnet gcdump collect --process-id 12345
# Visual Studio: Debug > Performance Profiler
# - CPU Usage, Memory Usage, .NET Object Allocation
# - Async tool, Database, Events
# JetBrains dotTrace / dotMemory
# Rider: built-in profiling tools Interop
P/Invoke for native C calls, COM interop, and strong .NET interop across languages (F#, VB.NET). NativeAOT enables C-compatible exports.
// P/Invoke — call native C libraries
using System.Runtime.InteropServices;
[DllImport("libc", EntryPoint = "sqrt")]
static extern double Sqrt(double x);
double result = Sqrt(16.0); // 4.0
// Windows API
[DllImport("user32.dll")]
static extern int MessageBox(IntPtr hWnd, string text, string caption, uint type);
// LibraryImport (source-generated, .NET 7+)
[LibraryImport("libm")]
private static partial double sqrt(double x);
// NativeAOT — export C-compatible functions
[UnmanagedCallersOnly(EntryPoint = "add")]
public static int Add(int a, int b) => a + b;
// Build: dotnet publish /p:PublishAot=true
// Produces .so/.dylib/.dll callable from C
// COM interop (Windows)
dynamic excel = Activator.CreateInstance(
Type.GetTypeFromProgID("Excel.Application")!);
excel.Visible = true;
// Process — call system commands
using System.Diagnostics;
var output = Process.Start(new ProcessStartInfo("ls", "-la") {
RedirectStandardOutput = true
})!.StandardOutput.ReadToEnd();
// .NET languages interop seamlessly
// C# can call F#, VB.NET, and vice versa
// All compile to the same IL Packaging & Distribution
Publish libraries to NuGet. Distribute apps as self-contained executables, Docker containers, or native AOT binaries.
# Create a NuGet package
dotnet pack -c Release
# Output: bin/Release/MyLib.1.0.0.nupkg
# Publish to NuGet.org
dotnet nuget push MyLib.1.0.0.nupkg --api-key <key> \
--source https://api.nuget.org/v3/index.json
# .csproj package metadata
# <PackageId>MyLib</PackageId>
# <Version>1.0.0</Version>
# <Authors>Me</Authors>
# <Description>My library</Description>
# <PackageLicenseExpression>MIT</PackageLicenseExpression>
# Self-contained deployment (includes .NET runtime)
dotnet publish -c Release --self-contained -r linux-x64
dotnet publish -c Release --self-contained -r osx-arm64
dotnet publish -c Release --self-contained -r win-x64
# Single file
dotnet publish -c Release -r linux-x64 \
/p:PublishSingleFile=true /p:IncludeNativeLibrariesForSelfExtract=true
# Native AOT (no .NET runtime, fast startup)
dotnet publish -c Release -r linux-x64 /p:PublishAot=true
# .NET CLI tools
dotnet pack
dotnet tool install -g my-tool
# Users: dotnet tool install -g my-tool
# Docker
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /app
COPY . .
RUN dotnet publish -c Release -o /out
FROM mcr.microsoft.com/dotnet/aspnet:8.0
COPY --from=build /out .
ENTRYPOINT ["dotnet", "MyApp.dll"]