# Lang Csharp Dev

> Foundational C# patterns covering LINQ, async/await, nullable types, records, and pattern matching. Use when writing C# code or needing guidance on C# features. This is the entry point for C# development.

- Skill: `arustydev/lang-csharp-dev` (Agent Skill)
- Install (CLI): `npx skillmds@latest add arustydev/lang-csharp-dev`
- Raw SKILL.md: https://api.skillmd.com/api/skills/arustydev/lang-csharp-dev/raw
- Safety review: PASS (external: skill-scanner PASS, skillspector CAUTION)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: aRustyDev (https://skillmd.com/u/arustydev)
- Updated: 2026-08-19
- Page: https://skillmd.com/skills/arustydev/lang-csharp-dev

---


# C# Development Skill

Comprehensive foundational patterns for modern C# development covering language features, best practices, and common idioms.

## Quick Reference

### Essential Patterns
```csharp
// Nullable reference types
string? nullableString = null;
string nonNullableString = "value";

// Records
public record Person(string Name, int Age);

// Pattern matching
var result = value switch
{
    null => "null",
    0 => "zero",
    > 0 => "positive",
    _ => "negative"
};

// LINQ method syntax
var results = collection
    .Where(x => x.IsActive)
    .Select(x => x.Name)
    .OrderBy(x => x)
    .ToList();

// Async/await
public async Task<string> GetDataAsync()
{
    return await httpClient.GetStringAsync(url);
}
```

### File Extensions
- `.cs` - C# source files
- `.csproj` - Project files
- `.sln` - Solution files
- `.cshtml` - Razor views
- `.razor` - Blazor components

## 1. Nullable Reference Types

### Overview
Nullable reference types help prevent null reference exceptions by making nullability explicit in the type system.

### Enabling Nullable Context
```csharp
// In .csproj
<PropertyGroup>
    <Nullable>enable</Nullable>
</PropertyGroup>

// Or per-file
#nullable enable

// Disable warnings
#nullable disable
```

### Nullable Annotations
```csharp
// Nullable reference type
string? nullableString = null;

// Non-nullable reference type (default when nullable context enabled)
string nonNullableString = "value";

// Array of nullable strings
string?[] arrayOfNullableStrings = new string?[10];

// Nullable array of strings
string[]? nullableArrayOfStrings = null;

// Nullable array of nullable strings
string?[]? fullyNullable = null;
```

### Null-Forgiving Operator
```csharp
// When you know a value isn't null but compiler doesn't
string value = GetValue()!;

// Use sparingly - defeats purpose of nullable reference types
public void Process(string? input)
{
    // Bad - suppresses warning without checking
    Console.WriteLine(input!.Length);

    // Good - check first
    if (input is not null)
    {
        Console.WriteLine(input.Length);
    }
}
```

### Null Checking Patterns
```csharp
// Traditional null check
if (value != null)
{
    Console.WriteLine(value.Length);
}

// Pattern matching
if (value is not null)
{
    Console.WriteLine(value.Length);
}

// Null-conditional operator
Console.WriteLine(value?.Length);

// Null-coalescing operator
string result = value ?? "default";

// Null-coalescing assignment
value ??= "default";
```

### Method Annotations
```csharp
// Return nullable
public string? FindUser(int id)
{
    return users.FirstOrDefault(u => u.Id == id)?.Name;
}

// Accept nullable
public void UpdateName(string? newName)
{
    if (newName is null)
    {
        throw new ArgumentNullException(nameof(newName));
    }

    name = newName;
}

// Attributes for advanced scenarios
public bool TryGetValue(string key, [NotNullWhen(true)] out string? value)
{
    // Tells compiler that value is not null when method returns true
    return dictionary.TryGetValue(key, out value);
}

[return: NotNullIfNotNull(nameof(input))]
public string? Transform(string? input)
{
    // Return value nullability matches input nullability
    return input?.ToUpper();
}
```

### Generic Nullability
```csharp
// Nullable value type
public class Container<T>
{
    public T? Value { get; set; }  // Works for both reference and value types
}

// Constrain to non-nullable reference types
public class Container<T> where T : notnull
{
    public T Value { get; set; } = default!;
}

// Nullable reference type constraint
public class Container<T> where T : class?
{
    public T? Value { get; set; }
}
```

### Best Practices
```csharp
// DO: Enable nullable context globally
// In .csproj
<Nullable>enable</Nullable>

// DO: Check for null before use
public void Process(string? input)
{
    ArgumentNullException.ThrowIfNull(input);  // C# 11+
    // or
    if (input is null)
    {
        throw new ArgumentNullException(nameof(input));
    }

    Console.WriteLine(input.Length);
}

// DO: Use nullable return types when appropriate
public User? FindUser(int id) => users.FirstOrDefault(u => u.Id == id);

// DON'T: Overuse null-forgiving operator
// Bad
public void Bad(string? input)
{
    Console.WriteLine(input!.Length);
}

// Good
public void Good(string? input)
{
    if (input is not null)
    {
        Console.WriteLine(input.Length);
    }
}

// DO: Initialize non-nullable properties
public class User
{
    public string Name { get; set; } = string.Empty;  // Good
    public string Email { get; set; }  // Warning: non-nullable field must contain non-null value
}
```

## 2. LINQ (Language Integrated Query)

### Overview
LINQ provides a consistent model for querying data across different data sources using both query and method syntax.

### Query Syntax
```csharp
// Basic query
var results = from user in users
              where user.Age > 18
              select user.Name;

// Multiple from clauses (SelectMany)
var pairs = from user in users
            from order in user.Orders
            where order.Total > 100
            select new { user.Name, order.Id };

// Join
var results = from user in users
              join order in orders on user.Id equals order.UserId
              select new { user.Name, order.Total };

// Group join (left join)
var results = from user in users
              join order in orders on user.Id equals order.UserId into userOrders
              select new { user.Name, Orders = userOrders };

// Group by
var grouped = from user in users
              group user by user.Department into g
              select new { Department = g.Key, Count = g.Count() };

// Order by
var ordered = from user in users
              orderby user.LastName, user.FirstName descending
              select user;

// Let clause
var results = from user in users
              let fullName = $"{user.FirstName} {user.LastName}"
              where fullName.Length > 10
              select fullName;
```

### Method Syntax
```csharp
// Filtering
var adults = users.Where(u => u.Age >= 18);

// Projection
var names = users.Select(u => u.Name);
var dto = users.Select(u => new UserDto { Name = u.Name, Email = u.Email });

// Ordering
var sorted = users.OrderBy(u => u.LastName)
                  .ThenByDescending(u => u.FirstName);

// Grouping
var grouped = users.GroupBy(u => u.Department)
                   .Select(g => new { Department = g.Key, Count = g.Count() });

// Joining
var results = users.Join(
    orders,
    u => u.Id,
    o => o.UserId,
    (u, o) => new { u.Name, o.Total }
);

// SelectMany (flattening)
var allOrders = users.SelectMany(u => u.Orders);
var pairs = users.SelectMany(
    u => u.Orders,
    (u, o) => new { u.Name, o.Id }
);

// Aggregation
var total = orders.Sum(o => o.Total);
var average = orders.Average(o => o.Total);
var max = orders.Max(o => o.Total);
var count = orders.Count(o => o.IsCompleted);

// Quantifiers
var hasAny = orders.Any(o => o.Total > 1000);
var allCompleted = orders.All(o => o.IsCompleted);

// Element operations
var first = users.First(u => u.Id == 1);  // Throws if not found
var firstOrNull = users.FirstOrDefault(u => u.Id == 1);  // Returns null/default
var single = users.Single(u => u.Email == email);  // Throws if 0 or >1 matches
```

### Deferred vs. Immediate Execution
```csharp
// Deferred execution - query not executed until enumerated
IEnumerable<User> query = users.Where(u => u.Age > 18);

// Query executes here when enumerating
foreach (var user in query) { }

// Immediate execution - query executes immediately
List<User> list = users.Where(u => u.Age > 18).ToList();
User[] array = users.Where(u => u.Age > 18).ToArray();
Dictionary<int, User> dict = users.ToDictionary(u => u.Id);

// Aggregation methods execute immediately
int count = users.Count();
decimal total = orders.Sum(o => o.Total);
```

### Complex LINQ Patterns
```csharp
// Conditional where clauses
var query = users.AsQueryable();
if (!string.IsNullOrEmpty(searchTerm))
{
    query = query.Where(u => u.Name.Contains(searchTerm));
}
if (minAge.HasValue)
{
    query = query.Where(u => u.Age >= minAge.Value);
}
var results = query.ToList();

// Nested queries
var usersWithExpensiveOrders = users
    .Where(u => u.Orders.Any(o => o.Total > 1000))
    .Select(u => new
    {
        u.Name,
        ExpensiveOrders = u.Orders.Where(o => o.Total > 1000)
    });

// Distinct
var uniqueAges = users.Select(u => u.Age).Distinct();
var uniqueUsers = users.DistinctBy(u => u.Email);  // C# 11+

// Set operations
var union = list1.Union(list2);
var intersect = list1.Intersect(list2);
var except = list1.Except(list2);

// Partitioning
var page = users.Skip(pageSize * pageNumber).Take(pageSize);

// Zip
var pairs = list1.Zip(list2, (x, y) => new { x, y });

// Chunk (C# 11+)
var batches = users.Chunk(100);
foreach (var batch in batches)
{
    ProcessBatch(batch);
}
```

### LINQ to Objects Performance
```csharp
// DO: Use List<T> or array for known collections
List<User> users = GetUsers();
var results = users.Where(u => u.Age > 18);  // Fast iteration

// DO: Materialize once if reusing query results
var activeUsers = users.Where(u => u.IsActive).ToList();
var count = activeUsers.Count;
var first = activeUsers.First();

// DON'T: Materialize unnecessarily
// Bad - Count() can work on IEnumerable
var badCount = users.Where(u => u.IsActive).ToList().Count();

// Good
var goodCount = users.Count(u => u.IsActive);

// DO: Filter before projecting
// Good
var names = users.Where(u => u.Age > 18).Select(u => u.Name);

// Less efficient
var names2 = users.Select(u => u.Name).Where(n => users.First(u => u.Name == n).Age > 18);

// DO: Use appropriate methods
// Good - short circuits
bool hasAdmin = users.Any(u => u.Role == "Admin");

// Bad - checks entire collection
bool hasAdmin2 = users.Where(u => u.Role == "Admin").Count() > 0;
```

### Queryable vs. Enumerable
```csharp
// IEnumerable<T> - LINQ to Objects (in-memory)
IEnumerable<User> enumerable = users.Where(u => u.Age > 18);

// IQueryable<T> - LINQ provider translates to data source query
IQueryable<User> queryable = dbContext.Users.Where(u => u.Age > 18);

// AsQueryable converts IEnumerable to IQueryable (still executes in memory)
IQueryable<User> query = users.AsQueryable().Where(u => u.Age > 18);

// AsEnumerable forces remaining query to execute in memory
var results = dbContext.Users
    .Where(u => u.Age > 18)  // Translated to SQL
    .AsEnumerable()
    .Where(u => ComplexInMemoryCheck(u));  // Executes in memory
```

## 3. Async/Await Patterns

### Overview
Async/await enables non-blocking asynchronous operations while maintaining readable code.

### Basic Async/Await
```csharp
// Async method returning Task
public async Task ProcessDataAsync()
{
    await Task.Delay(1000);
    Console.WriteLine("Processed");
}

// Async method returning Task<T>
public async Task<string> GetDataAsync()
{
    var result = await httpClient.GetStringAsync(url);
    return result;
}

// Async void - only for event handlers
private async void Button_Click(object sender, EventArgs e)
{
    await ProcessDataAsync();
}
```

### Task Basics
```csharp
// Creating tasks
Task task = Task.Run(() => DoWork());
Task<int> taskWithResult = Task.Run(() => CalculateValue());

// Completing immediately
Task<string> completed = Task.FromResult("value");
Task failed = Task.FromException(new Exception("error"));
Task canceled = Task.FromCanceled(cancellationToken);

// Waiting (blocks current thread - avoid in async code)
task.Wait();
int result = taskWithResult.Result;

// Async waiting (doesn't block thread)
await task;
int result = await taskWithResult;
```

### ConfigureAwait
```csharp
// Library code - don't capture synchronization context
public async Task<string> LibraryMethodAsync()
{
    var result = await httpClient.GetStringAsync(url)
        .ConfigureAwait(false);
    return result;
}

// UI/ASP.NET Core code - usually omit (capture context)
public async Task ButtonClickAsync()
{
    var data = await GetDataAsync();  // Returns to UI thread
    textBox.Text = data;  // Can update UI
}

// When to use ConfigureAwait(false)
// - Library code that doesn't need synchronization context
// - Improves performance by avoiding context capture
// - Prevents potential deadlocks

// When to omit ConfigureAwait or use ConfigureAwait(true)
// - UI code that needs to update controls
// - ASP.NET code that needs HttpContext
// - Code that depends on synchronization context
```

### Parallel Async Operations
```csharp
// Run tasks concurrently and wait for all
Task<string> task1 = GetDataAsync(url1);
Task<string> task2 = GetDataAsync(url2);
Task<string> task3 = GetDataAsync(url3);

await Task.WhenAll(task1, task2, task3);

string result1 = task1.Result;  // Already completed
string result2 = task2.Result;
string result3 = task3.Result;

// With results
var tasks = new[]
{
    GetDataAsync(url1),
    GetDataAsync(url2),
    GetDataAsync(url3)
};
string[] results = await Task.WhenAll(tasks);

// Wait for first to complete
Task<string> firstCompleted = await Task.WhenAny(task1, task2, task3);
string firstResult = await firstCompleted;

// Process as they complete
var tasks = urls.Select(url => GetDataAsync(url)).ToList();
while (tasks.Count > 0)
{
    Task<string> completedTask = await Task.WhenAny(tasks);
    tasks.Remove(completedTask);

    string result = await completedTask;
    ProcessResult(result);
}
```

### Cancellation
```csharp
// Creating cancellation token source
using var cts = new CancellationTokenSource();

// Cancel after timeout
cts.CancelAfter(TimeSpan.FromSeconds(30));

// Manual cancellation
cts.Cancel();

// Passing token to async method
await ProcessDataAsync(cts.Token);

// Implementing cancellation
public async Task ProcessDataAsync(CancellationToken cancellationToken)
{
    for (int i = 0; i < 1000; i++)
    {
        // Check for cancellation
        cancellationToken.ThrowIfCancellationRequested();

        // Or manual check
        if (cancellationToken.IsCancellationRequested)
        {
            // Cleanup
            return;
        }

        await ProcessItemAsync(i, cancellationToken);
    }
}

// Linking tokens
using var linkedCts = CancellationTokenSource
    .CreateLinkedTokenSource(token1, token2);
await ProcessAsync(linkedCts.Token);

// Registering callback
cancellationToken.Register(() =>
{
    Console.WriteLine("Cancellation requested");
});
```

### Error Handling
```csharp
// Try-catch with async
public async Task<string> GetDataWithErrorHandlingAsync()
{
    try
    {
        return await httpClient.GetStringAsync(url);
    }
    catch (HttpRequestException ex)
    {
        logger.LogError(ex, "HTTP request failed");
        throw;
    }
    catch (Exception ex)
    {
        logger.LogError(ex, "Unexpected error");
        return string.Empty;
    }
}

// Multiple tasks - exceptions aggregated
try
{
    await Task.WhenAll(task1, task2, task3);
}
catch (Exception ex)
{
    // Only first exception is caught
    logger.LogError(ex, "At least one task failed");
}

// To get all exceptions
var tasks = new[] { task1, task2, task3 };
try
{
    await Task.WhenAll(tasks);
}
catch
{
    foreach (var task in tasks)
    {
        if (task.IsFaulted)
        {
            logger.LogError(task.Exception, "Task failed");
        }
    }
}

// Handling faulted tasks
if (task.IsCompleted && !task.IsFaulted && !task.IsCanceled)
{
    var result = task.Result;
}
```

### Async Enumerable (IAsyncEnumerable)
```csharp
// Async iterator
public async IAsyncEnumerable<int> GenerateNumbersAsync(
    [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
    for (int i = 0; i < 100; i++)
    {
        await Task.Delay(100, cancellationToken);
        yield return i;
    }
}

// Consuming async enumerable
await foreach (var number in GenerateNumbersAsync())
{
    Console.WriteLine(number);
}

// With cancellation
await foreach (var number in GenerateNumbersAsync(cancellationToken))
{
    Console.WriteLine(number);
}

// Real-world example - streaming API results
public async IAsyncEnumerable<User> StreamUsersAsync(
    [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
    int page = 0;
    while (true)
    {
        var users = await GetPageAsync(page, cancellationToken);
        if (users.Count == 0)
            break;

        foreach (var user in users)
        {
            yield return user;
        }

        page++;
    }
}
```

### ValueTask
```csharp
// Use ValueTask when result is often available synchronously
public ValueTask<int> GetCachedValueAsync(string key)
{
    if (cache.TryGetValue(key, out int value))
    {
        return new ValueTask<int>(value);  // Synchronous completion
    }

    return new ValueTask<int>(FetchFromDatabaseAsync(key));  // Async completion
}

// Consuming ValueTask
int value = await GetCachedValueAsync("key");

// DO: Await ValueTask immediately
// Good
var result = await GetCachedValueAsync("key");

// DON'T: Store or await multiple times
// Bad
ValueTask<int> task = GetCachedValueAsync("key");
int result1 = await task;
int result2 = await task;  // May throw or return incorrect result

// DON'T: Use with Task.WhenAll
// Bad - convert to Task first
ValueTask<int> vt = GetCachedValueAsync("key");
await Task.WhenAll(vt.AsTask(), otherTask);
```

### Best Practices
```csharp
// DO: Use async all the way
// Good
public async Task<ActionResult> GetDataAsync()
{
    var data = await service.GetDataAsync();
    return Ok(data);
}

// Bad - sync over async (can cause deadlocks)
public ActionResult GetData()
{
    var data = service.GetDataAsync().Result;
    return Ok(data);
}

// DO: Suffix async methods with Async
public async Task<User> GetUserAsync(int id) { }

// DO: Return Task directly when possible
public Task<User> GetUserAsync(int id)
{
    return repository.GetByIdAsync(id);  // No await needed
}

// DON'T: Use async void except for event handlers
// Bad
public async void ProcessData()
{
    await DoWorkAsync();
}

// Good
public async Task ProcessDataAsync()
{
    await DoWorkAsync();
}

// DO: Use cancellation tokens
public async Task ProcessAsync(CancellationToken cancellationToken = default)
{
    await DoWorkAsync(cancellationToken);
}

// DO: ConfigureAwait(false) in library code
public async Task<string> LibraryMethodAsync()
{
    return await httpClient.GetStringAsync(url).ConfigureAwait(false);
}

// DON'T: Create unnecessary tasks
// Bad
public async Task<int> GetValueAsync()
{
    return await Task.Run(() => value);
}

// Good
public Task<int> GetValueAsync()
{
    return Task.FromResult(value);
}
```

## 4. Records and Init-Only Properties

### Overview
Records provide concise syntax for immutable reference types with value semantics. Init-only properties allow setting properties during object initialization but not after.

### Record Basics
```csharp
// Positional record
public record Person(string FirstName, string LastName, int Age);

// Usage
var person = new Person("John", "Doe", 30);
Console.WriteLine(person.FirstName);  // John

// Records are immutable by default - use 'with' for modifications
var older = person with { Age = 31 };

// Traditional property syntax
public record User
{
    public string Name { get; init; }
    public string Email { get; init; }
    public DateTime CreatedAt { get; init; }
}

// Mixed syntax
public record Product(string Name, decimal Price)
{
    public string Description { get; init; } = string.Empty;
    public bool IsAvailable { get; init; } = true;
}
```

### Record Value Semantics
```csharp
// Records use value-based equality
var person1 = new Person("John", "Doe", 30);
var person2 = new Person("John", "Doe", 30);

Console.WriteLine(person1 == person2);  // True
Console.WriteLine(person1.Equals(person2));  // True
Console.WriteLine(ReferenceEquals(person1, person2));  // False

// Automatic ToString implementation
Console.WriteLine(person1);  // Person { FirstName = John, LastName = Doe, Age = 30 }

// Automatic Deconstruction
var (firstName, lastName, age) = person1;

// GetHashCode based on values
var dict = new Dictionary<Person, string>();
dict[person1] = "Value";
Console.WriteLine(dict[person2]);  // Value (same key due to value equality)
```

### With-Expressions
```csharp
var original = new Person("John", "Doe", 30);

// Create modified copy
var modified = original with { Age = 31 };

// Multiple properties
var updated = original with
{
    LastName = "Smith",
    Age = 32
};

// Original unchanged
Console.WriteLine(original.Age);  // 30
Console.WriteLine(modified.Age);  // 31

// Chaining
var final = original
    .with { Age = 31 }
    .with { LastName = "Smith" };
```

### Record Inheritance
```csharp
// Base record
public record Person(string FirstName, string LastName);

// Derived record
public record Employee(string FirstName, string LastName, string Department)
    : Person(FirstName, LastName);

// Usage
Employee emp = new("John", "Doe", "IT");
Person person = emp;  // Upcasting

// With-expression preserves derived type
Employee updated = emp with { Department = "HR" };

// Equality respects hierarchy
Person p = new("John", "Doe");
Employee e = new("John", "Doe", "IT");
Console.WriteLine(p == e);  // False (different types)
```

### Record Structs (C# 10+)
```csharp
// Readonly record struct
public readonly record struct Point(int X, int Y);

// Mutable record struct
public record struct MutablePoint(int X, int Y);

// Usage
var p1 = new Point(1, 2);
// p1.X = 3;  // Error - readonly

var p2 = new MutablePoint(1, 2);
p2.X = 3;  // OK - mutable

// Value semantics still apply
var p3 = new Point(1, 2);
Console.WriteLine(p1 == p3);  // True
```

### Init-Only Properties
```csharp
// Init-only property
public class User
{
    public string Name { get; init; }
    public string Email { get; init; }
}

// Can set during initialization
var user = new User
{
    Name = "John",
    Email = "john@example.com"
};

// Cannot set after initialization
// user.Name = "Jane";  // Error

// With positional parameters
public class Product
{
    public Product(string name, decimal price)
    {
        Name = name;
        Price = price;
    }

    public string Name { get; init; }
    public decimal Price { get; init; }
    public string Description { get; init; } = string.Empty;
}

var product = new Product("Widget", 9.99m)
{
    Description = "A useful widget"
};
```

### Required Properties (C# 11+)
```csharp
// Required property must be set during initialization
public class User
{
    public required string Name { get; init; }
    public required string Email { get; init; }
    public string? PhoneNumber { get; init; }
}

// Must set required properties
var user = new User
{
    Name = "John",
    Email = "john@example.com"
    // PhoneNumber is optional
};

// With records
public record Person
{
    public required string FirstName { get; init; }
    public required string LastName { get; init; }
}

// SetsRequiredMembers attribute for constructors
public record Person
{
    public required string FirstName { get; init; }
    public required string LastName { get; init; }

    [SetsRequiredMembers]
    public Person(string firstName, string lastName)
    {
        FirstName = firstName;
        LastName = lastName;
    }
}

var person = new Person("John", "Doe");  // No initializer needed
```

### Record Patterns
```csharp
// DTOs
public record UserDto(int Id, string Name, string Email);

// Domain events
public record UserCreatedEvent(int UserId, DateTime CreatedAt);
public record UserUpdatedEvent(int UserId, DateTime UpdatedAt);

// API responses
public record ApiResponse<T>(bool Success, T? Data, string? Error);

// Configuration
public record DatabaseConfig
{
    public required string ConnectionString { get; init; }
    public int MaxRetries { get; init; } = 3;
    public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(30);
}

// Immutable collections in records
public record ShoppingCart
{
    public ImmutableList<CartItem> Items { get; init; } = ImmutableList<CartItem>.Empty;

    public ShoppingCart AddItem(CartItem item)
    {
        return this with { Items = Items.Add(item) };
    }
}
```

### Best Practices
```csharp
// DO: Use records for DTOs and value objects
public record AddressDto(string Street, string City, string ZipCode);

// DO: Use records for immutable data
public record Configuration(string ApiKey, string BaseUrl);

// DON'T: Use records for entities with identity
// Bad - entities need reference equality
public record User(int Id, string Name);

// Good - use class for entities
public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
}

// DO: Use init for immutability in classes
public class ValueObject
{
    public string Value { get; init; }
}

// DON'T: Mix mutable and immutable properties
// Bad
public record ConfusingRecord
{
    public string ImmutableProperty { get; init; }
    public string MutableProperty { get; set; }
}

// DO: Use required for mandatory properties
public record CreateUserRequest
{
    public required string Name { get; init; }
    public required string Email { get; init; }
    public string? PhoneNumber { get; init; }
}
```

## 5. Pattern Matching

### Overview
Pattern matching provides concise syntax for testing values against patterns and extracting information.

### Type Patterns
```csharp
// Basic type check
if (obj is string)
{
    string str = (string)obj;
}

// Type pattern with variable
if (obj is string str)
{
    Console.WriteLine(str.Length);
}

// Multiple type patterns
string result = obj switch
{
    string s => s,
    int i => i.ToString(),
    null => "null",
    _ => "unknown"
};
```

### Constant Patterns
```csharp
// Constant pattern
if (value is null)
{
    return;
}

if (value is 0)
{
    Console.WriteLine("Zero");
}

// Switch expression with constants
string description = value switch
{
    0 => "zero",
    1 => "one",
    2 => "two",
    _ => "other"
};
```

### Relational Patterns
```csharp
// Relational operators: <, <=, >, >=
string category = age switch
{
    < 13 => "child",
    < 20 => "teenager",
    < 65 => "adult",
    _ => "senior"
};

// Combining relational patterns
string grade = score switch
{
    >= 90 => "A",
    >= 80 => "B",
    >= 70 => "C",
    >= 60 => "D",
    _ => "F"
};

// With and/or patterns
bool isValid = value switch
{
    > 0 and < 100 => true,
    _ => false
};
```

### Logical Patterns
```csharp
// And pattern
if (obj is string s and { Length: > 0 })
{
    Console.WriteLine(s);
}

// Or pattern
if (value is 0 or 1 or 2)
{
    Console.WriteLine("Small number");
}

// Not pattern
if (value is not null)
{
    Process(value);
}

// Complex combinations
string result = value switch
{
    null or "" => "empty",
    { Length: > 0 and < 10 } => "short",
    { Length: >= 10 } => "long",
    _ => "unknown"
};
```

### Property Patterns
```csharp
// Property pattern
if (person is { Age: > 18 })
{
    Console.WriteLine("Adult");
}

// Multiple properties
if (person is { Age: > 18, IsActive: true })
{
    Process(person);
}

// Nested properties
if (order is { Customer: { IsVip: true }, Total: > 1000 })
{
    ApplyVipDiscount(order);
}

// Switch expression with properties
string description = person switch
{
    { Age: < 18 } => "minor",
    { Age: >= 18, IsStudent: true } => "student",
    { Age: >= 18, IsEmployed: true } => "employed",
    _ => "other"
};

// Extracting values
if (person is { Name: var name, Age: var age })
{
    Console.WriteLine($"{name} is {age} years old");
}
```

### Positional Patterns
```csharp
// Deconstruction pattern
if (point is (0, 0))
{
    Console.WriteLine("Origin");
}

// With variables
if (point is (var x, var y))
{
    Console.WriteLine($"X: {x}, Y: {y}");
}

// Switch expression
string quadrant = point switch
{
    (0, 0) => "origin",
    (var x, var y) when x > 0 && y > 0 => "quadrant I",
    (var x, var y) when x < 0 && y > 0 => "quadrant II",
    (var x, var y) when x < 0 && y < 0 => "quadrant III",
    (var x, var y) when x > 0 && y < 0 => "quadrant IV",
    _ => "on axis"
};

// Custom Deconstruct method
public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }

    public void Deconstruct(out string name, out int age)
    {
        name = Name;
        age = Age;
    }
}

if (person is ("John", var age))
{
    Console.WriteLine($"John is {age} years old");
}
```

### List Patterns (C# 11+)
```csharp
// List pattern matching
int[] numbers = { 1, 2, 3 };

string result = numbers switch
{
    [] => "empty",
    [1] => "single one",
    [1, 2] => "one and two",
    [1, 2, 3] => "one, two, three",
    _ => "other"
};

// Discard pattern
if (numbers is [_, 2, _])
{
    Console.WriteLine("Middle element is 2");
}

// Slice pattern
string description = numbers switch
{
    [1, .. var rest] => $"starts with 1, {rest.Length} more",
    [.. var middle, 3] => $"ends with 3, {middle.Length} before",
    [1, .., 3] => "starts with 1 and ends with 3",
    _ => "other"
};

// Var pattern for slice
if (numbers is [var first, .. var middle, var last])
{
    Console.WriteLine($"First: {first}, Middle: [{string.Join(", ", middle)}], Last: {last}");
}
```

### When Clauses
```csharp
// When clause (case guard)
string category = value switch
{
    int i when i < 0 => "negative",
    int i when i == 0 => "zero",
    int i when i > 0 => "positive",
    _ => "not an integer"
};

// Complex conditions
string result = obj switch
{
    string s when s.StartsWith("A") => "starts with A",
    string s when s.Length > 10 => "long string",
    int i when i % 2 == 0 => "even number",
    _ => "other"
};

// With property patterns
string description = person switch
{
    { Age: var age } when age < 18 => "minor",
    { Age: var age, IsStudent: true } when age < 25 => "student",
    { IsEmployed: true } => "employed",
    _ => "other"
};
```

### Practical Examples
```csharp
// Parsing different input types
public static int ParseInput(object input)
{
    return input switch
    {
        int i => i,
        string s when int.TryParse(s, out int result) => result,
        string => 0,
        _ => throw new ArgumentException("Cannot parse input")
    };
}

// State machine
public State ProcessEvent(Event evt, State current)
{
    return (evt, current) switch
    {
        (StartEvent, IdleState) => new RunningState(),
        (StopEvent, RunningState) => new IdleState(),
        (PauseEvent, RunningState) => new PausedState(),
        (ResumeEvent, PausedState) => new RunningState(),
        _ => current
    };
}

// Visitor pattern
public decimal CalculatePrice(Product product)
{
    return product switch
    {
        Book { Pages: > 500 } => 29.99m,
        Book => 19.99m,
        Electronics { Warranty: true } => 599.99m,
        Electronics => 499.99m,
        Clothing { Size: "XL" or "XXL" } => 39.99m,
        Clothing => 29.99m,
        _ => 9.99m
    };
}

// Response handling
public async Task<string> HandleResponseAsync(HttpResponseMessage response)
{
    return response.StatusCode switch
    {
        HttpStatusCode.OK => await response.Content.ReadAsStringAsync(),
        HttpStatusCode.NotFound => "Resource not found",
        HttpStatusCode.Unauthorized => "Unauthorized access",
        >= HttpStatusCode.BadRequest and < HttpStatusCode.InternalServerError
            => "Client error",
        >= HttpStatusCode.InternalServerError
            => "Server error",
        _ => "Unknown error"
    };
}
```

### Best Practices
```csharp
// DO: Use switch expressions for multiple cases
// Good
string result = value switch
{
    1 => "one",
    2 => "two",
    _ => "other"
};

// Less readable
string result;
if (value == 1)
    result = "one";
else if (value == 2)
    result = "two";
else
    result = "other";

// DO: Use not pattern for null checks
if (value is not null)
{
    Process(value);
}

// DO: Use property patterns for complex checks
if (person is { Age: > 18, IsActive: true })
{
    Process(person);
}

// DON'T: Overuse when clauses - consider separate methods
// Bad
var result = value switch
{
    int i when ComplexCondition1(i) => "a",
    int i when ComplexCondition2(i) => "b",
    _ => "c"
};

// Better
if (value is int i && ComplexCondition1(i))
    return "a";
if (value is int j && ComplexCondition2(j))
    return "b";
return "c";

// DO: Exhaust all possibilities or use discard pattern
string result = value switch
{
    0 => "zero",
    > 0 => "positive",
    < 0 => "negative",
    // All cases covered, _ not needed
};
```

## 6. Delegates and Events

### Overview
Delegates are type-safe function pointers. Events provide a publish-subscribe mechanism built on delegates.

### Delegate Basics
```csharp
// Delegate declaration
public delegate void NotifyHandler(string message);
public delegate int Calculate(int x, int y);

// Using delegates
NotifyHandler handler = ShowMessage;
handler("Hello");  // Invokes ShowMessage("Hello")

void ShowMessage(string message)
{
    Console.WriteLine(message);
}

// Multi-cast delegates
NotifyHandler handler = ShowMessage;
handler += LogMessage;
handler += SendEmail;
handler("Event occurred");  // Calls all three methods

// Removing delegates
handler -= LogMessage;

// Return value with multi-cast (only last value returned)
Calculate calc = Add;
calc += Multiply;
int result = calc(5, 3);  // Returns Multiply result, Add result discarded
```

### Built-in Delegates
```csharp
// Action - no return value
Action action = () => Console.WriteLine("Action");
Action<string> actionWithParam = message => Console.WriteLine(message);
Action<int, string> actionMultiParam = (id, name) =>
    Console.WriteLine($"{id}: {name}");

action();
actionWithParam("Hello");
actionMultiParam(1, "John");

// Func - with return value
Func<int> func = () => 42;
Func<int, int> funcWithParam = x => x * 2;
Func<int, int, int> funcMultiParam = (x, y) => x + y;

int value = func();
int doubled = funcWithParam(5);
int sum = funcMultiParam(3, 4);

// Predicate - returns bool
Predicate<int> isEven = x => x % 2 == 0;
bool result = isEven(4);

// Comparison
Comparison<int> comparison = (x, y) => x.CompareTo(y);
```

### Events
```csharp
// Event declaration
public class Publisher
{
    // Event with EventHandler
    public event EventHandler? SomethingHappened;

    // Event with EventHandler<TEventArgs>
    public event EventHandler<DataEventArgs>? DataReceived;

    // Event with custom delegate
    public event NotifyHandler? Notification;

    protected virtual void OnSomethingHappened(EventArgs e)
    {
        SomethingHappened?.Invoke(this, e);
    }

    protected virtual void OnDataReceived(DataEventArgs e)
    {
        DataReceived?.Invoke(this, e);
    }

    public void DoSomething()
    {
        // Raise event
        OnSomethingHappened(EventArgs.Empty);
        OnDataReceived(new DataEventArgs { Data = "test" });
    }
}

// Custom EventArgs
public class DataEventArgs : EventArgs
{
    public string Data { get; set; } = string.Empty;
}

// Subscribing to events
var publisher = new Publisher();
publisher.SomethingHappened += OnSomethingHappened;
publisher.DataReceived += OnDataReceived;

void OnSomethingHappened(object? sender, EventArgs e)
{
    Console.WriteLine("Something happened");
}

void OnDataReceived(object? sender, DataEventArgs e)
{
    Console.WriteLine($"Data received: {e.Data}");
}

// Unsubscribing
publisher.SomethingHappened -= OnSomethingHappened;
```

### Lambda Expressions
```csharp
// Expression lambda
Func<int, int> square = x => x * x;

// Statement lambda
Func<int, int, int> divide = (x, y) =>
{
    if (y == 0)
        throw new DivideByZeroException();
    return x / y;
};

// Lambda with no parameters
Action greet = () => Console.WriteLine("Hello");

// Capturing variables (closure)
int factor = 10;
Func<int, int> multiply = x => x * factor;
int result = multiply(5);  // 50

factor = 20;
result = multiply(5);  // 100 (captures current value)

// Async lambda
Func<Task<string>> fetchData = async () =>
{
    await Task.Delay(1000);
    return "Data";
};
```

### Event Patterns
```csharp
// Standard event pattern
public class Button
{
    public event EventHandler? Click;

    protected virtual void OnClick(EventArgs e)
    {
        Click?.Invoke(this, e);
    }

    public void PerformClick()
    {
        OnClick(EventArgs.Empty);
    }
}

// Weak event pattern (prevents memory leaks)
public class WeakEventManager
{
    private readonly List<WeakReference<EventHandler>> handlers = new();

    public void AddHandler(EventHandler handler)
    {
        handlers.Add(new WeakReference<EventHandler>(handler));
    }

    public void RemoveHandler(EventHandler handler)
    {
        handlers.RemoveAll(wr =>
        {
            if (!wr.TryGetTarget(out var target))
                return true;  // Remove dead reference
            return target == handler;
        });
    }

    public void Raise(object? sender, EventArgs e)
    {
        foreach (var wr in handlers.ToList())
        {
            if (wr.TryGetTarget(out var handler))
            {
                handler(sender, e);
            }
            else
            {
                handlers.Remove(wr);  // Cleanup
            }
        }
    }
}

// Custom add/remove
public class CustomEvents
{
    private EventHandler? _changed;

    public event EventHandler? Changed
    {
        add
        {
            Console.WriteLine("Handler added");
            _changed += value;
        }
        remove
        {
            Console.WriteLine("Handler removed");
            _changed -= value;
        }
    }
}
```

### Practical Examples
```csharp
// Observer pattern
public class StockMonitor
{
    public event EventHandler<StockChangedEventArgs>? StockChanged;

    private decimal _price;
    public decimal Price
    {
        get => _price;
        set
        {
            if (_price != value)
            {
                var oldPrice = _price;
                _price = value;
                OnStockChanged(new StockChangedEventArgs
                {
                    OldPrice = oldPrice,
                    NewPrice = value
                });
            }
        }
    }

    protected virtual void OnStockChanged(StockChangedEventArgs e)
    {
        StockChanged?.Invoke(this, e);
    }
}

public class StockChangedEventArgs : EventArgs
{
    public decimal OldPrice { get; set; }
    public decimal NewPrice { get; set; }
}

// Usage
var monitor = new StockMonitor();
monitor.StockChanged += (sender, e) =>
{
    Console.WriteLine($"Price changed from {e.OldPrice} to {e.NewPrice}");
};
monitor.Price = 100.50m;

// Progress reporting
public class FileProcessor
{
    public event EventHandler<ProgressEventArgs>? ProgressChanged;

    public async Task ProcessFilesAsync(string[] files)
    {
        for (int i = 0; i < files.Length; i++)
        {
            await ProcessFileAsync(files[i]);

            OnProgressChanged(new ProgressEventArgs
            {
                Percentage = (i + 1) * 100 / files.Length,
                Message = $"Processed {files[i]}"
            });
        }
    }

    protected virtual void OnProgressChanged(ProgressEventArgs e)
    {
        ProgressChanged?.Invoke(this, e);
    }
}

public class ProgressEventArgs : EventArgs
{
    public int Percentage { get; set; }
    public string Message { get; set; } = string.Empty;
}

// Callback pattern
public class DataLoader
{
    public async Task LoadDataAsync(
        Func<string, Task> onProgress,
        Func<Exception, Task<bool>> onError)
    {
        try
        {
            await onProgress("Starting...");
            // Load data
            await onProgress("Completed");
        }
        catch (Exception ex)
        {
            bool retry = await onError(ex);
            if (retry)
            {
                await LoadDataAsync(onProgress, onError);
     

…(truncated)
