csharp-advanced-patterns

Advanced C# language patterns and .NET 10 features for elegant, performant code.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "csharp-advanced-patterns" with this command: npx skills add thapaliyabikendra/ai-artifacts/thapaliyabikendra-ai-artifacts-csharp-advanced-patterns

C# Advanced Patterns

Advanced C# language patterns and .NET 10 features for elegant, performant code.

When to Use

  • Implementing complex business logic with pattern matching

  • Optimizing async/await usage

  • Writing performant code with Span/Memory

  • Refactoring legacy code to modern C#

  • Creating immutable DTOs with records

Modern C# Features (.NET 10)

Records for DTOs

// Immutable DTO with required properties public record CreatePatientDto { public required string FirstName { get; init; } public required string LastName { get; init; } public required string Email { get; init; } public DateTime DateOfBirth { get; init; } }

// Positional record with deconstruction public record PatientDto(Guid Id, string FullName, string Email);

// Usage var (id, name, email) = patient;

Pattern Matching

// Switch expression for status handling public string GetStatusMessage(AppointmentStatus status) => status switch { AppointmentStatus.Scheduled => "Your appointment is confirmed", AppointmentStatus.Completed => "Thank you for visiting", AppointmentStatus.Cancelled => "Your appointment was cancelled", AppointmentStatus.NoShow => "You missed your appointment", _ => throw new ArgumentOutOfRangeException(nameof(status)) };

// Property pattern matching public decimal CalculateDiscount(Patient patient) => patient switch { { Age: > 65 } => 0.20m, { IsVeteran: true } => 0.15m, { Visits: > 10 } => 0.10m, _ => 0m };

// List patterns (.NET 7+) public string DescribeList(int[] numbers) => numbers switch { [] => "Empty", [var single] => $"Single: {single}", [var first, .., var last] => $"First: {first}, Last: {last}", };

Primary Constructors

// Class with primary constructor public class PatientService( IRepository<Patient, Guid> repository, ILogger<PatientService> logger) { public async Task<Patient> GetAsync(Guid id) { logger.LogInformation("Getting patient {Id}", id); return await repository.GetAsync(id); } }

Collection Expressions

// Modern collection initialization int[] numbers = [1, 2, 3, 4, 5]; List<string> names = ["Alice", "Bob", "Charlie"]; Span<int> span = [1, 2, 3];

// Spread operator int[] combined = [..numbers, 6, 7, 8];

Async/Await Patterns

Proper Async with Cancellation

public async Task<PatientDto> GetPatientAsync( Guid id, CancellationToken cancellationToken = default) { var patient = await _repository .GetAsync(id, cancellationToken);

return ObjectMapper.Map&#x3C;Patient, PatientDto>(patient);

}

Parallel Processing with SemaphoreSlim

public async Task ProcessPatientsAsync( IEnumerable<Guid> patientIds, CancellationToken ct) { var semaphore = new SemaphoreSlim(10); // Max 10 concurrent var tasks = patientIds.Select(async id => { await semaphore.WaitAsync(ct); try { await ProcessPatientAsync(id, ct); } finally { semaphore.Release(); } });

await Task.WhenAll(tasks);

}

ValueTask for Hot Paths

// Use ValueTask when result is often synchronous public ValueTask<Patient?> GetCachedPatientAsync(Guid id) { if (_cache.TryGetValue(id, out var patient)) return ValueTask.FromResult<Patient?>(patient);

return new ValueTask&#x3C;Patient?>(LoadPatientAsync(id));

}

Channel for Producer/Consumer

public class PatientProcessor { private readonly Channel<Patient> _channel = Channel.CreateBounded<Patient>(100);

public async Task ProduceAsync(Patient patient, CancellationToken ct)
{
    await _channel.Writer.WriteAsync(patient, ct);
}

public async Task ConsumeAsync(CancellationToken ct)
{
    await foreach (var patient in _channel.Reader.ReadAllAsync(ct))
    {
        await ProcessAsync(patient);
    }
}

}

Result Pattern

public readonly record struct Result<T> { public T? Value { get; } public string? Error { get; } public bool IsSuccess => Error is null;

private Result(T value) => Value = value;
private Result(string error) => Error = error;

public static Result&#x3C;T> Success(T value) => new(value);
public static Result&#x3C;T> Failure(string error) => new(error);

public TResult Match&#x3C;TResult>(
    Func&#x3C;T, TResult> onSuccess,
    Func&#x3C;string, TResult> onFailure)
    => IsSuccess ? onSuccess(Value!) : onFailure(Error!);

}

// Usage public Result<Patient> CreatePatient(CreatePatientDto dto) { if (string.IsNullOrEmpty(dto.Email)) return Result<Patient>.Failure("Email is required");

var patient = new Patient(dto.FirstName, dto.LastName, dto.Email);
return Result&#x3C;Patient>.Success(patient);

}

Extension Methods

public static class PatientExtensions { public static string GetFullName(this Patient patient) => $"{patient.FirstName} {patient.LastName}";

public static bool IsEligibleForDiscount(this Patient patient)
    => patient.Age > 65 || patient.Visits > 10;

// IQueryable extension for reusable filters
public static IQueryable&#x3C;Patient> ActiveOnly(this IQueryable&#x3C;Patient> query)
    => query.Where(p => p.IsActive);

public static IQueryable&#x3C;Patient> ByEmail(
    this IQueryable&#x3C;Patient> query,
    string email)
    => query.Where(p => p.Email == email);

}

Performance Patterns

Span for Zero-Allocation

public static int CountOccurrences(ReadOnlySpan<char> text, char target) { int count = 0; foreach (var c in text) { if (c == target) count++; } return count; }

// String slicing without allocation ReadOnlySpan<char> firstName = fullName.AsSpan(0, spaceIndex);

ArrayPool for Temporary Buffers

public async Task ProcessLargeDataAsync(Stream stream) { var buffer = ArrayPool<byte>.Shared.Rent(4096); try { int bytesRead; while ((bytesRead = await stream.ReadAsync(buffer)) > 0) { ProcessChunk(buffer.AsSpan(0, bytesRead)); } } finally { ArrayPool<byte>.Shared.Return(buffer); } }

StringBuilder for String Building

// Bad: String concatenation in loops string result = ""; foreach (var item in items) result += item; // Creates new string each iteration

// Good: Use StringBuilder var sb = new StringBuilder(); foreach (var item in items) sb.Append(item); return sb.ToString();

Anti-Patterns to Avoid

Anti-Pattern Problem Solution

.Result / .Wait()

Deadlock risk Use await

catch (Exception)

Catches everything Catch specific types

String concat in loops O(n²) allocations Use StringBuilder

async void

Unobserved exceptions Use async Task

Premature optimization Complexity Profile first

// Bad: Blocking on async var result = GetPatientAsync(id).Result; // Deadlock risk!

// Good: Proper async var result = await GetPatientAsync(id);

// Bad: async void (fire and forget) async void ProcessPatient(Guid id) { ... }

// Good: async Task async Task ProcessPatientAsync(Guid id) { ... }

// Bad: Catching base Exception try { } catch (Exception ex) { }

// Good: Catch specific exceptions try { } catch (InvalidOperationException ex) { _logger.LogWarning(ex, "..."); } catch (ArgumentException ex) { _logger.LogError(ex, "..."); }

LINQ Best Practices

// Avoid multiple enumeration var patients = await _repository.GetListAsync(); // Materialize once var count = patients.Count; var first = patients.FirstOrDefault();

// Use AsNoTracking for read-only queries var patients = await _context.Patients .AsNoTracking() .Where(p => p.IsActive) .ToListAsync();

// Prefer Any() over Count() > 0 if (await _repository.AnyAsync(p => p.Email == email)) { ... }

// Project early to reduce data transfer var dtos = await _context.Patients .Where(p => p.IsActive) .Select(p => new PatientDto(p.Id, p.FullName, p.Email)) .ToListAsync();

Quality Checklist

  • Use records for DTOs (immutability)

  • Use switch expressions over switch statements

  • Pass CancellationToken through async chain

  • Use ValueTask for hot paths with sync results

  • Avoid blocking calls (.Result, .Wait())

  • Use Span for performance-critical parsing

  • Catch specific exception types

  • Use nullable reference types

Integration Points

This skill is used by:

  • abp-developer: Modern C# patterns in implementation

  • abp-code-reviewer: Pattern validation during reviews

  • debugger: Performance analysis and fixes

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

clean-code-dotnet

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

code-review-excellence

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

react-code-review-patterns

No summary provided by upstream source.

Repository SourceNeeds Review