C# (pronounced "C sharp") is a modern, versatile programming language developed by Microsoft and used for building web APIs, desktop apps, mobile apps, games, and cloud services. As of 2025, C# consistently ranks in the top 5 most-used programming languages worldwide. This tutorial takes you from zero to writing real C# programs — no prior experience required.
What you'll learn
| Topic | What you'll be able to do |
|---|---|
| Setup | Install .NET and write your first program |
| Syntax & variables | Understand how C# code works |
| Data types | Work with value types, strings, arrays |
| Control flow | Use if/else, loops, switch expressions |
| Methods & functions | Write reusable, clean code |
| OOP | Build classes, interfaces, inheritance |
| Generics & collections | Use List, Dictionary, LINQ |
| Async/await | Handle asynchronous operations |
| Error handling | Use try/catch, custom exceptions |
| Projects | Build a to-do CLI, REST API, and more |
Why learn C#?
| Use case | Example technologies |
|---|---|
| Web APIs & backends | ASP.NET Core, Minimal APIs |
| Game development | Unity (most popular game engine) |
| Desktop apps | WPF, MAUI, Windows Forms |
| Mobile apps | .NET MAUI (iOS + Android) |
| Cloud & microservices | Azure, AWS Lambda with .NET |
| Enterprise applications | Banking, healthcare, insurance |
| Machine learning | ML.NET |
| IoT / embedded | .NET nanoFramework |
C# vs other languages quick table:
| Language | Typing | Speed | Use case strength |
|---|---|---|---|
| C# | Static | Fast (near C++) | Web, games, enterprise |
| Java | Static | Fast | Enterprise, Android |
| Python | Dynamic | Slow | ML, scripting, data |
| JavaScript | Dynamic | Medium | Browser, Node.js |
| Go | Static | Fast | Cloud, DevOps |
| Rust | Static | Fastest | Systems, WASM |
1. Setup: Install .NET
Option 1: Download from microsoft.com
- Go to dotnet.microsoft.com/download
- Download .NET 8 SDK (LTS)
- Run the installer
- Verify in terminal:
dotnet --version
# 8.0.xxx
Option 2: Package manager
# macOS (Homebrew)
brew install dotnet
# Ubuntu / Debian
sudo apt install dotnet-sdk-8.0
# Windows (winget)
winget install Microsoft.DotNet.SDK.8
Recommended editor
Visual Studio Code with the C# Dev Kit extension (free, cross-platform) — or Visual Studio 2022 Community (Windows/Mac, free).
Create your first project
dotnet new console -n HelloWorld
cd HelloWorld
dotnet run
Output:
Hello, World!
Open Program.cs:
// Program.cs — top-level statements (C# 9+, no class/Main needed)
Console.WriteLine("Hello, World!");
That's a complete C# program. No boilerplate required since C# 9.
2. Variables and data types
Declaring variables
// Explicit type
string name = "Alice";
int age = 30;
double price = 9.99;
bool isActive = true;
// Type inference with var (type still fixed at compile time)
var city = "London"; // string
var count = 42; // int
var ratio = 3.14; // double
All primitive types
| Type | Size | Range | Example |
|---|---|---|---|
byte |
1 byte | 0–255 | byte b = 200; |
sbyte |
1 byte | -128–127 | sbyte sb = -5; |
short |
2 bytes | -32,768–32,767 | short s = 1000; |
int |
4 bytes | -2B to 2B | int n = 42; |
long |
8 bytes | ±9.2 quintillion | long l = 1_000_000L; |
float |
4 bytes | ±3.4×10³⁸ | float f = 3.14f; |
double |
8 bytes | ±1.7×10³⁰⁸ | double d = 3.14; |
decimal |
16 bytes | 28–29 digits | decimal m = 9.99m; |
char |
2 bytes | Unicode character | char c = 'A'; |
bool |
1 byte | true/false | bool ok = true; |
Constants
const double Pi = 3.14159265358979;
const int MaxRetries = 3;
String basics
string first = "Alice";
string last = "Smith";
// Concatenation
string full = first + " " + last; // "Alice Smith"
// String interpolation (preferred)
string greeting = $"Hello, {first}!"; // "Hello, Alice!"
// Multi-line (raw string literal, C# 11+)
string json = """
{
"name": "Alice",
"age": 30
}
""";
// Common string methods
string msg = " Hello, World! ";
Console.WriteLine(msg.Trim()); // "Hello, World!"
Console.WriteLine(msg.ToUpper()); // " HELLO, WORLD! "
Console.WriteLine(msg.Contains("World")); // True
Console.WriteLine(msg.Replace("World", "C#")); // " Hello, C#! "
Console.WriteLine(msg.Length); // 17
Console.WriteLine(msg.Substring(8, 5)); // "World" (start, length)
Console.WriteLine(msg.Split(',')[0].Trim()); // "Hello"
Nullable value types
int? age = null; // nullable int — can be null
double? price = null; // nullable double
if (age.HasValue)
Console.WriteLine(age.Value);
// Null-coalescing operator
int displayAge = age ?? 0; // 0 if null
// Null-coalescing assignment
age ??= 18; // assign 18 if null
3. Control flow
if / else if / else
int score = 75;
if (score >= 90)
Console.WriteLine("A");
else if (score >= 80)
Console.WriteLine("B");
else if (score >= 70)
Console.WriteLine("C");
else
Console.WriteLine("F");
// Ternary operator
string result = score >= 60 ? "Pass" : "Fail";
switch expression (C# 8+, preferred)
string grade = score switch
{
>= 90 => "A",
>= 80 => "B",
>= 70 => "C",
>= 60 => "D",
_ => "F" // default case
};
Console.WriteLine(grade); // C
for loop
for (int i = 0; i < 5; i++)
Console.WriteLine(i); // 0 1 2 3 4
// Reverse
for (int i = 4; i >= 0; i--)
Console.Write(i + " "); // 4 3 2 1 0
while / do-while
int n = 1;
while (n <= 5)
{
Console.Write(n + " ");
n++;
}
// 1 2 3 4 5
// do-while (always runs at least once)
do
{
Console.Write(n + " ");
n--;
} while (n > 0);
foreach
string[] fruits = { "apple", "banana", "cherry" };
foreach (string fruit in fruits)
Console.WriteLine(fruit);
// With index using LINQ
foreach (var (fruit, i) in fruits.Select((f, i) => (f, i)))
Console.WriteLine($"{i}: {fruit}");
break / continue
for (int i = 0; i < 10; i++)
{
if (i == 3) continue; // skip 3
if (i == 7) break; // stop at 7
Console.Write(i + " ");
}
// 0 1 2 4 5 6
4. Methods
// Basic method
static int Add(int a, int b)
{
return a + b;
}
// Expression body (single expression)
static int Multiply(int a, int b) => a * b;
// Default parameters
static void Greet(string name, string greeting = "Hello")
=> Console.WriteLine($"{greeting}, {name}!");
// Named arguments
Greet(greeting: "Hi", name: "Bob");
// Params (variable number of arguments)
static int Sum(params int[] numbers)
=> numbers.Sum();
Console.WriteLine(Sum(1, 2, 3, 4, 5)); // 15
// Out parameters
static bool TryParse(string s, out int result)
{
return int.TryParse(s, out result);
}
if (TryParse("42", out int value))
Console.WriteLine(value); // 42
// Tuple return
static (string Name, int Age) GetPerson()
=> ("Alice", 30);
var (name, age) = GetPerson();
Console.WriteLine($"{name} is {age}"); // Alice is 30
5. Arrays and Collections
Arrays
// Fixed-size array
int[] numbers = { 1, 2, 3, 4, 5 };
string[] names = new string[3]; // ["", "", ""]
// Access
Console.WriteLine(numbers[0]); // 1
numbers[4] = 99;
// Length
Console.WriteLine(numbers.Length); // 5
// Multi-dimensional
int[,] matrix = { { 1, 2 }, { 3, 4 }, { 5, 6 } };
Console.WriteLine(matrix[1, 0]); // 3
// Jagged array (array of arrays)
int[][] jagged = new int[3][];
jagged[0] = new int[] { 1, 2, 3 };
jagged[1] = new int[] { 4, 5 };
List
var fruits = new List<string> { "apple", "banana" };
fruits.Add("cherry");
fruits.Insert(0, "avocado"); // insert at index
fruits.Remove("banana"); // remove by value
fruits.RemoveAt(1); // remove by index
Console.WriteLine(fruits.Count); // 2
Console.WriteLine(fruits.Contains("apple")); // True
// Sort
fruits.Sort();
// Convert array ↔ List
int[] arr = { 3, 1, 2 };
var list = arr.ToList();
int[] back = list.ToArray();
Dictionary<TKey, TValue>
var ages = new Dictionary<string, int>
{
["Alice"] = 30,
["Bob"] = 25
};
ages["Charlie"] = 35; // add
ages["Alice"] = 31; // update
// Safe read
if (ages.TryGetValue("Dave", out int daveAge))
Console.WriteLine(daveAge);
else
Console.WriteLine("Not found");
// Iterate
foreach (var (key, val) in ages)
Console.WriteLine($"{key}: {val}");
// Check key
Console.WriteLine(ages.ContainsKey("Bob")); // True
ages.Remove("Bob");
HashSet
var set = new HashSet<string> { "a", "b", "c" };
set.Add("b"); // ignored — no duplicates
set.Add("d");
Console.WriteLine(set.Count); // 4
Console.WriteLine(set.Contains("c")); // True
// Set operations
var other = new HashSet<string> { "b", "c", "e" };
set.IntersectWith(other); // { b, c }
set.UnionWith(other); // original union
set.ExceptWith(other); // set minus other
6. Object-Oriented Programming
Classes and objects
public class Person
{
// Properties (preferred over public fields)
public string Name { get; set; }
public int Age { get; private set; } // read-only outside class
// Constructor
public Person(string name, int age)
{
Name = name;
Age = age;
}
// Method
public string Greet()
=> $"Hi, I'm {Name} and I'm {Age} years old.";
// Override ToString
public override string ToString()
=> $"Person(Name={Name}, Age={Age})";
}
// Usage
var p = new Person("Alice", 30);
Console.WriteLine(p.Greet());
Console.WriteLine(p); // uses ToString()
Constructor shortcuts (primary constructors, C# 12)
// C# 12: primary constructor
public class Point(double x, double y)
{
public double X { get; } = x;
public double Y { get; } = y;
public double Distance() => Math.Sqrt(X * X + Y * Y);
}
var pt = new Point(3, 4);
Console.WriteLine(pt.Distance()); // 5
Records (immutable data, C# 9+)
// Value-based equality, immutable by default
public record Product(string Name, decimal Price);
var p1 = new Product("Widget", 9.99m);
var p2 = new Product("Widget", 9.99m);
Console.WriteLine(p1 == p2); // True (value equality)
// Non-destructive mutation
var p3 = p1 with { Price = 14.99m };
Console.WriteLine(p3.Name); // Widget
Console.WriteLine(p3.Price); // 14.99
Inheritance
public class Animal
{
public string Name { get; init; }
public Animal(string name) => Name = name;
public virtual string Speak() => "...";
public override string ToString() => $"{GetType().Name}({Name})";
}
public class Dog : Animal
{
public Dog(string name) : base(name) { }
public override string Speak() => "Woof!";
}
public class Cat : Animal
{
public Cat(string name) : base(name) { }
public override string Speak() => "Meow!";
}
Animal[] animals = { new Dog("Rex"), new Cat("Luna") };
foreach (var animal in animals)
Console.WriteLine($"{animal.Name} says: {animal.Speak()}");
// Rex says: Woof!
// Luna says: Meow!
Interfaces
public interface IShape
{
double Area();
double Perimeter();
string Describe() => $"Area: {Area():F2}, Perimeter: {Perimeter():F2}"; // default impl
}
public class Circle : IShape
{
private readonly double _radius;
public Circle(double radius) => _radius = radius;
public double Area() => Math.PI * _radius * _radius;
public double Perimeter() => 2 * Math.PI * _radius;
}
public class Rectangle : IShape
{
private readonly double _w, _h;
public Rectangle(double w, double h) { _w = w; _h = h; }
public double Area() => _w * _h;
public double Perimeter() => 2 * (_w + _h);
}
IShape[] shapes = { new Circle(5), new Rectangle(4, 6) };
foreach (var shape in shapes)
Console.WriteLine(shape.Describe());
// Area: 78.54, Perimeter: 31.42
// Area: 24.00, Perimeter: 20.00
Abstract classes
public abstract class Vehicle
{
public string Model { get; }
protected int Speed;
protected Vehicle(string model) => Model = model;
public abstract void Accelerate(int amount); // must override
public virtual string Status()
=> $"{Model} going {Speed} km/h";
}
public class Car : Vehicle
{
public Car(string model) : base(model) { }
public override void Accelerate(int amount)
{
Speed = Math.Min(Speed + amount, 200); // top speed 200
}
}
abstract class vs interface:
| Feature | Abstract class | Interface |
|---|---|---|
| Multiple inheritance | No (single) | Yes (multiple) |
| Constructor | Yes | No |
| Fields | Yes | No |
| Default methods | Yes | Yes (C# 8+) |
| Access modifiers | Yes | Public only |
| State | Yes | No |
| Use when | Shared base + state | Contract / capability |
Static members and singleton
public class Counter
{
private static int _total = 0; // shared across all instances
private int _count = 0;
public void Increment()
{
_count++;
_total++;
}
public static int Total => _total;
public int Count => _count;
}
var c1 = new Counter();
var c2 = new Counter();
c1.Increment();
c1.Increment();
c2.Increment();
Console.WriteLine(Counter.Total); // 3
Console.WriteLine(c1.Count); // 2
7. Generics
// Generic method
static T Max<T>(T a, T b) where T : IComparable<T>
=> a.CompareTo(b) >= 0 ? a : b;
Console.WriteLine(Max(3, 7)); // 7
Console.WriteLine(Max("apple", "banana")); // banana
// Generic class
public class Stack<T>
{
private readonly List<T> _items = new();
public void Push(T item) => _items.Add(item);
public T Pop()
{
if (_items.Count == 0) throw new InvalidOperationException("Stack is empty");
var last = _items[^1]; // ^1 = last element (index from end)
_items.RemoveAt(_items.Count - 1);
return last;
}
public T Peek() => _items[^1];
public int Count => _items.Count;
public bool IsEmpty => _items.Count == 0;
}
var stack = new Stack<int>();
stack.Push(1);
stack.Push(2);
stack.Push(3);
Console.WriteLine(stack.Pop()); // 3
Console.WriteLine(stack.Peek()); // 2
8. LINQ (Language Integrated Query)
LINQ lets you query collections with a readable, SQL-like syntax.
var numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// Filter
var evens = numbers.Where(n => n % 2 == 0);
// [2, 4, 6, 8, 10]
// Transform
var squares = numbers.Select(n => n * n);
// [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
// Chain
var result = numbers
.Where(n => n % 2 == 0)
.Select(n => n * n)
.Take(3);
// [4, 16, 36]
// Aggregation
Console.WriteLine(numbers.Sum()); // 55
Console.WriteLine(numbers.Average()); // 5.5
Console.WriteLine(numbers.Max()); // 10
Console.WriteLine(numbers.Min()); // 1
Console.WriteLine(numbers.Count()); // 10
// Any / All
Console.WriteLine(numbers.Any(n => n > 9)); // True
Console.WriteLine(numbers.All(n => n > 0)); // True
// First / Single / Last
Console.WriteLine(numbers.First(n => n > 5)); // 6
Console.WriteLine(numbers.Last()); // 10
Console.WriteLine(numbers.FirstOrDefault(n => n > 100)); // 0 (default)
LINQ with objects
var products = new List<(string Name, string Category, decimal Price)>
{
("Widget", "Tools", 9.99m),
("Gadget", "Electronics", 49.99m),
("Doohickey", "Tools", 14.99m),
("Thingamajig", "Electronics", 99.99m),
("Gizmo", "Tools", 24.99m),
};
// Group by category
var byCategory = products
.GroupBy(p => p.Category)
.Select(g => new
{
Category = g.Key,
Count = g.Count(),
TotalValue = g.Sum(p => p.Price)
});
foreach (var g in byCategory)
Console.WriteLine($"{g.Category}: {g.Count} items, ${g.TotalValue:F2}");
// Sort
var cheapest = products
.Where(p => p.Price < 30)
.OrderBy(p => p.Price);
// Query syntax (alternative to method chaining)
var tools = from p in products
where p.Category == "Tools"
orderby p.Price
select p;
9. Error Handling
// Basic try/catch/finally
try
{
int result = 10 / 0;
Console.WriteLine(result);
}
catch (DivideByZeroException ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
catch (Exception ex) // catch-all
{
Console.WriteLine($"Unexpected: {ex.Message}");
}
finally
{
Console.WriteLine("Always runs");
}
// Multiple specific exceptions
static int ParseAge(string input)
{
try
{
int age = int.Parse(input);
if (age < 0 || age > 150)
throw new ArgumentOutOfRangeException(nameof(input), "Age must be 0–150");
return age;
}
catch (FormatException)
{
throw new ArgumentException($"'{input}' is not a valid age", nameof(input));
}
}
// Custom exception
public class InsufficientFundsException : Exception
{
public decimal Amount { get; }
public InsufficientFundsException(decimal amount)
: base($"Insufficient funds: need {amount:C} more")
{
Amount = amount;
}
}
// Exception filters (C# 6+)
try
{
// ...
}
catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
{
Console.WriteLine("Resource not found");
}
Common exceptions:
| Exception | When it occurs |
|---|---|
ArgumentNullException |
Null passed where not allowed |
ArgumentOutOfRangeException |
Value outside valid range |
InvalidOperationException |
Object in wrong state |
NullReferenceException |
Calling method on null object |
IndexOutOfRangeException |
Array index too large/small |
FormatException |
String in wrong format |
IOException |
File/network I/O failure |
OverflowException |
Arithmetic overflow (checked context) |
10. File I/O
using System.IO;
// Write a file
await File.WriteAllTextAsync("notes.txt", "Hello from C#\nLine 2");
// Read entire file
string content = await File.ReadAllTextAsync("notes.txt");
Console.WriteLine(content);
// Read all lines
string[] lines = await File.ReadAllLinesAsync("notes.txt");
foreach (string line in lines)
Console.WriteLine(line);
// Append
await File.AppendAllTextAsync("notes.txt", "\nLine 3");
// Check existence
if (File.Exists("notes.txt"))
Console.WriteLine("File exists");
// Delete
File.Delete("notes.txt");
// Work with directories
Directory.CreateDirectory("mydir");
string[] files = Directory.GetFiles("mydir", "*.txt");
// Path operations
string path = Path.Combine("mydir", "notes.txt");
Console.WriteLine(Path.GetExtension(path)); // .txt
Console.WriteLine(Path.GetFileName(path)); // notes.txt
Console.WriteLine(Path.GetDirectoryName(path)); // mydir
11. Async / Await
C# has first-class async support. All I/O should be async to avoid blocking threads.
using System.Net.Http;
using System.Text.Json;
// Basic async method
static async Task<string> FetchDataAsync(string url)
{
using var client = new HttpClient();
string json = await client.GetStringAsync(url);
return json;
}
// Run multiple tasks in parallel
static async Task Main()
{
string[] urls = {
"https://api.example.com/users",
"https://api.example.com/posts",
"https://api.example.com/comments"
};
// Sequential — slow
foreach (var url in urls)
await FetchDataAsync(url);
// Parallel — fast (all at once)
var tasks = urls.Select(FetchDataAsync);
string[] results = await Task.WhenAll(tasks);
Console.WriteLine($"Got {results.Length} responses");
}
// Return types
async Task DoWorkAsync() { } // no return value
async Task<int> GetCountAsync() { return 42; } // returns int
async ValueTask<bool> CheckAsync() { return true; } // ValueTask for hot paths
async/await rules:
| Rule | Explanation |
|---|---|
async marks a method |
Enables await inside |
await unwraps a Task<T> |
Gets the result, yields thread |
Never use .Result or .Wait() |
Causes deadlocks |
Return Task, not void |
Allows proper error propagation |
Use ConfigureAwait(false) in libraries |
Avoid context capture |
CancellationToken for cancellation |
Pass to all async methods |
// Cancellation
static async Task LongOperationAsync(CancellationToken ct = default)
{
for (int i = 0; i < 10; i++)
{
ct.ThrowIfCancellationRequested();
await Task.Delay(1000, ct);
Console.WriteLine($"Step {i + 1}");
}
}
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3));
try
{
await LongOperationAsync(cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("Cancelled after 3 seconds");
}
12. Modern C# features
Pattern matching
// is pattern
object obj = 42;
if (obj is int number && number > 0)
Console.WriteLine($"Positive int: {number}");
// switch expression with patterns
static string Describe(object value) => value switch
{
int n when n < 0 => "negative int",
int n => $"int: {n}",
string s => $"string: '{s}'",
null => "null",
_ => "unknown"
};
// List patterns (C# 11)
int[] arr = { 1, 2, 3 };
if (arr is [1, .., 3])
Console.WriteLine("Starts with 1, ends with 3");
Records and init-only
// Record struct (value type, C# 10)
public record struct Coordinate(double Lat, double Lon);
// Init-only properties
public class Config
{
public string Host { get; init; } = "localhost";
public int Port { get; init; } = 8080;
}
var config = new Config { Host = "example.com", Port = 443 };
// config.Host = "other"; // compile error — init only
Nullable reference types (C# 8+)
#nullable enable
string name = "Alice"; // non-nullable — never null
string? maybeNull = null; // nullable — can be null
// Compiler warns if you don't check
if (maybeNull != null)
Console.WriteLine(maybeNull.Length); // safe
// Null-forgiving operator (use sparingly)
Console.WriteLine(maybeNull!.Length); // tells compiler "trust me"
// Null-conditional operator
int? len = maybeNull?.Length; // null if maybeNull is null
Global using / File-scoped namespaces (C# 10)
// Program.cs — file-scoped namespace (no braces)
namespace MyApp;
public class App
{
public static void Run() => Console.WriteLine("Running!");
}
// GlobalUsings.cs — apply to entire project
global using System;
global using System.Collections.Generic;
global using System.Linq;
13. Project: Command-Line To-Do Manager
// TodoManager.cs
using System.Text.Json;
namespace TodoApp;
record Todo(int Id, string Title, bool Done = false);
class TodoManager
{
private List<Todo> _todos = new();
private int _nextId = 1;
private const string FilePath = "todos.json";
public TodoManager()
{
Load();
}
public void Add(string title)
{
_todos.Add(new Todo(_nextId++, title));
Save();
Console.WriteLine($"Added: {title}");
}
public void Complete(int id)
{
var index = _todos.FindIndex(t => t.Id == id);
if (index < 0)
{
Console.WriteLine($"Todo #{id} not found");
return;
}
_todos[index] = _todos[index] with { Done = true };
Save();
Console.WriteLine($"Completed: {_todos[index].Title}");
}
public void Delete(int id)
{
var removed = _todos.RemoveAll(t => t.Id == id);
if (removed == 0) Console.WriteLine($"Todo #{id} not found");
else { Save(); Console.WriteLine($"Deleted #{id}"); }
}
public void List(bool? showDone = null)
{
var items = showDone.HasValue
? _todos.Where(t => t.Done == showDone.Value)
: _todos;
if (!items.Any())
{
Console.WriteLine("No todos found.");
return;
}
foreach (var todo in items.OrderBy(t => t.Id))
{
string status = todo.Done ? "✓" : "○";
Console.WriteLine($"[{status}] {todo.Id}. {todo.Title}");
}
}
private void Save()
{
var json = JsonSerializer.Serialize(_todos, new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText(FilePath, json);
}
private void Load()
{
if (!File.Exists(FilePath)) return;
var json = File.ReadAllText(FilePath);
_todos = JsonSerializer.Deserialize<List<Todo>>(json) ?? new();
_nextId = _todos.Count > 0 ? _todos.Max(t => t.Id) + 1 : 1;
}
}
// Program.cs
var manager = new TodoManager();
if (args.Length == 0)
{
Console.WriteLine("Usage: todo [add <title>] [done <id>] [delete <id>] [list]");
return;
}
switch (args[0])
{
case "add" when args.Length > 1:
manager.Add(string.Join(" ", args[1..]));
break;
case "done" when args.Length > 1 && int.TryParse(args[1], out int doneId):
manager.Complete(doneId);
break;
case "delete" when args.Length > 1 && int.TryParse(args[1], out int delId):
manager.Delete(delId);
break;
case "list":
manager.List();
break;
default:
Console.WriteLine("Unknown command");
break;
}
Run it:
dotnet run -- add "Buy groceries"
dotnet run -- add "Write unit tests"
dotnet run -- list
# [○] 1. Buy groceries
# [○] 2. Write unit tests
dotnet run -- done 1
dotnet run -- list
# [✓] 1. Buy groceries
# [○] 2. Write unit tests
14. Project: Simple REST API with ASP.NET Core
dotnet new webapi -n NotesApi --no-openapi
cd NotesApi
dotnet run
Replace Program.cs:
// Program.cs — Minimal API
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// In-memory store
var notes = new List<Note>();
var nextId = 1;
app.MapGet("/notes", () => notes);
app.MapGet("/notes/{id}", (int id) =>
{
var note = notes.Find(n => n.Id == id);
return note is not null ? Results.Ok(note) : Results.NotFound();
});
app.MapPost("/notes", (NoteRequest req) =>
{
var note = new Note(nextId++, req.Title, req.Content);
notes.Add(note);
return Results.Created($"/notes/{note.Id}", note);
});
app.MapPut("/notes/{id}", (int id, NoteRequest req) =>
{
var index = notes.FindIndex(n => n.Id == id);
if (index < 0) return Results.NotFound();
notes[index] = notes[index] with { Title = req.Title, Content = req.Content };
return Results.Ok(notes[index]);
});
app.MapDelete("/notes/{id}", (int id) =>
{
var removed = notes.RemoveAll(n => n.Id == id);
return removed > 0 ? Results.NoContent() : Results.NotFound();
});
app.Run();
record Note(int Id, string Title, string Content);
record NoteRequest(string Title, string Content);
Test with curl:
# Create
curl -X POST http://localhost:5000/notes \
-H "Content-Type: application/json" \
-d '{"title":"Hello","content":"My first note"}'
# List all
curl http://localhost:5000/notes
# Get by ID
curl http://localhost:5000/notes/1
# Update
curl -X PUT http://localhost:5000/notes/1 \
-H "Content-Type: application/json" \
-d '{"title":"Updated","content":"Changed content"}'
# Delete
curl -X DELETE http://localhost:5000/notes/1
15. Project: CSV Data Analyzer
// DataAnalyzer.cs
using System.Globalization;
var path = args.Length > 0 ? args[0] : "data.csv";
if (!File.Exists(path))
{
// Create sample data
await File.WriteAllTextAsync(path, """
Name,Category,Revenue
Widget,Electronics,1250.50
Gadget,Electronics,3400.00
Doohickey,Tools,850.75
Thingamajig,Electronics,2100.25
Gizmo,Tools,1750.00
Whatsit,Office,450.00
Contraption,Tools,3200.50
""");
Console.WriteLine($"Created sample {path}");
}
var lines = await File.ReadAllLinesAsync(path);
var headers = lines[0].Split(',');
var records = lines[1..]
.Where(line => !string.IsNullOrWhiteSpace(line))
.Select(line =>
{
var cols = line.Split(',');
return new
{
Name = cols[0].Trim(),
Category = cols[1].Trim(),
Revenue = decimal.Parse(cols[2].Trim(), CultureInfo.InvariantCulture)
};
})
.ToList();
Console.WriteLine($"\nLoaded {records.Count} records\n");
// Summary stats
Console.WriteLine("=== Overall ===");
Console.WriteLine($"Total Revenue: ${records.Sum(r => r.Revenue):N2}");
Console.WriteLine($"Average Revenue: ${records.Average(r => r.Revenue):N2}");
Console.WriteLine($"Max Revenue: ${records.Max(r => r.Revenue):N2}");
Console.WriteLine($"Min Revenue: ${records.Min(r => r.Revenue):N2}");
// By category
Console.WriteLine("\n=== By Category ===");
var byCategory = records
.GroupBy(r => r.Category)
.Select(g => new
{
Category = g.Key,
Count = g.Count(),
Total = g.Sum(r => r.Revenue),
Avg = g.Average(r => r.Revenue)
})
.OrderByDescending(g => g.Total);
foreach (var g in byCategory)
Console.WriteLine($"{g.Category,-15} Items: {g.Count} Total: ${g.Total:N2} Avg: ${g.Avg:N2}");
// Top 3
Console.WriteLine("\n=== Top 3 Items ===");
var top3 = records.OrderByDescending(r => r.Revenue).Take(3);
int rank = 1;
foreach (var item in top3)
Console.WriteLine($"{rank++}. {item.Name} ({item.Category}): ${item.Revenue:N2}");
Learning path
| Stage | Duration | Focus |
|---|---|---|
| Beginner | 2–4 weeks | Syntax, types, control flow, methods |
| OOP | 2–4 weeks | Classes, interfaces, inheritance, generics |
| Intermediate | 4–6 weeks | LINQ, async/await, collections, file I/O |
| Framework | 4–8 weeks | ASP.NET Core (web) or Unity (games) |
| Advanced | Ongoing | Design patterns, Entity Framework, testing |
| Specialization | Ongoing | Your specific domain (cloud, mobile, games) |
Free resources:
| Resource | Best for |
|---|---|
| docs.microsoft.com/dotnet | Official docs + tutorials |
| learn.microsoft.com | Interactive C# courses |
| dotnet.microsoft.com | Downloads, news |
| exercism.org/tracks/csharp | Practice exercises |
| csharppad.com | Online REPL |
Common mistakes
| Mistake | Problem | Fix |
|---|---|---|
Using == to compare strings with null |
NPE risk | Use string.IsNullOrEmpty() or is null |
.Result or .Wait() on async |
Deadlock | Always await |
Forgetting await on async call |
Task not awaited, errors swallowed | Enable warning CS4014 |
Mutating a foreach collection |
InvalidOperationException | Collect to list first, then mutate |
catch (Exception) with no rethrow |
Swallows errors silently | Log and rethrow or handle specifically |
public fields instead of properties |
Breaks encapsulation | Use { get; set; } |
Not using using for IDisposable |
Resource leaks | Always using var for streams, clients |
| Magic strings everywhere | Hard to maintain | Use constants or enums |
C# vs related terms
| Term | What it is |
|---|---|
| C# | The programming language |
| .NET | The platform/runtime C# runs on |
| CLR | Common Language Runtime — executes .NET code |
| NuGet | Package manager for .NET (like npm for Node.js) |
| MSBuild | Build system for .NET projects |
| ASP.NET Core | Web framework built on .NET |
| Entity Framework Core | ORM for databases in .NET |
| Blazor | C# web UI framework (runs in browser via WASM) |
| MAUI | Cross-platform desktop/mobile framework |
| Unity | Game engine that uses C# for scripting |
FAQ
Q: Is C# only for Windows? No. Since .NET Core (2016) and .NET 5+ (2020), C# runs natively on Windows, macOS, and Linux. ASP.NET Core apps run everywhere.
Q: Should I learn C# or Java? Both are strongly typed, OOP languages with similar concepts. C# is stronger for game development (Unity), Windows/Azure ecosystems, and has more modern language features. Java dominates Android (legacy) and certain enterprise sectors. Either is a solid first language.
Q: Do I need Visual Studio? No. VS Code with the C# Dev Kit extension works great on all platforms. Visual Studio (Windows/Mac only) has more tooling but is heavier. Both are free.
Q: What's the difference between .NET Framework and .NET? .NET Framework (1.0–4.8) is Windows-only legacy. Modern .NET (5, 6, 7, 8) is cross-platform. Use .NET 8 for all new projects.
Q: How long does it take to learn C#? Basic syntax: 1–2 weeks. Writing useful programs: 1–2 months. Professional proficiency: 6–12 months with practice. The investment pays off — C# developers are well-paid across games, enterprise, and web.
Q: Can C# be used for machine learning? Yes, via ML.NET (Microsoft's ML framework) or by calling Python ML services from C#. Most ML teams still prefer Python, but ML.NET is viable for .NET-integrated ML workloads.