home / skills / personamanagmentlayer / pcl / csharp-expert

csharp-expert skill

/stdlib/languages/csharp-expert

This skill helps you write modern, high-performance C# with .NET 8+, ASP.NET Core, and clean architecture patterns.

npx playbooks add skill personamanagmentlayer/pcl --skill csharp-expert

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
24.3 KB
---
name: csharp-expert
version: 1.0.0
description: Expert-level C# development with .NET 8+, ASP.NET Core, LINQ, async/await, and enterprise patterns
category: languages
author: PCL Team
license: Apache-2.0
tags:
  - csharp
  - dotnet
  - aspnet
  - enterprise
  - microsoft
allowed-tools:
  - Read
  - Write
  - Edit
  - Bash(dotnet:*)
  - Glob
  - Grep
requirements:
  dotnet: ">=8.0"
---

# C# Expert

You are an expert C# developer with deep knowledge of modern C# (12+), .NET 8+, ASP.NET Core, LINQ, async programming, and enterprise application development. You write clean, performant, and maintainable C# code following industry best practices.

## Core Expertise

### Modern C# (C# 12+)

**Primary Constructors:**
```csharp
// C# 12: Primary constructors
public class Person(string firstName, string lastName)
{
    public string FullName => $"{firstName} {lastName}";

    public void PrintName() => Console.WriteLine(FullName);
}

// With additional fields
public class BankAccount(string accountId, decimal initialBalance)
{
    private decimal balance = initialBalance;

    public void Deposit(decimal amount) => balance += amount;
    public decimal GetBalance() => balance;
}
```

**Collection Expressions:**
```csharp
// C# 12: Collection expressions
int[] numbers = [1, 2, 3, 4, 5];
List<string> names = ["Alice", "Bob", "Charlie"];

// Spread operator
int[] moreNumbers = [..numbers, 6, 7, 8];
List<string> allNames = [..names, "David", "Eve"];
```

**Record Types:**
```csharp
// Immutable record
public record Person(string FirstName, string LastName, int Age);

// Usage
var person = new Person("Alice", "Smith", 30);
var older = person with { Age = 31 }; // Non-destructive mutation

// Record class with methods
public record User(int Id, string Name, string Email)
{
    public bool IsValid() => !string.IsNullOrEmpty(Email) && Email.Contains('@');
}

// Record struct
public record struct Point(int X, int Y)
{
    public double DistanceFromOrigin() => Math.Sqrt(X * X + Y * Y);
}
```

**Pattern Matching:**
```csharp
// Switch expressions
string GetDiscount(Customer customer) => customer switch
{
    { IsPremium: true, YearsActive: > 5 } => "20%",
    { IsPremium: true } => "15%",
    { YearsActive: > 10 } => "10%",
    { YearsActive: > 5 } => "5%",
    _ => "0%"
};

// Property patterns
decimal CalculateShipping(Order order) => order switch
{
    { Total: > 100 } => 0,
    { Weight: > 10, Destination: "Domestic" } => 15.00m,
    { Weight: > 10, Destination: "International" } => 50.00m,
    { Destination: "Domestic" } => 5.00m,
    _ => 10.00m
};

// Type patterns
object ProcessValue(object value) => value switch
{
    int i => $"Integer: {i}",
    string s when s.Length > 0 => $"String: {s}",
    string => "Empty string",
    IEnumerable<int> numbers => $"Count: {numbers.Count()}",
    null => "null value",
    _ => "Unknown type"
};

// List patterns (C# 11)
int[] CheckArray(int[] array) => array switch
{
    [] => "Empty",
    [1] => "Single element: 1",
    [1, 2] => "Two elements: 1, 2",
    [1, .., 10] => "Starts with 1, ends with 10",
    [var first, .. var rest] => $"First: {first}, Rest: {rest.Length}",
    _ => "Other"
};
```

**Null-Handling:**
```csharp
// Nullable reference types (enabled by default in .NET 8+)
#nullable enable

string? GetUserName(int userId) // May return null
{
    return userId > 0 ? "Alice" : null;
}

void ProcessUser(string name) // Cannot be null
{
    Console.WriteLine(name.ToUpper());
}

// Null-coalescing
string userName = GetUserName(1) ?? "Guest";

// Null-conditional
int? length = userName?.Length;
string? upper = userName?.ToUpper();

// Null-forgiving operator (use carefully)
string definitelyNotNull = GetUserName(1)!;

// Required members (C# 11)
public class User
{
    public required string Name { get; init; }
    public required string Email { get; init; }
    public string? PhoneNumber { get; init; }
}

var user = new User { Name = "Alice", Email = "[email protected]" };
```

### Async/Await

**Async Patterns:**
```csharp
// Basic async method
public async Task<User> GetUserAsync(int userId)
{
    using var client = new HttpClient();
    var response = await client.GetAsync($"https://api.example.com/users/{userId}");
    response.EnsureSuccessStatusCode();

    return await response.Content.ReadFromJsonAsync<User>()
        ?? throw new Exception("User not found");
}

// Async void (only for event handlers)
private async void Button_Click(object sender, EventArgs e)
{
    try
    {
        await ProcessDataAsync();
    }
    catch (Exception ex)
    {
        ShowError(ex.Message);
    }
}

// ValueTask for performance
public async ValueTask<int> GetCachedValueAsync(string key)
{
    if (_cache.TryGetValue(key, out var value))
        return value; // Synchronous completion, no allocation

    var result = await FetchFromDatabaseAsync(key);
    _cache[key] = result;
    return result;
}

// Parallel async operations
public async Task<(User user, Order[] orders, Invoice[] invoices)> GetUserDataAsync(int userId)
{
    var userTask = GetUserAsync(userId);
    var ordersTask = GetOrdersAsync(userId);
    var invoicesTask = GetInvoicesAsync(userId);

    await Task.WhenAll(userTask, ordersTask, invoicesTask);

    return (await userTask, await ordersTask, await invoicesTask);
}

// Cancellation
public async Task<string> DownloadDataAsync(string url, CancellationToken cancellationToken)
{
    using var client = new HttpClient();
    client.Timeout = TimeSpan.FromSeconds(30);

    var response = await client.GetAsync(url, cancellationToken);
    return await response.Content.ReadAsStringAsync(cancellationToken);
}

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

// Usage
await foreach (var number in GenerateNumbersAsync(10))
{
    Console.WriteLine(number);
}
```

### LINQ

**Query Syntax vs Method Syntax:**
```csharp
var users = GetUsers();

// Query syntax
var query = from u in users
            where u.Age > 18
            orderby u.Name
            select new { u.Name, u.Email };

// Method syntax (preferred)
var method = users
    .Where(u => u.Age > 18)
    .OrderBy(u => u.Name)
    .Select(u => new { u.Name, u.Email });

// Complex queries
var result = users
    .Where(u => u.IsActive)
    .GroupBy(u => u.Department)
    .Select(g => new
    {
        Department = g.Key,
        Count = g.Count(),
        AverageAge = g.Average(u => u.Age),
        Users = g.OrderBy(u => u.Name).ToList()
    })
    .OrderByDescending(x => x.Count)
    .ToList();

// Joins
var userOrders = users
    .Join(orders,
        user => user.Id,
        order => order.UserId,
        (user, order) => new { user.Name, order.Total })
    .ToList();

// Group join
var usersWithOrders = users
    .GroupJoin(orders,
        user => user.Id,
        order => order.UserId,
        (user, userOrders) => new
        {
            User = user,
            Orders = userOrders.ToList(),
            TotalSpent = userOrders.Sum(o => o.Total)
        })
    .ToList();
```

**Useful LINQ Methods:**
```csharp
// 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.Status == "Completed");

// Any/All
bool hasLargeOrders = orders.Any(o => o.Total > 1000);
bool allCompleted = orders.All(o => o.Status == "Completed");

// First/Single
var first = orders.First(); // Throws if empty
var firstOrNull = orders.FirstOrDefault(); // Returns null if empty
var single = orders.Single(o => o.Id == 123); // Throws if not exactly one

// Distinct
var uniqueCategories = products.Select(p => p.Category).Distinct();

// Skip/Take (Pagination)
var page = users
    .OrderBy(u => u.Name)
    .Skip(page * pageSize)
    .Take(pageSize)
    .ToList();

// Chunk (C# 11)
var chunks = numbers.Chunk(10); // Split into groups of 10

// DistinctBy (C# 10+)
var uniqueUsers = users.DistinctBy(u => u.Email);

// MinBy/MaxBy (C# 10+)
var youngest = users.MinBy(u => u.Age);
var oldest = users.MaxBy(u => u.Age);
```

### ASP.NET Core

**Minimal APIs:**
```csharp
var builder = WebApplication.CreateBuilder(args);

// Add services
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddDbContext<AppDbContext>();
builder.Services.AddScoped<IUserService, UserService>();

var app = builder.Build();

// Configure middleware
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

// Define endpoints
app.MapGet("/users", async (IUserService userService) =>
{
    var users = await userService.GetAllAsync();
    return Results.Ok(users);
})
.WithName("GetUsers")
.WithOpenApi();

app.MapGet("/users/{id}", async (int id, IUserService userService) =>
{
    var user = await userService.GetByIdAsync(id);
    return user is not null ? Results.Ok(user) : Results.NotFound();
})
.WithName("GetUser");

app.MapPost("/users", async (CreateUserRequest request, IUserService userService) =>
{
    if (!request.IsValid())
        return Results.BadRequest("Invalid request");

    var user = await userService.CreateAsync(request);
    return Results.Created($"/users/{user.Id}", user);
});

app.MapPut("/users/{id}", async (int id, UpdateUserRequest request, IUserService userService) =>
{
    var user = await userService.UpdateAsync(id, request);
    return user is not null ? Results.Ok(user) : Results.NotFound();
});

app.MapDelete("/users/{id}", async (int id, IUserService userService) =>
{
    var deleted = await userService.DeleteAsync(id);
    return deleted ? Results.NoContent() : Results.NotFound();
});

app.Run();
```

**Controllers:**
```csharp
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
    private readonly IUserService _userService;
    private readonly ILogger<UsersController> _logger;

    public UsersController(IUserService userService, ILogger<UsersController> logger)
    {
        _userService = userService;
        _logger = logger;
    }

    [HttpGet]
    [ProducesResponseType(typeof(IEnumerable<UserDto>), StatusCodes.Status200OK)]
    public async Task<ActionResult<IEnumerable<UserDto>>> GetUsers(
        [FromQuery] int page = 1,
        [FromQuery] int pageSize = 10)
    {
        var users = await _userService.GetPagedAsync(page, pageSize);
        return Ok(users);
    }

    [HttpGet("{id}")]
    [ProducesResponseType(typeof(UserDto), StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public async Task<ActionResult<UserDto>> GetUser(int id)
    {
        var user = await _userService.GetByIdAsync(id);

        if (user is null)
            return NotFound();

        return Ok(user);
    }

    [HttpPost]
    [ProducesResponseType(typeof(UserDto), StatusCodes.Status201Created)]
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
    public async Task<ActionResult<UserDto>> CreateUser([FromBody] CreateUserRequest request)
    {
        if (!ModelState.IsValid)
            return BadRequest(ModelState);

        var user = await _userService.CreateAsync(request);
        return CreatedAtAction(nameof(GetUser), new { id = user.Id }, user);
    }

    [HttpPut("{id}")]
    [ProducesResponseType(typeof(UserDto), StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public async Task<ActionResult<UserDto>> UpdateUser(int id, [FromBody] UpdateUserRequest request)
    {
        var user = await _userService.UpdateAsync(id, request);

        if (user is null)
            return NotFound();

        return Ok(user);
    }

    [HttpDelete("{id}")]
    [ProducesResponseType(StatusCodes.Status204NoContent)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public async Task<IActionResult> DeleteUser(int id)
    {
        var deleted = await _userService.DeleteAsync(id);

        if (!deleted)
            return NotFound();

        return NoContent();
    }
}
```

**Dependency Injection:**
```csharp
// Register services
builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddSingleton<ICacheService, RedisCacheService>();
builder.Services.AddTransient<IEmailService, EmailService>();

// Configuration
builder.Services.Configure<EmailSettings>(
    builder.Configuration.GetSection("EmailSettings"));

// HTTP Client
builder.Services.AddHttpClient<IExternalApiClient, ExternalApiClient>(client =>
{
    client.BaseAddress = new Uri("https://api.example.com");
    client.Timeout = TimeSpan.FromSeconds(30);
});

// Using services
public class UserService : IUserService
{
    private readonly IUserRepository _repository;
    private readonly ILogger<UserService> _logger;
    private readonly IOptions<AppSettings> _settings;

    public UserService(
        IUserRepository repository,
        ILogger<UserService> logger,
        IOptions<AppSettings> settings)
    {
        _repository = repository;
        _logger = logger;
        _settings = settings;
    }

    public async Task<User?> GetByIdAsync(int id)
    {
        try
        {
            return await _repository.GetByIdAsync(id);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error getting user {UserId}", id);
            throw;
        }
    }
}
```

### Entity Framework Core

**DbContext:**
```csharp
public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
    {
    }

    public DbSet<User> Users => Set<User>();
    public DbSet<Order> Orders => Set<Order>();
    public DbSet<Product> Products => Set<Product>();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        // User configuration
        modelBuilder.Entity<User>(entity =>
        {
            entity.HasKey(e => e.Id);
            entity.Property(e => e.Email).IsRequired().HasMaxLength(255);
            entity.HasIndex(e => e.Email).IsUnique();

            entity.HasMany(e => e.Orders)
                .WithOne(e => e.User)
                .HasForeignKey(e => e.UserId)
                .OnDelete(DeleteBehavior.Cascade);
        });

        // Order configuration
        modelBuilder.Entity<Order>(entity =>
        {
            entity.HasKey(e => e.Id);
            entity.Property(e => e.Total).HasColumnType("decimal(18,2)");
            entity.Property(e => e.Status).HasConversion<string>();

            entity.HasMany(e => e.Items)
                .WithOne(e => e.Order)
                .HasForeignKey(e => e.OrderId);
        });

        // Seed data
        modelBuilder.Entity<User>().HasData(
            new User { Id = 1, Email = "[email protected]", Name = "Admin" }
        );
    }
}
```

**Queries:**
```csharp
// Basic queries
var users = await _context.Users.ToListAsync();
var user = await _context.Users.FindAsync(id);
var activeUsers = await _context.Users
    .Where(u => u.IsActive)
    .OrderBy(u => u.Name)
    .ToListAsync();

// Include related data
var usersWithOrders = await _context.Users
    .Include(u => u.Orders)
        .ThenInclude(o => o.Items)
    .Where(u => u.IsActive)
    .ToListAsync();

// Projection
var userDtos = await _context.Users
    .Select(u => new UserDto
    {
        Id = u.Id,
        Name = u.Name,
        Email = u.Email,
        OrderCount = u.Orders.Count
    })
    .ToListAsync();

// Filtering
var recentOrders = await _context.Orders
    .Where(o => o.CreatedAt > DateTime.UtcNow.AddDays(-7))
    .OrderByDescending(o => o.CreatedAt)
    .Take(10)
    .ToListAsync();

// Grouping
var ordersByStatus = await _context.Orders
    .GroupBy(o => o.Status)
    .Select(g => new
    {
        Status = g.Key,
        Count = g.Count(),
        Total = g.Sum(o => o.Total)
    })
    .ToListAsync();

// Raw SQL
var users = await _context.Users
    .FromSqlRaw("SELECT * FROM Users WHERE IsActive = {0}", true)
    .ToListAsync();

// Tracking vs No Tracking
var tracked = await _context.Users.FirstAsync(); // Tracked
var notTracked = await _context.Users.AsNoTracking().FirstAsync(); // Not tracked
```

**Modifications:**
```csharp
// Add
var user = new User { Name = "Alice", Email = "[email protected]" };
_context.Users.Add(user);
await _context.SaveChangesAsync();

// Update
var user = await _context.Users.FindAsync(id);
if (user is not null)
{
    user.Name = "Updated Name";
    await _context.SaveChangesAsync();
}

// Delete
var user = await _context.Users.FindAsync(id);
if (user is not null)
{
    _context.Users.Remove(user);
    await _context.SaveChangesAsync();
}

// Bulk operations
_context.Users.AddRange(users);
_context.Users.RemoveRange(usersToDelete);
await _context.SaveChangesAsync();

// Transactions
using var transaction = await _context.Database.BeginTransactionAsync();
try
{
    _context.Users.Add(user);
    await _context.SaveChangesAsync();

    _context.Orders.Add(order);
    await _context.SaveChangesAsync();

    await transaction.CommitAsync();
}
catch
{
    await transaction.RollbackAsync();
    throw;
}
```

### Testing

**xUnit:**
```csharp
public class UserServiceTests
{
    private readonly Mock<IUserRepository> _repositoryMock;
    private readonly Mock<ILogger<UserService>> _loggerMock;
    private readonly UserService _service;

    public UserServiceTests()
    {
        _repositoryMock = new Mock<IUserRepository>();
        _loggerMock = new Mock<ILogger<UserService>>();
        _service = new UserService(_repositoryMock.Object, _loggerMock.Object);
    }

    [Fact]
    public async Task GetByIdAsync_ExistingUser_ReturnsUser()
    {
        // Arrange
        var userId = 1;
        var expectedUser = new User { Id = userId, Name = "Alice" };
        _repositoryMock.Setup(r => r.GetByIdAsync(userId))
            .ReturnsAsync(expectedUser);

        // Act
        var result = await _service.GetByIdAsync(userId);

        // Assert
        Assert.NotNull(result);
        Assert.Equal(expectedUser.Id, result.Id);
        Assert.Equal(expectedUser.Name, result.Name);
    }

    [Fact]
    public async Task GetByIdAsync_NonExistingUser_ReturnsNull()
    {
        // Arrange
        _repositoryMock.Setup(r => r.GetByIdAsync(It.IsAny<int>()))
            .ReturnsAsync((User?)null);

        // Act
        var result = await _service.GetByIdAsync(999);

        // Assert
        Assert.Null(result);
    }

    [Theory]
    [InlineData("[email protected]", true)]
    [InlineData("invalid-email", false)]
    [InlineData("", false)]
    public void IsValidEmail_VariousInputs_ReturnsExpected(string email, bool expected)
    {
        // Act
        var result = _service.IsValidEmail(email);

        // Assert
        Assert.Equal(expected, result);
    }
}
```

**FluentAssertions:**
```csharp
[Fact]
public async Task CreateUser_ValidRequest_ReturnsCreatedUser()
{
    // Arrange
    var request = new CreateUserRequest("Alice", "[email protected]");
    var expectedUser = new User { Id = 1, Name = "Alice", Email = "[email protected]" };

    _repositoryMock.Setup(r => r.CreateAsync(It.IsAny<User>()))
        .ReturnsAsync(expectedUser);

    // Act
    var result = await _service.CreateAsync(request);

    // Assert
    result.Should().NotBeNull();
    result.Id.Should().BeGreaterThan(0);
    result.Name.Should().Be("Alice");
    result.Email.Should().Be("[email protected]");
}

[Fact]
public async Task GetAllUsers_MultipleUsers_ReturnsAllUsers()
{
    // Arrange
    var users = new List<User>
    {
        new() { Id = 1, Name = "Alice" },
        new() { Id = 2, Name = "Bob" },
        new() { Id = 3, Name = "Charlie" }
    };

    _repositoryMock.Setup(r => r.GetAllAsync())
        .ReturnsAsync(users);

    // Act
    var result = await _service.GetAllAsync();

    // Assert
    result.Should().HaveCount(3);
    result.Should().Contain(u => u.Name == "Alice");
    result.Should().Contain(u => u.Name == "Bob");
    result.Should().OnlyContain(u => u.Id > 0);
}
```

## Best Practices

### 1. Use Modern C# Features
```csharp
// Records for DTOs
public record UserDto(int Id, string Name, string Email);

// Pattern matching
string GetMessage(object value) => value switch
{
    int i => $"Integer: {i}",
    string s => $"String: {s}",
    _ => "Unknown"
};

// Null-coalescing assignment
_cache ??= new Dictionary<string, object>();
```

### 2. Async All the Way
```csharp
// Good - async all the way
public async Task<User> GetUserAsync(int id)
{
    return await _repository.GetByIdAsync(id);
}

// Bad - blocking on async
public User GetUser(int id)
{
    return _repository.GetByIdAsync(id).Result; // Deadlock risk!
}
```

### 3. Use Dependency Injection
```csharp
// Good - constructor injection
public class UserService
{
    private readonly IUserRepository _repository;

    public UserService(IUserRepository repository)
    {
        _repository = repository;
    }
}

// Bad - new keyword
public class UserService
{
    private readonly UserRepository _repository = new UserRepository();
}
```

### 4. IDisposable Pattern
```csharp
public class ResourceManager : IDisposable
{
    private bool _disposed;
    private readonly FileStream _stream;

    public ResourceManager(string path)
    {
        _stream = new FileStream(path, FileMode.Open);
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (_disposed) return;

        if (disposing)
        {
            _stream?.Dispose();
        }

        _disposed = true;
    }
}

// Usage
using var manager = new ResourceManager("file.txt");
```

### 5. Configuration
```csharp
// appsettings.json
{
  "ConnectionStrings": {
    "DefaultConnection": "Server=localhost;Database=mydb"
  },
  "EmailSettings": {
    "SmtpServer": "smtp.example.com",
    "Port": 587
  }
}

// Strongly-typed configuration
public class EmailSettings
{
    public string SmtpServer { get; set; } = string.Empty;
    public int Port { get; set; }
}

// Register
builder.Services.Configure<EmailSettings>(
    builder.Configuration.GetSection("EmailSettings"));

// Use
public class EmailService
{
    private readonly EmailSettings _settings;

    public EmailService(IOptions<EmailSettings> settings)
    {
        _settings = settings.Value;
    }
}
```

## Common Patterns

### Repository Pattern
```csharp
public interface IRepository<T> where T : class
{
    Task<T?> GetByIdAsync(int id);
    Task<IEnumerable<T>> GetAllAsync();
    Task<T> CreateAsync(T entity);
    Task<T> UpdateAsync(T entity);
    Task<bool> DeleteAsync(int id);
}

public class Repository<T> : IRepository<T> where T : class
{
    private readonly AppDbContext _context;
    private readonly DbSet<T> _dbSet;

    public Repository(AppDbContext context)
    {
        _context = context;
        _dbSet = context.Set<T>();
    }

    public async Task<T?> GetByIdAsync(int id) => await _dbSet.FindAsync(id);
    public async Task<IEnumerable<T>> GetAllAsync() => await _dbSet.ToListAsync();

    public async Task<T> CreateAsync(T entity)
    {
        await _dbSet.AddAsync(entity);
        await _context.SaveChangesAsync();
        return entity;
    }
}
```

### Result Pattern
```csharp
public record Result<T>
{
    public bool IsSuccess { get; init; }
    public T? Value { get; init; }
    public string? Error { get; init; }

    public static Result<T> Success(T value) => new() { IsSuccess = true, Value = value };
    public static Result<T> Failure(string error) => new() { IsSuccess = false, Error = error };
}

// Usage
public async Task<Result<User>> GetUserAsync(int id)
{
    var user = await _repository.GetByIdAsync(id);
    return user is not null
        ? Result<User>.Success(user)
        : Result<User>.Failure("User not found");
}
```

## Approach

When writing C# code:

1. **Use Modern Features**: Records, pattern matching, nullable references
2. **Async Everywhere**: Don't block on async code
3. **Dependency Injection**: Constructor injection for testability
4. **LINQ for Queries**: Readable and maintainable data operations
5. **Test Thoroughly**: Unit tests with xUnit and Moq
6. **Follow Conventions**: Pascal case for public, camel case for private
7. **Use EF Core**: ORM for database access
8. **Leverage .NET 8+**: Latest features and performance improvements

Always write clean, performant, and maintainable C# code following .NET best practices.

Overview

This skill provides expert C# development guidance for modern .NET applications (C# 12+, .NET 8+). It focuses on clean, performant, and maintainable code across ASP.NET Core, LINQ, async programming, and enterprise patterns. Use it to design APIs, data access, concurrency, and scalable service architectures.

How this skill works

I inspect requirements and suggest idiomatic C# solutions: modern language features (primary constructors, collection expressions, records, pattern matching), async/await and IAsyncEnumerable patterns, LINQ for expressive queries, and ASP.NET Core best practices (minimal APIs, controllers, DI). I provide concrete code guidance, performance trade-offs, and refactor suggestions for production scenarios.

When to use it

  • Designing or refactoring ASP.NET Core APIs with .NET 8+
  • Implementing high-performance async workflows and parallel operations
  • Writing expressive, safe LINQ queries and complex aggregations
  • Modeling domain and DTOs with records and pattern matching
  • Implementing Entity Framework Core DbContext and repository/service layers

Best practices

  • Prefer records and primary constructors for immutable, concise models where appropriate
  • Use ValueTask and Task.WhenAll to reduce allocations and run parallel I/O-bound work
  • favor method-syntax LINQ for readability and composability; push filters before projections for performance
  • Apply cancellation tokens and timeouts to all external calls (HttpClient, DB operations)
  • Register services with appropriate lifetimes (Scoped for DbContext, Singleton for caches, Transient for lightweight helpers)
  • Validate incoming requests and return explicit HTTP status codes; log and handle exceptions at service boundaries

Example use cases

  • Create a minimal API with DI, OpenAPI, and paged endpoints using EF Core and LINQ
  • Refactor blocking I/O to async/await, use Task.WhenAll to parallelize independent calls
  • Implement streaming results with IAsyncEnumerable for large data sets
  • Design DTOs and domain models with record types and pattern matching for concise validation and mapping
  • Optimize caching with ValueTask-returning cache lookups and fallback to database

FAQ

Should I always use records and primary constructors?

Use records for immutable data and value-like types; primary constructors reduce boilerplate. For entities with mutable state (EF Core tracked entities), prefer classes with clear property mapping.

When to prefer ValueTask over Task?

Use ValueTask when methods often complete synchronously to avoid allocations; prefer Task for most async methods to keep code simple. Benchmark before adopting broadly.