Agent Skills: Modern C#

|

UncategorizedID: phrazzld/claude-config/csharp-modern

Install this agent skill to your local

pnpm dlx add-skill https://github.com/phrazzld/claude-config/tree/HEAD/skills/csharp-modern

Skill Files

Browse the full folder contents for csharp-modern.

Download Skill

Loading file tree…

skills/csharp-modern/SKILL.md

Skill Metadata

Name
csharp-modern
Description
|

Modern C#

.NET 8+, nullable enabled, async-first, records for data.

Async Patterns

Always use async/await for I/O. Always pass CancellationToken:

public async Task<User?> GetUserAsync(
    int id,
    CancellationToken cancellationToken = default)
{
    using var connection = await _factory
        .CreateConnectionAsync(cancellationToken)
        .ConfigureAwait(false);

    return await connection
        .QuerySingleOrDefaultAsync<User>(sql, cancellationToken: cancellationToken)
        .ConfigureAwait(false);
}

ConfigureAwait(false) in library code. Never block on async (.Result, .Wait()).

Nullable Reference Types

Enable project-wide. Treat warnings as errors:

<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>

Explicit nullability:

// Nullable when user might not exist
Task<User?> GetByIdAsync(int id);

// Non-nullable with exception on not found
Task<User> GetRequiredByIdAsync(int id);

// Handle nulls explicitly
if (user is not null) { ProcessUser(user); }
var name = user?.Name ?? "Anonymous";

Records & Immutability

Records for DTOs and value types:

// DTO
public record CreateOrderRequest(
    string CustomerId,
    IReadOnlyList<OrderItemDto> Items);

// Domain entity
public record class Order
{
    public string Id { get; init; }
    public OrderStatus Status { get; init; }

    public Order Ship() => this with { Status = OrderStatus.Shipped };
}

// Value type (<16 bytes)
public readonly record struct Money(decimal Amount, string Currency);

Never expose mutable collections. Use IReadOnlyList<T>.

Pattern Matching

Switch expressions over if-else chains:

public decimal CalculateDiscount(object discount) => discount switch
{
    decimal amount => amount,
    int percentage => percentage / 100m,
    string code => GetDiscountForCode(code),
    _ => throw new ArgumentException("Unsupported type")
};

public string GetShipping(Order order) => order switch
{
    { TotalAmount: > 100, Customer.IsPremium: true } => "Free Express",
    { TotalAmount: > 100 } => "Free Standard",
    _ => "Standard"
};

Project Setup

<!-- Directory.Build.props -->
<PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <LangVersion>12.0</LangVersion>
    <Nullable>enable</Nullable>
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
    <EnableNETAnalyzers>true</EnableNETAnalyzers>
    <AnalysisLevel>latest-recommended</AnalysisLevel>
</PropertyGroup>

Anti-Patterns

  • Blocking on async (.Result, .Wait())
  • async void outside event handlers
  • Missing ConfigureAwait(false) in libraries
  • null! without documented justification
  • Mutable DTOs with public setters
  • Switch statements over switch expressions
  • Legacy .csproj or packages.config