C# logoC#v14INTERMEDIATE

C# Programming Language

C# cheat sheet with syntax, LINQ, async/await, classes, generics, pattern matching, and .NET best practices with code examples.

45 min read
Loading your progress

Getting Started

Basic setup and first program

Hello World

Your first C# program

csharp
// Simple console output
Console.WriteLine("Hello, World!");
Console.Write("Same line");
Console.Write(" output");

// Reading input
Console.Write("Enter your name: ");
string name = Console.ReadLine();
Console.WriteLine($"Hello, {name}!");
💡 Console.WriteLine adds a new line, Console.Write doesn't
⚡ Top-level programs (C# 9+) remove boilerplate
📌 $ before strings enables string interpolation
🟢 Console.ReadLine() always returns a string

Comments

Code documentation

csharp
// Single line comment

/* Multi-line
   comment block */

/// <summary>
/// XML documentation comment
/// </summary>

// TODO: Remember to implement this
// HACK: Temporary fix - refactor later
// NOTE: Important information
💡 Use /// for IntelliSense documentation
⚡ Regions help organize large files
📌 TODO comments are tracked by Visual Studio
🟢 XML comments generate documentation files

Variables & Data Types

Basic data types and variable declarations

Basic Types

Common data types

csharp
// Numbers
int whole = 42;              // Whole numbers (-2,147,483,648 to 2,147,483,647)
double pi = 3.14;       // Decimals (15-16 digits precision)
float smaller = 3.14f;       // Smaller decimals (6-7 digits)
decimal money = 19.99m;      // Financial calculations (28-29 digits)
long bigNumber = 9000000000L; // Large whole numbers

// Text
string text = "Hello";
char letter = 'A';

// Boolean
bool isTrue = true;
bool isFalse = false;
💡 Use decimal for money to avoid rounding errors
⚡ int is the most common integer type
📌 Add f for float, m for decimal, L for long
🟢 string is for text, char is for single characters

Declaring and initializing variables

csharp
// Declaration and initialization
int age = 25;
string name = "John";

// Declaration then assignment
int score;
score = 100;

// Multiple declarations
int x = 1, y = 2, z = 3;

// Type inference with var
var message = "Hello";  // Compiler infers string
var number = 42;        // Compiler infers int
var price = 19.99;      // Compiler infers double

// Constants (cannot change)
const double PI = 3.14159;
const int MAX_SIZE = 100;
💡 Use var when type is obvious from right side
⚡ const for compile-time constants, readonly for runtime
📌 Variables are case-sensitive (age ≠ Age)
🟢 Follow C# naming conventions: camelCase for variables

Control Flow

Conditionals and loops

If Statements

Conditional execution

csharp
// Basic if
if (age >= 18)
{
    Console.WriteLine("Adult");
}

// If-else
if (score >= 90)
{
    Console.WriteLine("A");
}
else if (score >= 80)
{
    Console.WriteLine("B");
}
else
{
    Console.WriteLine("C or below");
}

// Ternary operator
string status = age >= 18 ? "Adult" : "Minor";

// Null checking
if (name != null)
{
    Console.WriteLine(name.Length);
}
💡 Use && for AND, || for OR, ! for NOT
⚡ Ternary operator (? :) for simple if-else
📌 Always use braces {} even for single statements
🟢 Pattern matching makes type checking cleaner

Loops

Iteration and repetition

csharp
// For loop
for (int i = 0; i < 5; i++)
{
    Console.WriteLine(i);
}

// While loop
int count = 0;
while (count < 5)
{
    Console.WriteLine(count);
    count++;
}

// Do-while (runs at least once)
do
{
    Console.WriteLine("Run at least once");
} while (false);

// Foreach loop
int[] numbers = { 1, 2, 3, 4, 5 };
foreach (int num in numbers)
{
    Console.WriteLine(num);
}
💡 Use foreach when you don't need the index
⚡ break exits loop, continue skips to next iteration
📌 While for unknown iterations, for when count is known
🟢 Do-while guarantees at least one execution

Methods (Functions)

Defining and calling methods

Method Basics

Creating and using methods

csharp
// Simple method
void SayHello()
{
    Console.WriteLine("Hello!");
}

// Method with parameters
void Greet(string name)
{
    Console.WriteLine($"Hello, {name}!");
}

// Method with return value
int Add(int a, int b)
{
    return a + b;
}

// Calling methods
SayHello();
Greet("Alice");
int sum = Add(5, 3);
💡 void means method doesn't return a value
⚡ Static methods belong to class, instance methods to objects
📌 Method overloading allows same name with different parameters
🟢 Use => for single-expression methods

Advanced parameter passing

csharp
// Pass by value (default)
void ChangeValue(int x)
{
    x = 100;  // Doesn't affect original
}

// Pass by reference
void ChangeRef(ref int x)
{
    x = 100;  // Changes original
}

// Out parameters (must assign)
bool TryParse(string input, out int result)
{
    return int.TryParse(input, out result);
}

// Multiple returns with tuples
(int sum, int product) Calculate(int a, int b)
{
    return (a + b, a * b);
}

// Named arguments
PrintInfo(age: 25, name: "Bob");
💡 ref modifies original, out returns multiple values
⚡ params allows variable number of arguments
📌 in parameters for read-only reference passing
🟢 Tuples are great for returning multiple values

Object-Oriented Programming

Classes, objects, inheritance, and polymorphism

Defining classes and creating objects

csharp
// Simple class
public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }

    public void Introduce()
    {
        Console.WriteLine($"Hi, I'm {Name}");
    }
}

// Usage
Person person = new Person();
person.Name = "Alice";
person.Age = 30;
person.Introduce();
💡 Auto-properties provide shorthand for simple get/set operations
⚡ Constructor initializes object state when created
📌 Access modifiers control visibility (public, private, protected)
🟢 Object initializer syntax allows setting properties during creation

Class inheritance and method overriding

csharp
// Basic inheritance
public class Animal
{
    public virtual void MakeSound()
    {
        Console.WriteLine("Some sound");
    }
}

public class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("Woof!");
    }
}

// Usage
Animal myPet = new Dog();
myPet.MakeSound(); // "Woof!"
💡 Abstract classes cannot be instantiated directly
⚡ Virtual methods can be overridden in derived classes
📌 base keyword calls parent class constructor or methods
🟢 Polymorphism allows treating derived objects as base type

Collections and Generics

Working with collections and generic types

List, Dictionary, and other generic collections

csharp
// List<T>
List<string> names = new List<string> { "Alice", "Bob" };
names.Add("Charlie");

// Dictionary<TKey, TValue>
Dictionary<string, int> ages = new Dictionary<string, int>();
ages["Alice"] = 30;
ages["Bob"] = 25;

// Array
int[] numbers = { 1, 2, 3, 4, 5 };

// HashSet<T>
HashSet<int> uniqueNumbers = new HashSet<int> { 1, 2, 3 };
💡 Generic collections provide type safety and performance
⚡ Dictionary provides O(1) average lookup time
📌 HashSet automatically handles uniqueness of elements
🟢 Queue (FIFO) and Stack (LIFO) for specific ordering needs

LINQ and Lambda Expressions

Language Integrated Query and functional programming

Querying collections with LINQ

csharp
// Query syntax
var adults = from p in people
             where p.Age >= 18
             select p;

// Method syntax
var adults2 = people.Where(p => p.Age >= 18);

// Common operations
var names = people.Select(p => p.Name);
var sorted = people.OrderBy(p => p.Age);
var first = people.First();
💡 LINQ provides SQL-like querying for .NET collections
⚡ Method syntax often more flexible than query syntax
📌 Lambda expressions (=>) create anonymous functions
🟢 LINQ is lazy-evaluated until enumerated

Async/Await and Tasks

Asynchronous programming patterns

Asynchronous methods and Task handling

csharp
// Async method
public async Task<string> GetDataAsync()
{
    await Task.Delay(1000);
    return "Data";
}

// Using async method
string result = await GetDataAsync();

// Multiple async operations
Task<string> task1 = GetDataAsync();
Task<string> task2 = GetDataAsync();
string[] results = await Task.WhenAll(task1, task2);
💡 async/await makes asynchronous code look synchronous
⚡ Use Task.WhenAll for concurrent operations
📌 Avoid async void except for event handlers
🟢 ConfigureAwait(false) can improve performance in libraries

Exception Handling

Error handling and custom exceptions

Basic exception handling patterns

csharp
// Basic try-catch
try
{
    int result = 10 / 0;
}
catch (DivideByZeroException ex)
{
    Console.WriteLine($"Error: {ex.Message}");
}
finally
{
    Console.WriteLine("Cleanup");
}
💡 Catch specific exceptions before general ones
⚡ using statement ensures proper resource disposal
📌 finally block always executes, even after return
🟢 Exception filters allow conditional catch blocks

Interfaces and Abstract Classes

Contracts and abstract implementations

Creating and implementing interfaces

csharp
// Simple interface
public interface IPlayable
{
    void Play();
    void Pause();
}

public class Video : IPlayable
{
    public void Play() => Console.WriteLine("Playing video");
    public void Pause() => Console.WriteLine("Pausing video");
}
💡 Interfaces define contracts without implementation
⚡ C# 8+ allows default interface methods
📌 Classes can implement multiple interfaces
🟢 Use interfaces for dependency injection and testing

Delegates and Events

Function pointers and event-driven programming

Working with delegates and built-in delegate types

csharp
// Delegate declaration
public delegate void MyDelegate(string message);

// Using delegates
MyDelegate handler = Method1;
handler += Method2; // Multicast
handler("Hello");

// Built-in delegates
Action<string> logger = Console.WriteLine;
Func<int, int, int> add = (x, y) => x + y;
Predicate<int> isEven = n => n % 2 == 0;
💡 Delegates are type-safe function pointers
⚡ Action for void methods, Func for methods with return values
📌 Multicast delegates can call multiple methods
🟢 Events are special multicast delegates with restrictions

Properties and Indexers

Advanced property patterns and indexers

Auto-properties, computed properties, and validation

csharp
// Auto-properties
public string Name { get; set; }
public int Age { get; private set; }

// Read-only property
public string Id { get; } = Guid.NewGuid().ToString();

// Computed property
public string FullName => $"{FirstName} {LastName}";

// Property with validation
private int _age;
public int Age
{
    get => _age;
    set => _age = value >= 0 ? value : 0;
}
💡 Init-only properties can only be set during object initialization
⚡ Computed properties recalculate on each access
📌 Property setters can include validation logic
🟢 Indexers allow array-like access to custom classes

Extension Methods and Nullable Types

Extending existing types and handling null values

Adding methods to existing types

csharp
// Extension method
public static class StringExtensions
{
    public static bool IsValidEmail(this string email)
    {
        return email?.Contains("@") ?? false;
    }
}

// Usage
string email = "test@example.com";
bool valid = email.IsValidEmail();

// Nullable types
int? nullableInt = null;
string? nullableString = null;

// Null operators
int value = nullableInt ?? 0; // Null coalescing
string length = nullableString?.Length.ToString() ?? "0";
💡 Extension methods must be in static classes with static methods
⚡ First parameter with this keyword defines extended type
📌 Null-conditional operator (?.) safely accesses members
🟢 Nullable reference types help catch null issues at compile time

Attributes and Reflection

Metadata and runtime type inspection

Using and creating attributes for metadata

csharp
// Built-in attributes
[Obsolete("Use NewMethod instead")]
public void OldMethod() { }

[Serializable]
public class MyClass { }

// Custom attribute
[AttributeUsage(AttributeTargets.Property)]
public class RequiredAttribute : Attribute { }

// Using custom attribute
public class User
{
    [Required]
    public string Name { get; set; }
}
💡 Attributes add metadata to code elements
⚡ Reflection allows runtime type inspection and manipulation
📌 Custom attributes enable declarative programming
🟢 Use reflection sparingly as it impacts performance

Pattern Matching

Advanced pattern matching and switch expressions

Modern pattern matching with switch expressions

csharp
// Switch expression
string result = number switch
{
    1 => "One",
    2 => "Two",
    < 0 => "Negative",
    > 100 => "Large",
    _ => "Other"
};

// Property patterns
string description = person switch
{
    { Age: < 18 } => "Minor",
    { Age: >= 65 } => "Senior",
    _ => "Adult"
};

// Type patterns
object obj = GetValue();
string type = obj switch
{
    int i => $"Integer: {i}",
    string s => $"String: {s}",
    null => "Null",
    _ => "Unknown"
};
💡 Switch expressions provide concise pattern matching
⚡ Property patterns match on object properties
📌 Guard clauses (when) add additional conditions
🟢 List patterns enable array/collection matching

Records and Value Types

Modern data types and value semantics

Record Types

Immutable reference types with value semantics

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

// Using records
var person = new Person("Alice", 30);
var older = person with { Age = 31 };

// Struct
public struct Point
{
    public double X { get; init; }
    public double Y { get; init; }
}

// Value equality
var p1 = new Person("Bob", 25);
var p2 = new Person("Bob", 25);
bool equal = p1 == p2; // true
💡 Records provide value equality and immutability by default
⚡ with expressions create copies with modified properties
📌 Records automatically implement ToString, Equals, GetHashCode
🟢 Use readonly structs for better performance

Memory Management

Memory optimization and performance considerations

Resource management and deterministic cleanup

csharp
// Using statement
using var file = File.OpenRead("data.txt");
// Automatically disposed at end of scope

// IDisposable pattern
public class Resource : IDisposable
{
    public void Dispose()
    {
        // Cleanup
        GC.SuppressFinalize(this);
    }
}

// Span<T> for efficient memory
Span<int> numbers = stackalloc int[100];

// Memory<T> for async scenarios
Memory<byte> buffer = new byte[1024];
💡 IDisposable pattern ensures deterministic resource cleanup
⚡ Span<T> enables zero-allocation array operations
📌 ArrayPool reduces garbage collection pressure
🟢 Using declarations simplify resource management

Latest C# Features

Modern C# features and syntax improvements

Latest language enhancements and syntax improvements

csharp
// Global using (C# 10)
global using System;
global using System.Collections.Generic;

// File-scoped namespace (C# 10)
namespace MyApp.Services;

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

// Required members (C# 11)
public class Person
{
    public required string Name { get; init; }
}

// Collection expressions (C# 12)
int[] numbers = [1, 2, 3, 4, 5];
💡 Global using reduces repetitive using statements
⚡ Raw string literals handle multi-line strings elegantly
📌 Required members ensure proper object initialization
🟢 Collection expressions provide concise initialization