Skip to content
Development
Skill

/csharp-patterns

C#/.NET: LINQ, async/await, DI, records, nullable refs, ASP.NET Core, EF Core, MediatR. Triggers: C#, .NET, dotnet, ASP.NET, EF Core, LINQ, record type, IServiceCollection.

From plugin
ai-toolkit
161111 skills44 agents
Install
$ npx -y skills add softspark/ai-toolkit --skill csharp-patterns --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/csharp-patterns

Context preview

The summary Claude sees to decide when to auto-load this skill.

C#/.NET: LINQ, async/await, DI, records, nullable refs, ASP.NET Core, EF Core, MediatR. Triggers: C#, .NET, dotnet, ASP.NET, EF Core, LINQ, record type, IServiceCollection.

SKILL.md

csharp-patterns.SKILL.md
name: csharp-patterns
description: "C#/.NET: LINQ, async/await, DI, records, nullable refs, ASP.NET Core, EF Core, MediatR. Triggers: C#, .NET, dotnet, ASP.NET, EF Core, LINQ, record type, IServiceCollection."
effort: medium
user-invocable: false
allowed-tools: Read

C# / .NET Patterns

Project Structure

Solution Layout

MyApp.sln
Directory.Build.props              # Shared build properties
Directory.Packages.props           # Central package management
src/
  MyApp.Api/                       # ASP.NET Core host (Controllers, Middleware, Program.cs)
  MyApp.Application/               # Use cases, MediatR handlers, Behaviors
  MyApp.Domain/                    # Entities, value objects, domain events
  MyApp.Infrastructure/            # EF Core, external services
tests/
  MyApp.UnitTests/
  MyApp.IntegrationTests/

Directory.Build.props

<Project>
  <PropertyGroup>
    <TargetFramework>net9.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
    <EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
    <AnalysisLevel>latest-recommended</AnalysisLevel>
  </PropertyGroup>
</Project>

Central Package Management (Directory.Packages.props)

<Project>
  <PropertyGroup>
    <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
  </PropertyGroup>
  <ItemGroup>
    <PackageVersion Include="MediatR" Version="12.4.1" />
    <PackageVersion Include="FluentValidation" Version="11.11.0" />
    <PackageVersion Include="Microsoft.EntityFrameworkCore" Version="9.0.0" />
  </ItemGroup>
</Project>

---

Idioms / Code Style

Nullable Reference Types

public class Order
{
    public required string Id { get; init; }
    public string? Notes { get; set; }               // explicitly nullable
    public required Customer Customer { get; init; }  // never null
    public string Summary => $"Order {Id}: {Notes ?? "no notes"}";
}

Records

public record Money(decimal Amount, string Currency)
{
    public Money Add(Money other) => Currency != other.Currency
        ? throw new InvalidOperationException("Currency mismatch")
        : this with { Amount = Amount + other.Amount };
}

public record CreateOrderRequest(string CustomerId, List<OrderLineDto> Lines);
public record OrderLineDto(string ProductId, int Quantity);

Pattern Matching

public decimal CalculateDiscount(Customer c) => c switch
{
    { Tier: CustomerTier.Gold, TotalSpent: > 10_000m } => 0.20m,
    { Tier: CustomerTier.Gold }                        => 0.15m,
    { Tier: CustomerTier.Silver }                      => 0.10m,
    { IsNewCustomer: true }                            => 0.05m,
    _                                                  => 0m
};

// List patterns (.NET 8+)
public string Describe(int[] v) => v switch
{
    [] => "empty", [var x] => $"one: {x}", [var f, .., var l] => $"{f}..{l}",
};

LINQ

var activeUsers = users
    .Where(u => u.IsActive)
    .OrderByDescending(u => u.LastLogin)
    .Select(u => new UserDto(u.Id, u.Name))
    .ToList();

Async/Await

// Always: Async suffix, CancellationToken parameter, never .Result/.Wait()
public async Task<Order?> GetOrderAsync(string id, CancellationToken ct = default)
    => await _db.Orders.Include(o => o.Lines).FirstOrDefaultAsync(o => o.Id == id, ct);

// Parallel independent tasks
var ordersTask = _orderRepo.GetRecentAsync(ct);
var statsTask = _statsService.ComputeAsync(ct);
await Task.WhenAll(ordersTask, statsTask);

Primary Constructors (C# 12)

public class OrderService(IOrderRepository repo, ILogger<OrderService> logger, IPublisher pub)
{
    public async Task<Order> CreateAsync(CreateOrderRequest req, CancellationToken ct)
    {
        logger.LogInformation("Creating order for {CustomerId}", req.CustomerId);
        var order = Order.Create(req);
        await repo.AddAsync(order, ct);
        await pub.Publish(new OrderCreatedEvent(order.Id), ct);
        return order;
    }
}

---

Error Handling

Result Pattern

public sealed class Result<T>
{
    public T? Value { get; }
    public Error? Error { get; }
    public bool IsSuccess => Error is null;
    private Result(T value) => Value = value;
    private Result(Error error) => Error = error;
    public static Result<T> Success(T value) => new(value);
    public static Result<T> Failure(Error error) => new(error);
    public TOut Match<TOut>(Func<T, TOut> ok, Func<Error, TOut> err) =>
        IsSuccess ? ok(Value!) : err(Error!);
}
public record Error(string Code, string Message);

FluentValidation + MediatR Pipeline

public class CreateOrderValidator : AbstractValidator<CreateOrderRequest>
{
    public CreateOrderValidator()
    {
        RuleFor(x => x.CustomerId).NotEmpty().MaximumLength(36);
        RuleForEach(x => x.Lines).ChildRules(line =>
        {
            line.RuleFor(l => l.ProductId).NotEmpty();
            line.RuleFor(l => l.Quantity).GreaterThan(0).LessThanOrEqualTo(1000);
        });
    }
}

public class ValidationBehavior<TReq, TRes>(IEnumerable<IValidator<TReq>> validators)
    : IPipelineBehavior<TReq, TRes> where TReq : IRequest<TRes>
{
    public async Task<TRes> Handle(TReq req, RequestHandlerDelegate<TRes> next, CancellationToken ct)
    {
        var failures = validators.Select(v => v.Validate(req)).SelectMany(r => r.Errors).ToList();
        return failures.Count > 0 ? throw new ValidationException(failures) : await next();
    }
}

IAsyncDisposable

public sealed class TempFileHandle(string path) : IAsyncDisposable
{
    public async ValueTask DisposeAsync()
    {
        if (File.Exists(path)) await Task.Run(() => File.Delete(path));
    }
}
// Usage: await using var handle = new TempFileHandle("/tmp/export.csv");

---

Testing Patterns

xUnit + NSubstitute + FluentAsser

Read more
Ships withai-toolkit

Professional-grade AI coding toolkit with multi-platform support. Machine-enforced safety, 109 skills, 44 agents, expanded lifecycle hooks, persona presets, experimental opt-in plugin packs, and benchmark tooling — works with Claude Code, Claude Chat/Cowork,

Get the whole plugin